[Java knowledge points detailed 2] dynamic agent

1. Agency model

The definition of proxy mode: Provide a proxy for other objects to control access to this object. In some cases, an object is not suitable or cannot directly reference another object, and the proxy object can play an intermediary role between the client and the target object.

A well-known example of proxy mode is reference counting (English: reference counting) pointers.

When multiple copies of a complex object must exist, the proxy mode can be combined with the flyweight mode to reduce memory usage. The typical approach is to create a complex object and multiple agents, each agent will refer to the original complex object. The calculations acting on the agent will be transferred to the original object. Once all the agents are gone, the complex object will be removed.

2. Composition

Abstract role: A business method implemented by declaring a real role through an interface or abstract class.

Agent role: Realize abstract role, which is the agent of real role. The abstract method is realized through the business logic method of real role, and you can attach your own operations.

Real role: implement abstract roles, define the business logic to be implemented by real roles, and be called by agent roles.

3. Advantages

1. Clear responsibilities

The real role is to realize the actual business logic. You don't need to care about other affairs that are not your responsibility. The later agent completes a completed transaction. The incidental result is that the programming is simple and clear.

2. Object of protection

The proxy object can play an intermediary role between the client and the target object, thus playing the role of an intermediary and protecting the target object.

3. High scalability

Four, model structure

One is the real object you want to visit (target class), and the other is the proxy object. The real object and the proxy
  object implement the same interface. First visit the proxy class and then visit the real object.

The proxy mode is divided into static proxy and dynamic proxy.

The static proxy is created by the programmer or the tool generates the source code of the proxy class, and then compiles the proxy class. The so-called static means that the bytecode file of the proxy class exists before the program runs, and the relationship between the proxy class and the delegate class is determined before the program runs.

The dynamic agent does not need to care about the agent class in the implementation phase, but only specifies which object in the runtime phase.

Five, static proxy

Create an interface, and then create a proxy class that implements the interface and implements the abstract methods in the interface. Then create a proxy class and make it also implement this interface. Hold a reference to the proxy object in the proxy class, and then call the method of the object in the proxy class method.

It is easy to use a static proxy to complete the proxy operation for a class. But the shortcomings of static proxy are also exposed: since the proxy can only serve one class, if there are many classes to be proxied, then a large number of proxy classes need to be written, which is cumbersome.

Six, dynamic agency

1. Dynamic agent flow chart

2. Implementation of dynamic proxy code

(1) Agency

Use the reflection mechanism to create proxy classes at runtime.
The interface and the proxy class remain unchanged, we build a ProxyInvocationHandler class to implement the InvocationHandler interface.

package com.guor.aop.dynamicproxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class ProxyInvocationHandler implements InvocationHandler {
	private Object target;
	
	public Object getTarget() {
		return target;
	}
 
	public void setTarget(Object target) {
		this.target = target;
	}
 
	//生成得到代理类
	public Object getProxy() {
		return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
	}
	
	//处理代理实例,并返回结果
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		log(method.getName());
		//动态代理的本质就是使用反射机制来实现
		Object result = method.invoke(target, args);
		return result;
	}
	
	public void log(String msg) {
		System.out.println("执行了"+msg+"方法");
	}
}

 Return a proxy instance of an interface through the static method newProxyInstance of the Proxy class. For different agent classes, pass in the corresponding agent controller InvocationHandler. 

(2) The proxy class UserService

package com.guor.aop;
 
public interface UserService {
	public void add();
	public void delete();
	public void update();
	public void query();
}
package com.guor.aop;
 
public class UserServiceImpl implements UserService {
 
	public void add() {
		System.out.println("add");
	}
 
	public void delete() {
		System.out.println("delete");
	}
 
	public void update() {
		System.out.println("update");
	}
 
	public void query() {
		System.out.println("query");
	}
 
}

(3) Implement dynamic proxy

package com.guor.aop.dynamicproxy;
 
