Reference blog:
https://www.cnblogs.com/baizhanshi/p/6611164.html https://mp.weixin.qq.com/s/34LAnTGhqe7DTYmT1PRQJg
Proxy pattern is a kind of design pattern, which mainly solves the problems caused by directly accessing objects
It is possible that the functions of the proxy object can be realized, but sometimes we will consider enhancing it to solve it; But sometimes we don't need to enhance it.
In essence, we need to implement it according to our own needs to adopt special treatment.

In order to maintain the consistency of behavior, proxy class and delegate class usually implement the same interface, so there is no difference between them in the eyes of visitors. Through the middle layer of proxy class, it can effectively control the direct access to the delegate object, hide and protect the delegate object, and reserve space for the implementation of different control strategies, so as to obtain greater flexibility in design.
Examples of usage:
If we need to delegate a class to handle a business, we can handle it uniformly in the proxy class first, and then call the specific implementation class
According to the creation period of the agent, the agent class can be divided into two types:
Static: programmers create proxy classes or specific tools to automatically generate source code and then compile it. The of the proxy class before the program runs The class file already exists.
Dynamic: it is created dynamically by using reflection mechanism when the program is running.
1. Static proxy:
Take a direct look at a corresponding example to illustrate:
public interface UserManager { void addUser(String userId, String userName); void delUser(String userId); String findUser(String userId); void modifyUser(String userId, String userName); }
Create one of the implementation classes to implement it:
public class UserManagerImpl implements UserManager { @Override public void addUser(String userId, String userName) { System.out.println("UserManagerImpl.addUser"); } @Override public void delUser(String userId) { System.out.println("UserManagerImpl.delUser"); } @Override public String findUser(String userId) { System.out.println("UserManagerImpl.findUser"); return "Zhang San"; } @Override public void modifyUser(String userId, String userName) { System.out.println("UserManagerImpl.modifyUser"); } }
Create a static proxy class:
public class UserManagerImplProxy implements UserManager { // Target object private UserManager userManager; // Pass in the target object through the construction method public UserManagerImplProxy(UserManager userManager){ this.userManager=userManager; } @Override public void addUser(String userId, String userName) { try{ //Add the function of printing log //Start adding users System.out.println("start-->addUser()"); userManager.addUser(userId, userName); //User added successfully System.out.println("success-->addUser()"); }catch(Exception e){ //Failed to add user System.out.println("error-->addUser()"); } } @Override public void delUser(String userId) { userManager.delUser(userId); } @Override public String findUser(String userId) { userManager.findUser(userId); return "Zhang San"; } @Override public void modifyUser(String userId, String userName) { userManager.modifyUser(userId,userName); } }
Test:
public class TestStaticProxy { public static void main(String[] args) { UserManagerImpl userManager = new UserManagerImpl(); UserManagerImplProxy userManagerImplProxy = new UserManagerImplProxy(userManager); userManagerImplProxy.addUser("111","lig"); } }
Advantages and disadvantages of static proxy class
advantage:
The proxy makes the client do not need to know what the implementation class is and how to do it. The client only needs to know the proxy (decoupling). For the above client code, newUserManagerImpl() can be hidden by the application factory. The above is just an example.
Disadvantages:
1) The proxy class and the delegate class implement the same interface, and the proxy class implements the same method through the delegate class. This leads to a lot of code duplication. If a method is added to the interface, all proxy classes need to implement this method in addition to all implementation classes. It increases the complexity of code maintenance.
2) Proxy objects only serve one type of objects. If you want to serve multiple types of objects. It is bound to proxy for every object, and static proxy is not competent when the program scale is a little large. The above code only provides a proxy for the access of UserManager class, but if we need to provide a proxy for other classes such as Department class, we need to add the proxy class of Department again.
Examples show that agents can manage the implementation classes uniformly, for example, before calling specific implementation classes, we need to print logs and other information, so we only need to add a proxy class, add the function of print log in the proxy class, and then call the implementation class, thus avoiding modifying the concrete implementation class. Meet what we call the opening and closing principle. However, if you want each implementation class to add the function of printing log, you need to add multiple agent classes, and each method in the agent class needs to add the function of printing log (in the agent method above, the function of printing log needs to be added for deletion, modification and query)
That is, static proxy classes can only serve specific interfaces (services). If you want to serve multiple interfaces, you need to establish many proxy classes.
The above two shortcomings are very fatal, so these two proxy classes will not be used for operation in development
2. Dynamic proxy:
According to the above introduction, you will find that each proxy class can only serve one interface, so there will be many proxy classes in program development
So we will find a way to complete all the proxy functions through a proxy class, so we need to use dynamic proxy
In the above example, an agent can only represent one type, and the object to be represented has been determined by the compiler. Dynamic agent is to realize dynamic agent through reflection mechanism at runtime, and can represent various types of objects
stay Java In order to implement the dynamic proxy mechanism, Java lang.reflect. Invocationhandler interface and Java lang.reflect. Proxy class support
java. lang.reflect. The invocationhandler interface is defined as follows:
//Object proxy: the proxy object //Method: method to call //Object[] args: parameters required for method call public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
java. lang.reflect. The proxy class is defined as follows:
//Classloader: loader of class //Class<?> Interfaces: get all interfaces //InvocationHandler h: get the instance of subclass of InvocationHandler interface public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
The corresponding proxy examples are as follows:
Interface class:
public interface UserManager { void addUser(String userId, String userName); void delUser(String userId); String findUser(String userId); void modifyUser(String userId, String userName); }
Implementation class:
public class UserManagerImpl implements UserManager { @Override public void addUser(String userId, String userName) { System.out.println("UserManagerImpl.addUser"); } @Override public void delUser(String userId) { System.out.println("UserManagerImpl.delUser"); } @Override public String findUser(String userId) { System.out.println("UserManagerImpl.findUser"); return "Zhang San"; } @Override public void modifyUser(String userId, String userName) { System.out.println("UserManagerImpl.modifyUser"); } }
Corresponding proxy class:
//Dynamic proxy classes can only proxy interfaces (abstract classes are not supported). Proxy classes need to implement InvocationHandler class and invoke method. // The invoke method needs to be called when calling all methods of the proxy interface. The value returned by the invoke method is an implementation class of the proxy interface public class LogHandlerProxy implements InvocationHandler { // Target object private Object targetObject; // Binding relationship, that is, when the methods associated with which interface (bound to the specific implementation class) will be called, execute the invoke method. public UserManager bind(Object targetObject){ this.targetObject=targetObject; // This method is used to generate a dynamic proxy class instance for the specified class loader, a set of interfaces and the calling processor // The first parameter specifies the class loader that generates the proxy object, which needs to be specified as the same class loader as the target object // The second parameter needs to implement the same interface as the target object, so you only need to get the implementation interface of the target object // The third parameter indicates which InvocationHandler's invoke method needs to be executed when these intercepted methods are intercepted // Returns a proxy object based on the incoming destination return (UserManager) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),this); } @Override //The method of the associated implementation class will be executed when called /*InvocationHandler Interface method. Proxy represents proxy, method represents the method called by the original object, and args represents the parameters of the method*/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("start-->>"); for(int i=0;i<args.length;i++){ System.out.println(args[i]); } Object ret=null; try{ /*Processing log information before calling the original object method*/ System.out.println("satrt-->>"); //Call target method ret=method.invoke(targetObject, args); /*Processing log information after original object method call*/ System.out.println("success-->>"); }catch(Exception e){ e.printStackTrace(); System.out.println("error-->>"); throw e; } return ret; } }
Test class:
public class UserManagerTest { public static void main(String[] args) { LogHandlerProxy logHandlerProxy = new LogHandlerProxy(); UserManager userManager = logHandlerProxy.bind(new UserManagerImpl()); userManager.addUser("111","lig"); System.out.println("====================="); userManager.delUser("666"); } }
There are three objects:
- UserManagerImpl object, which we call the proxied object
- LogHandlerProxy object, which we call performer object
- Proxy objects (objects generated by using Proxy.newProxyInstance in the ProviderHandler bind method) are called proxy objects
What is the relationship between these three objects?
Proxy is the real proxy class, UserManagerImpl is the proxied class, and LogHandlerProxy is the executor of method enhancement.
We are to enhance the addUser method of UserManagerImpl (Proxy object) to Proxy the execution of the Proxy object. The Proxy does not do this in person, but gives the executor object ProviderHandler to implement the added directory and perform the flow limit verification before the call.
How did it actually happen?
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //Empty the Invocationhandler Objects.requireNonNull(h); //Copy [IProvider interface] final Class<?>[] intfs = interfaces.clone(); //The Proxy class is generated according to the IProvider class loader IProvider interface. The key is to generate a class object in the JVM cache according to the class loader and interface object Class<?> cl = getProxyClass0(loader, intfs); //Get constructor final Constructor<?> cons = cl.getConstructor(constructorParams); //Save a reference to InvocationHandler final InvocationHandler ih = h; //Instantiate the Proxy proxy object through the constructor return cons.newInstance(new Object[]{h}); }
3. How does the generated Proxy object call the invoke function of the executor
This place outputs the class bytecode of Proxy0 to a file through this code.
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", WeiboProvider.class.getInterfaces()); String path = "C:**/IdeaProjects/study/out/production/study/SimpleProxy.class"; try(FileOutputStream fos = new FileOutputStream(path)) { fos.write(classFile); fos.flush(); System.out.println("proxy class class File written successfully"); } catch (Exception e) { System.out.println("Write file error"); }
Decompile Proxy0 as follows:
//Proxy0 is a dynamically generated class, which inherits from proxy and implements the IProvider interface public final class $Proxy0 extends Proxy implements IProvider { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String getData(String var1) throws { try { // Here comes the most essential place //m3 is the getData method of IProvider interface //super.h is the parent class java lang.reflect. Property InvocationHandler of proxy return (String)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); //m3 is the getData method of IProvider interface m3 = Class.forName("aop.IProvider").getMethod("getData", new Class[]{Class.forName("java.lang.String")}); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
Focus on return (string) super h.invoke(this, m3, new Object[]{var1}); code.
$Proxy0 inherits the Proxy class and implements the IProvider interface, so there is also a getData() function. The getData function calls the invoke Method of the executor InvocationHandler, and m3 is the Method object obtained through reflection. Therefore, it depends on the way that getData calls invoke. Three parameters, the first is the Proxy object, the second is the getData Method object, and the third is the parameter.
To sum up:
- The essence of dynamic Proxy is to generate a class that inherits from Proxy and implements the Proxy interface (IProvider) - Proxy0.
- Proxy0 holds the InvocationHandler instance, and InvocationHandler holds the SimpleProvider instance. When proxy0 calls the interface getData method, it is first passed to InvocationHandler. When passing, it calls the invoke() method, and then InvocationHandler is passed to the SimpleProvider instance to reflect and call the methods therein.
Dynamic proxy actually helps us directly regenerate the proxy class and corresponding class objects in the JVM memory, and then call the proxy object SimpleProvider through the executor InvocationHandler.
This is why we often see that when a proxy object calls a method, it first calls the Invoke() method of the proxy object, and then calls the method of the target object.