1. Definition of proxy mode
Provide an object with a proxy object, and the proxy object controls access to the original object, that is, the client does not directly manipulate the original object, but indirectly manipulates the original object through the proxy object.
An example of a famous proxy pattern is reference counting: When multiple copies of a complex object are needed, the proxy pattern can be combined with the meta mode to reduce the amount of memory. A typical approach is to create a complex object and multiple proxy, each proxy referring to the original object. The operations that act on the agent will be forwarded to the original object. Once all agents do not exist, complex objects are removed.
It is simple to understand the proxy model, but in fact there is an proxy model in life:
We can buy train tickets at the train station, but we can also buy them at the train ticket sales office. The train ticket sales office here is the agent for ticket purchases at the train station. That is, we issue a ticket purchase request at the sales outlet. The sales outlet will send the request to the train station, and the train station will send the successful response to the purchase to the sales outlet, and the sales outlet will tell you again.
However, tickets can only be purchased at the sales outlet, but not refunds, while tickets can be purchased at the train station, so the operations supported by the agent may be different from those of the commissioned object.
Let me give you another example that you will encounter when writing a program:
If there is an existing project (you don't have source code, you can only call it) that can call int compute(String exp1) to implement the calculation of the suffix expression. If you want to use this project to implement the calculation of the infix expression, then you can write a proxy class and define a compute(String exp2). This exp2 parameter is an infix expression. Therefore, you need to convert the infix expression into a suffix expression (Preprocess) before calling the compute() of the existing project, and then call the compute() of the existing project. Of course, you can also receive the return value and do other operations, such as saving the file (Postprocess). This process uses the proxy mode.
When using a computer, you will also encounter proxy mode applications:
Remote proxy: We cannot access Facebook because of GFW in China. We can access it by browsing the wall (setting up a proxy). The access process is:
(1) The user sends the HTTP request to the proxy
(2) The proxy sends HTTP request to the web server
(3) The web server sends the HTTP response to the proxy
(4) The proxy sends the HTTP response back to the user
2. Static proxy
The so-called static proxy means that a proxy class is generated during the compilation stage to complete a series of operations on the proxy object. The following is the structure class diagram of the proxy pattern:
1. Participants in the proxy model
There are four roles in the proxy mode:
Topic interface: that is, the behavioral interface implemented by the proxy class.
Target object: That is, the object being proxyed.
Proxy object: The proxy client used to encapsulate the real topic class is the following is the class diagram structure of the proxy pattern:
2. Ideas for implementing agent model
Both the proxy object and the target object implement the same behavioral interface.
The proxy class and the target class implement the interface logic separately.
Instantiate a target object in the constructor of the proxy class.
Calling the behavioral interface of the target object in the proxy class.
If the client wants to call the behavioral interface of the target object, it can only operate through the proxy class.
3. Examples of static proxy
The following is a lazy loading example to illustrate the static proxy. When we start a service system, it may take a long time to load a certain class. In order to obtain better performance, when starting the system, we often do not initialize this complex class, but instead initialize its proxy class. This will separate the resource-consuming methods using proxy for separation, which can speed up the system startup speed and reduce the user's waiting time.
Define a topic interface
public interface Subject { public void saysHello(); public void saysGoodBye();} Define a target class and implement the topic interface
public class RealSubject implements Subject { public void saysHello() { System.out.println("Hello World"); } public void saysGoodBye() { System.out.println("GoodBye World"); }} Define a proxy class to proxy the target object.
public class StaticProxy implements Subject { Private RealSubject realSubject = null; public StaticProxy() {} public void saysHello() { //It is loaded at that time, lazy loading if(realSubject == null) { realSubject = new RealSubject(); } realSubject.sayHello(); } //SayGoodbye method is the same...} Define a client
public class Client { public static void main(String [] args) { StaticProxy sp = new StaticProxy(); sp.sayHello(); sp.sayGoodBye(); }}The above is a simple test example of a static proxy. It may not feel practical. However, this is not the case. Using a proxy, we can also transform the target object methods. For example, a series of connections are created in the database connection pool. In order to ensure that connections are opened infrequently, these connections are almost never closed. However, we always have the habit of closing the open Connection. In this way, we can use the proxy mode to reproxy the close method in the Connection interface, and change it to recycle it into the database connection pool instead of actually executing the Connection#close method. There are many other examples, and you need to experience them yourself.
3. Dynamic Agent
Dynamic proxy refers to dynamically generating proxy classes at runtime. That is, the bytecode of the proxy class will be generated and loaded at runtime to the ClassLoader of the current proxy. Compared with static processing classes, dynamic classes have many benefits.
There is no need to write a completely identical encapsulation class for the real topic. If there are many methods in the topic interface, it is also troublesome to write a proxy method for each interface. If the interface changes, the real theme and proxy classes must be modified, which is not conducive to system maintenance;
Using some dynamic proxy generation methods can even formulate the execution logic of the proxy class at runtime, thereby greatly improving the flexibility of the system.
There are many ways to generate dynamic proxy: the JDK comes with dynamic proxy, CGlib, javassist, etc. These methods have their own advantages and disadvantages. This article mainly explores the use of dynamic proxy and source code analysis in JDK.
Here is an example to explain the usage of dynamic proxy in JDK:
public class dynamicProxy implements InvocationHandler { private RealSubject = null; public Object invoke(Object proxy, Method method, Object[] args){ if(RealSubject == null) { RealSubject = new RealSubject(); } method.invoke(RealSubject, args); return RealSubject; }}Client code example
public class Client { public static void main(Strings[] args) { Subject subject = (Subject)Proxy.newInstance(ClassLoader.getSystemLoader(), RealSubject.class.getInterfaces(), new DynamicProxy()); Subject.sayHello(); Subject.sayGoodBye(); }}As can be seen from the above code, we need to use dynamic proxy in JDK. Use the static method Proxy.newInstance(ClassLoader, Interfaces[], InvokeHandler) to create a dynamic proxy class. The newInstance method has three parameters, which represent the class loader, a list of interfaces that you want the proxy class to be implemented, and an instance that implements the InvokeHandler interface. The dynamic proxy handed over the execution process of each method to the Invoke method for processing.
JDK dynamic proxy requires that the proxy must be an interface, but a simple class cannot. The proxy classes generated by JDK dynamic proxy will inherit the Proxy class, and the proxy class will implement all the interface list you passed in. Therefore, the type can be cast to the interface type. Below is the structure diagram of Proxy.
It can be seen that Proxy is all static methods, so if the proxy class does not implement any interface, then it is the Proxy type and has no instance methods.
Of course, if you join, you have to proxy a class that does not implement a certain interface, and the methods of this class are the same as those defined by other interfaces, and it can be easily implemented using reflection.
public class DynamicProxy implements InvokeHandler { //The class you want to proxy private TargetClass targetClass = null; //Initialize this class public DynamicProxy(TargetClass targetClass) { this.targetClass = targetClass; } public Object invoke(Object proxy, Method method, Object[] args) { //Use reflection to obtain the class you want to proxy Method myMethod = targetClass.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); myMethod.setAccessible(true); return myMethod.invoke(targetClass, args); }}4. JDK dynamic proxy source code analysis (JDK7)
After looking at the example above, we just know how to use dynamic proxy. However, it is still foggy about how the proxy class is created, who called the Invoke method, etc. The following analysis
1. How are proxy objects created?
First look at the source code of the Proxy.newInstance method:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { } //Get interface information final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } //Generate proxy class Class<?> cl = getProxyClass0(loader, intfs); // ...OK Let's look at the first half first}From the source code, it can be seen that the generation of proxy classes depends on the getProxyClass0 method. Next, let’s take a look at the getProxyClass0 source code:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //The number of interface lists cannot exceed 0xFFFF if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //Note here, the following explanation is given in detail to return proxyClassCache.get(loader, interfaces); } The explanation of proxyClassCache.get is: If the proxy class that implements the interface list already exists, then take it directly from the cache. If it does not exist, one is generated through ProxyClassFactory.
Before looking at the source code of proxyClassCache.get, let’s briefly understand proxyClassCache:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache is a WeakCache type cache. Its constructor has two parameters. One of them is the ProxyClassFactory used to generate the proxy class. The following is the source code of proxyClassCache.get:
final class WeakCache<K, P, V> { ... public V get(K key, P parameter) {}}Here K represents key, P represents parameters, V represents value
public V get(K key, P parameter) { //java7 NullObject judgment method, if the parameter is empty, an exception with the specified message will be thrown. If not empty, return. Objects.requireNonNull(parameter); //Clean the data structure of WeakHashMap that holds weak references, which is generally used to cache expungeStaleEntries(); //Get cacheKey from the queue Object cacheKey = CacheKey.valueOf(key, refQueue); //Fill Supplier with lazy loading. Concurrent is a thread-safe map ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; while (true) { if (supplier != null) { // Get Value from Supplier. This Value may be a factory or cache realization. //The following three sentences are the core code, which returns the class that implements InvokeHandler and contains the required information. V value = supplier.get(); if (value != null) { return value; } } // else no supplier in cache // or a supplier that returned null (can be a cleared CacheValue // or a Factory that wasn't successful in installing the CacheValue) //The following process is the process of filling the supplier if(factory == null) { //Create a factory } if(supplier == null) { //Fill supply }else { //Fill supply } } The function of while loop is to continuously obtain the class that implements InvokeHandler. This class can be obtained from the cache or generated from proxyFactoryClass.
Factory is an internal class that implements the Supplier<V> interface. This class overrides the get method, and an instance method of type proxyFactoryClass is called in the get method. This method is the real way to create a proxy class. Let’s see the source code of the ProxyFactoryClass#apply method:
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 { //Load information about each interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } //If the class loaded with your own classload is not equal to the class you passed in, throw an exception if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } //If the incoming is not an interface type if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } //Verify whether the interface is repeated if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in /* 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. */ //This paragraph depends on whether there are interfaces that are not public in the interface you passed in. If so, all of these interfaces must be defined in one package. Otherwise, throw exceptions for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { 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(); //Generate the class name of the random proxy class, $Proxy + num String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the class file of the proxy class, return the byte stream*/ byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { //End throw new IllegalArgumentException(e.toString()); } } }The aforementioned ProxyFactoryClass#apply is a method to really generate proxy classes, which is actually inaccurate. After reading the source code here, we will find that ProxyGenerator#generateProxyClass is the method to truly generate proxy classes. Generate the corresponding Class file according to the Java class bytecode composition (see my other article Java bytecode learning notes). The specific source code of ProxyGenerator#generateProxyClass is as follows:
private byte[] generateClassFile() { /* * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for. */ // addProxyMethod method is to add all methods to a list and correspond to the corresponding class // Here are three methods corresponding to the Object, toString and equals addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); //Compare the interface in the interface list with the methods under the interface for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } /* * For each set of proxy methods with the same signature, * verify that the methods' return types are compatible. */ for (List<ProxyMethod> signmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } /* * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. */ // Add a constructor method to the method, which is only one constructor, which is a constructor with the InvocationHandler interface.//This is the real method to add a method to the class file, that is, the proxy class. However, it has not been processed yet. It is just added first and wait for the loop. The name description of the constructor in the class file is <init> try { methods.add(generateConstructor()); for (List<ProxyMethod> signmethods : proxyMethods.values()) { for (ProxyMethod pm : signmethods) { //Add a Method type attribute to each proxy method. The number 10 is the identifier of the class file, which means that these attributes are fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); //Add each proxy method to the proxy class method methods.add(pm.generateMethod()); } } //Add a static initialization block and initialize each attribute. Here, the static code block is also called a class constructor. It is actually a method with the name <clinit>, so add it to the method list methods.add(generateStaticInitializer()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } //The number of methods and attributes cannot exceed 65535, including the previous number of interfaces. //This is because in the class file, these numbers are represented in 4-bit hexadecimal, so the maximum value is 2 to the power of 16th -1 if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } //The next step is to write a class file, including magic numbers, class names, constant pools and other series of bytecodes. I won't go into details. If you need it, you can refer to the relevant knowledge of JVM virtual machine bytecode. cp.getClass(dotToSlash(className)); cp.getClass(superclassName); for (int i = 0; i < interfaces.length; i++) { cp.getClass(dotToSlash(interfaces[i].getName())); } cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); try { // 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(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); // 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 (int i = 0; i < interfaces.length; i++) { dout.writeShort(cp.getClass( dotToSlash(interfaces[i].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"); } return bout.toByteArray(); }After layers of calls, a proxy class is finally generated.
2. Who called Invoke?
We simulate JDK to generate a proxy class by itself, the class name is TestProxyGen:
public class TestGeneratorProxy { public static void main(String[] args) throws IOException { byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", Subject.class.getInterfaces()); File file = new File("/Users/yadoao/Desktop/TestProxyGen.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(classFile); fos.flush(); fos.close(); }}Decompile the class file with JD-GUI, and the result is as follows:
import com.su.dynamicProxy.ISubject;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class TestProxyGen extends Proxy implements ISubject{ private static Method m3; private static Method m1; private static Method m0; private static Method m4; private static Method m2; public TestProxyGen(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final void saysHello() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void saysGoodBye() throws { try { this.h.invoke(this, m4, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m3 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m4 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayGoodBye", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } }} First, I noticed that the constructor of the generated proxy class is passed in a class that implements the InvokeHandler interface as a parameter, and called the constructor of the parent class Proxy, which initialized the member variable protected InvokeHander h in Proxy.
I noticed several static initialization blocks again. The static initialization block here is to initialize the proxy interface list and hashcode, toString, and equals methods.
Finally, there is the calling process of these methods, all of which are callbacks to the Invoke method.
This ends with the analysis of this proxy pattern.