import com.guor.aop.UserService;
import com.guor.aop.UserServiceImpl;
 
public class Client {
 
	public static void main(String[] args) {
		//真实角色
		UserService userService = new UserServiceImpl();
		
		//代理角色
		ProxyInvocationHandler pih = new ProxyInvocationHandler();
		//通过调用程序处理角色来处理我们要调用的接口对象
		pih.setTarget(userService);
		
		UserService proxy = (UserService) pih.getProxy();
		proxy.update();
	}
 
}

(4) Console output 

Seven, the underlying implementation of dynamic proxy

1. Specific steps of dynamic agent

  • Create your own invocation handler by implementing the InvocationHandler interface;
  • Create a dynamic proxy class by specifying a ClassLoader object and a set of interfaces for the Proxy class;
  • Obtain the constructor of the dynamic proxy class through the reflection mechanism, and its only parameter type is the call processor interface type;
  • The dynamic proxy class instance is created through the constructor, and the processor object is called as a parameter during construction.

2. Source code analysis

(1)newProxyInstance

Since the static method newProxyInstance of the Proxy class is used to generate the proxy object, let's go to its source code to see what it does?

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
									  Class<?>[] interfaces,
									  InvocationHandler h)
	throws IllegalArgumentException
{
	Objects.requireNonNull(h);
 
	final Class<?>[] intfs = interfaces.clone();
	final SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
		checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
	}
 
	/*
	 * Look up or generate the designated proxy class.
	 */
	Class<?> cl = getProxyClass0(loader, intfs);
 
	/*
	 * Invoke its constructor with the designated invocation handler.
	 */
	try {
		if (sm != null) {
			checkNewProxyPermission(Reflection.getCallerClass(), cl);
		}
 
		final Constructor<?> cons = cl.getConstructor(constructorParams);
		final InvocationHandler ih = h;
		if (!Modifier.isPublic(cl.getModifiers())) {
			AccessController.doPrivileged(new PrivilegedAction<Void>() {
				public Void run() {
					cons.setAccessible(true);
					return null;
				}
			});
		}
		return cons.newInstance(new Object[]{h});
	} catch (IllegalAccessException|InstantiationException e) {
		throw new InternalError(e.toString(), e);
	} catch (InvocationTargetException e) {
		Throwable t = e.getCause();
		if (t instanceof RuntimeException) {
			throw (RuntimeException) t;
		} else {
			throw new InternalError(t.toString(), t);
		}
	} catch (NoSuchMethodException e) {
		throw new InternalError(e.toString(), e);
	}
}

(2)getProxyClass0

Use getProxyClass0(loader, intfs) to generate the Class object of the proxy class Proxy.

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
	if (interfaces.length > 65535) {
		throw new IllegalArgumentException("interface limit exceeded");
	}
 
	// If the proxy class defined by the given loader implementing
	// the given interfaces exists, this will simply return the cached copy;
	// otherwise, it will create the proxy class via the ProxyClassFactory
	return proxyClassCache.get(loader, interfaces);
}

(3)ProxyClassFactory

ProxyClassFactory internal classes create and define proxy classes, and return proxy classes for a given ClassLoader and interfaces.

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
	// prefix for all proxy class names
	private static final String proxyClassNamePrefix = "$Proxy";
 
	// next number to use for generation of unique proxy class names
	private static final AtomicLong nextUniqueNumber = new AtomicLong();
 
	@Override
	public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
 
		Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
		for (Class<?> intf : interfaces) {
			/*
			 * Verify that the class loader resolves the name of this
			 * interface to the same Class object.
			 */
			Class<?> interfaceClass = null;
			try {
				interfaceClass = Class.forName(intf.getName(), false, loader);
			} catch (ClassNotFoundException e) {
			}
			if (interfaceClass != intf) {
				throw new IllegalArgumentException(
					intf + " is not visible from class loader");
			}
			/*
			 * Verify that the Class object actually represents an
			 * interface.
			 */
			if (!interfaceClass.isInterface()) {
				throw new IllegalArgumentException(
					interfaceClass.getName() + " is not an interface");
			}
			/*
			 * Verify that this interface is not a duplicate.
			 */
			if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
				throw new IllegalArgumentException(
					"repeated interface: " + interfaceClass.getName());
			}
		}
 
		String proxyPkg = null;     // package to define proxy class in
		int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
 
		/*
		 * Record the package of a non-public proxy interface so that the
		 * proxy class will be defined in the same package.  Verify that
		 * all non-public proxy interfaces are in the same package.
		 */
		for (Class<?> intf : interfaces) {
			int flags = intf.getModifiers();
			if (!Modifier.isPublic(flags)) {
				accessFlags = Modifier.FINAL;
				String name = intf.getName();
				int n = name.lastIndexOf('.');
				String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
				if (proxyPkg == null) {
					proxyPkg = pkg;
				} else if (!pkg.equals(proxyPkg)) {
					throw new IllegalArgumentException(
						"non-public interfaces from different packages");
				}
			}
		}
 
		if (proxyPkg == null) {
			// if no non-public proxy interfaces, use com.sun.proxy package
			proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
 
		/*
		 * Choose a name for the proxy class to generate.
		 */
		long num = nextUniqueNumber.getAndIncrement();
		String proxyName = proxyPkg + proxyClassNamePrefix + num;
 
		/*
		 * Generate the specified proxy class.
		 */
		byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
			proxyName, interfaces, accessFlags);
		try {
			return defineClass0(loader, proxyName,
								proxyClassFile, 0, proxyClassFile.length);
		} catch (ClassFormatError e) {
			/*
			 * A ClassFormatError here means that (barring bugs in the
			 * proxy class generation code) there was some other
			 * invalid aspect of the arguments supplied to the proxy
			 * class creation (such as virtual machine limitations
			 * exceeded).
			 */
			throw new IllegalArgumentException(e.toString());
		}
	}
}

(4)generateProxyClass

After a series of checks, call ProxyGenerator.generateProxyClass to generate a bytecode file.

public static byte[] generateProxyClass(final String name,Class<?>[] interfaces,int accessFlags)
{
	ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
	final byte[] classFile = gen.generateClassFile();
 
	if (saveGeneratedFiles) {
		java.security.AccessController.doPrivileged(
		new java.security.PrivilegedAction<Void>() {
			public Void run() {
				try {
					int i = name.lastIndexOf('.');
					Path path;
					if (i > 0) {
						Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
						Files.createDirectories(dir);
						path = dir.resolve(name.substring(i+1, name.length()) + ".class");
					} else {
						path = Paths.get(name + ".class");
					}
					Files.write(path, classFile);
					return null;
				} catch (IOException e) {
					throw new InternalError(
						"I/O exception saving generated file: " + e);
				}
			}
		});
	}
 
	return classFile;
}

(5)generateClassFile

The generateClassFile method to generate the proxy class bytecode file:

private byte[] generateClassFile() {
	/* ============================================================
	 * Step 1: Assemble ProxyMethod objects for all methods to
	 * generate proxy dispatching code for.
	 */
 
	/*
	 * Record that proxy methods are needed for the hashCode, equals,
	 * and toString methods of java.lang.Object.  This is done before
	 * the methods from the proxy interfaces so that the methods from
	 * java.lang.Object take precedence over duplicate methods in the
	 * proxy interfaces.
	 */
	addProxyMethod(hashCodeMethod, Object.class);
	addProxyMethod(equalsMethod, Object.class);
	addProxyMethod(toStringMethod, Object.class);
 
	/*
	 * Now record all of the methods from the proxy interfaces, giving
	 * earlier interfaces precedence over later ones with duplicate
	 * methods.
	 */
	for (Class<?> intf : interfaces) {
		for (Method m : intf.getMethods()) {
			addProxyMethod(m, intf);
		}
	}
 
	/*
	 * For each set of proxy methods with the same signature,
	 * verify that the methods' return types are compatible.
	 */
	for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
		checkReturnTypes(sigmethods);
	}
 
	/* ============================================================
	 * Step 2: Assemble FieldInfo and MethodInfo structs for all of
	 * fields and methods in the class we are generating.
	 */
	try {
		methods.add(generateConstructor());
 
		for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
			for (ProxyMethod pm : sigmethods) {
 
				// add static field for method's Method object
				fields.add(new FieldInfo(pm.methodFieldName,
					"Ljava/lang/reflect/Method;",
					 ACC_PRIVATE | ACC_STATIC));
 
				// generate code for proxy method and add it
				methods.add(pm.generateMethod());
			}
		}
 
		methods.add(generateStaticInitializer());
 
	} catch (IOException e) {
		throw new InternalError("unexpected I/O Exception", e);
	}
 
	if (methods.size() > 65535) {
		throw new IllegalArgumentException("method limit exceeded");
	}
	if (fields.size() > 65535) {
		throw new IllegalArgumentException("field limit exceeded");
	}
 
	/* ============================================================
	 * Step 3: Write the final class file.
	 */
 
	/*
	 * Make sure that constant pool indexes are reserved for the
	 * following items before starting to write the final class file.
	 */
	cp.getClass(dotToSlash(className));
	cp.getClass(superclassName);
	for (Class<?> intf: interfaces) {
		cp.getClass(dotToSlash(intf.getName()));
	}
 
	/*
	 * Disallow new constant pool additions beyond this point, since
	 * we are about to write the final constant pool table.
	 */
	cp.setReadOnly();
 
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream dout = new DataOutputStream(bout);
 
	try {
		/*
		 * Write all the items of the "ClassFile" structure.
		 * See JVMS section 4.1.
		 */
									// u4 magic;
		dout.writeInt(0xCAFEBABE);
									// u2 minor_version;
		dout.writeShort(CLASSFILE_MINOR_VERSION);
									// u2 major_version;
		dout.writeShort(CLASSFILE_MAJOR_VERSION);
 
		cp.write(dout);             // (write constant pool)
 
									// u2 access_flags;
		dout.writeShort(accessFlags);
									// u2 this_class;
		dout.writeShort(cp.getClass(dotToSlash(className)));
									// u2 super_class;
		dout.writeShort(cp.getClass(superclassName));
 
									// u2 interfaces_count;
		dout.writeShort(interfaces.length);
									// u2 interfaces[interfaces_count];
		for (Class<?> intf : interfaces) {
			dout.writeShort(cp.getClass(
				dotToSlash(intf.getName())));
		}
 
									// u2 fields_count;
		dout.writeShort(fields.size());
									// field_info fields[fields_count];
		for (FieldInfo f : fields) {
			f.write(dout);
		}
 
									// u2 methods_count;
		dout.writeShort(methods.size());
									// method_info methods[methods_count];
		for (MethodInfo m : methods) {
			m.write(dout);
		}
 
									 // u2 attributes_count;
		dout.writeShort(0); // (no ClassFile attributes for proxy classes)
 
	} catch (IOException e) {
		throw new InternalError("unexpected I/O Exception", e);
	}
 
	return bout.toByteArray();
}

After the bytecode is generated, defineClass0 is called to parse the bytecode, and the Proxy Class object is generated. After understanding the dynamic generation process of the proxy class, what is the proxy class produced, and who will execute the proxy class.

Among them, saveGeneratedFiles in the ProxyGenerator.generateProxyClass function is defined as follows, which refers to whether to save the generated proxy class file, and the default false is not saved.

In the previous example, we modified this system variable:

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

 

Previous: [Summary of the most complete Java framework in the full stack] SSH, SSM, Springboot

Next: Spring common annotations (absolutely classic)

Guess you like

Origin blog.csdn.net/guorui_java/article/details/108630273