proxy pattern
Proxy pattern is a structural design pattern that allows you to provide alternatives to objects or placeholders for them. The agent controls access to the original object and allows some processing before and after the request is submitted to the object.
Agent mode structure
- The service interface declares the functions provided by the service interface. The proxy must follow this interface to masquerade as an object
- Service class, which provides some practical business logic
- The proxy class contains a reference member variable that points to the service object. After the proxy completes its assigned tasks (such as delayed loading, logging, access control or caching), it will pass the request to the service object. Generally, the proxy will manage the whole declaration cycle of its service object.
- The client can interact with the service or proxy through the same interface, so you can use the proxy in some code that needs the service object.
case analysis
We have a common database access interface. A large number of clients directly access the database, which consumes a lot of system resources, and there are a lot of repeated query operations.
Accessing the database directly may be very slow
At this time, we consider adding the cache. When repeated queries are needed, we can directly get the data from the cache and return it to the client to save system overhead, and record the time spent on each client access.
In proxy mode, it is recommended to create a new proxy class with the same interface as the original service object, and then update the application to pass the proxy object to all original object clients. After receiving the client request, the proxy class will create the actual service object and delegate all work to it.
The agent disguises itself as a database object, which can do cache query operation without the client's knowledge, and record its access time or log
code implementation
Define the interface to query the database
public interface DataService { // Query data by ID String getById(Integer id); }
Specific database query business class
public class DataServiceImpl implements DataService{ // Analog data final Map<Integer,String> dataMap = new HashMap<Integer,String>(){{ for (int i = 0; i < 10; i++) { put(i,"data_"+ i); } }}; @Override public String getById(Integer id) { // Time consuming for simulating database query try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } return dataMap.get(id); } }
Create agent class and disguise business class
public class DataServiceProxy implements DataService{ DataService dataService; // cache Map<Integer,String> cacheMap = new HashMap<>(); public DataServiceProxy(DataService dataService) { this.dataService = dataService; } @Override public String getById(Integer id) { // Record the start time of the visit final long start = System.currentTimeMillis(); String result = null; // Get priority from cache String cache = getCache(id); if (cache == null){ result = dataService.getById(id); // Put in cache putCache(id,result); }else { result = cache; } final long end = System.currentTimeMillis(); System.out.println("Time consuming:" + (end - start) + "ms"); return result; } // Cache information private void putCache(Integer id,String value){ cacheMap.put(id,value); } // Get cache information private String getCache(Integer id){ return cacheMap.get(id); } }
client
@Test public void ProxyTest() { DataService dataService = new DataServiceImpl(); DataServiceProxy dataServiceProxy = new DataServiceProxy(dataService); dataServiceProxy.getById(1); // Second query dataServiceProxy.getById(1); dataServiceProxy.getById(1); }
The design method of this agent mode is generally called static agent: the source code is created by the coder or generated by a specific tool. The interface, proxy class and proxy class have been determined during compilation. Before the program runs, the bytecode file of the proxy class has been generated. If you use other proxy content, you may need to create a lot of new code to implement it.
Dynamic agent
The biggest difference from static agent is that dynamic agent class is an agent created when the program is running. For example, in the above example, the DataServiceProxy proxy class is defined by ourselves and has been compiled before the program runs. In the dynamic proxy, the proxy class is not defined in the code, but dynamically generated in the Java code according to our needs when the program runs.
In Java, we mention dynamic agent, which is generally inseparable from JDK dynamic agent and CGLIB dynamic agent.
JDK dynamic agent
Using JDK's own proxy class to complete, it is equivalent to using an interceptor (to implement interface InvocationHanlder) to match with the reflection mechanism to generate an anonymous interface to implement the proxy class, and call InvocationHanlder before calling the specific method.
We still use the DataService interface and DataServiceImpl business class to complete a dynamic proxy case.
- Create the interface and business class of the proxy class (already available)
- Create the implementation class of InvocationHanlder interface and implement the logic of the agent in the invoke method
- Create a Proxy object through the Proxy's static method newproxyinstance (classloader, loader, class [] interfaces, invocationhandler h).
public class JDKProxy implements InvocationHandler { // Proxied object private Object object; // cache Map<Integer,String> cacheMap = new HashMap<>(); public JDKProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Proxy only the query methods if (method.getName().equals("getById")){ // parameter Integer id = (Integer) args[0]; // Record the start time of the visit final long start = System.currentTimeMillis(); String result = null; // Get priority from cache String cache = getCache(id); if (cache == null){ // Agent execution result =(String) method.invoke(object,args); // Put in cache putCache(id,result); }else { result = cache; } final long end = System.currentTimeMillis(); System.out.println("Time consuming:" + (end - start) + "ms"); return result; }else { return method.invoke(object,args); } } // Cache information private void putCache(Integer id,String value){ cacheMap.put(id,value); } // Get cache information private String getCache(Integer id){ return cacheMap.get(id); } }
InvocationHandler interface details
There is an instance of the proxy handler that invokes the proxy interface; When a proxy instance invokes a method, the method call is encoded and dispatched to the invoke method of the calling handler.
The calling handler of each dynamic proxy class must implement the InvocationHandler interface, and the instance of each proxy class is associated with the dynamic proxy class calling handler that implements the interface. When we call a method through the dynamic proxy object, the call of this method will be forwarded to the invoke method that implements the InvocationHandler interface class, See the following invoke method:
/** * proxy:Real proxy object of proxy class proxy com sun. proxy.$ Proxy0 (in order, each generated + 1) * method:The Method object of the real Method of an object we want to call * args:Refers to the parameters passed by the proxy object method */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Client
The method of the client when calling is also different from that of the static Proxy. Finally, the Proxy class $Proxy is used to call the method
@Test public void JDKProxyTest() { DataService dataService = new DataServiceImpl(); JDKProxy jdkProxy = new JDKProxy(dataService); // Get proxy object DataService dataServiceProxy = (DataService) Proxy.newProxyInstance(DataService.class.getClassLoader(), new Class[]{DataService.class}, jdkProxy); dataServiceProxy.getById(1); dataServiceProxy.getById(1); }
The result of its operation is the same, and the agent content is completed.
Proxy class details
Proxy class is a class used to create a proxy object. It provides many methods. The most commonly used method is newProxyInstance.
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
newProxyInstance is to create a proxy class object, which receives three parameters:
- Loader: Specifies the class loader of the proxy class (we pass in the class loader of the current test class)
- interfaces: an array of interface objects, the interface that the proxy class needs to implement (we pass in the interface implemented by the proxy class, so that the generated proxy class and the proxy class implement the same interface)
- h: An InvocationHandler object indicates which InvocationHandler object will be associated with when the dynamic proxy object calls the method to handle the method call. Here we pass in our own handler
CGLIB dynamic proxy
Using asm open source package, load the class file of proxy object class, and modify its bytecode to generate subclasses for processing.
- Import cglib XXX Jar package, which contains asm and cglib
- Create the implementation class of MethodInterceptor interface and implement the logic of agent in intercept method
- Write the getCglibProxy method (custom) to return the proxy class object
Pom import cglb
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
Override MethodInterceptor
public class CglibProxy implements MethodInterceptor { // The proxy Object is convenient for general use and can be written as Object private Object object; // cache Map<Integer,String> cacheMap = new HashMap<>(); @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // Proxy only the query methods if (method.getName().equals("getById")) { // parameter Integer id = (Integer) args[0]; // Record the start time of the visit final long start = System.currentTimeMillis(); String result = null; // Get priority from cache String cache = getCache(id); if (cache == null) { result = (String)method.invoke(object,args); // Put in cache putCache(id, result); } else { result = cache; } final long end = System.currentTimeMillis(); System.out.println("Time consuming:" + (end - start) + "ms"); return result; } else { return method.invoke(object, args); } } // Get the proxy object, which adopts the writing method of paradigm, more intuitively passes in the proxy class, and then returns the proxy object public <T> T getCglibProxy(T t){ this.object = t;//Assign a value to the target object target Enhancer enhancer = new Enhancer(); //Set the parent class. Because Cglib generates a subclass for the specified class, you need to specify the parent class enhancer.setSuperclass(object.getClass()); //Set callback enhancer.setCallback(this); //Create and return proxy objects Object result = enhancer.create(); return (T) result; } // Cache information private void putCache(Integer id,String value){ cacheMap.put(id,value); } // Get cache information private String getCache(Integer id){ return cacheMap.get(id); } }
Client
@Test public void CGLBProxyTest(){ // The proxy class does not need interface declaration here DataService dataService = new DataServiceImpl(); CglibProxy cglibProxy = new CglibProxy(); // Get proxy object DataService proxy = cglibProxy.getCglibProxy(dataService); proxy.getById(1); proxy.getById(1); }
It can be found that the writing methods of the two dynamic agents are basically the same. The basic idea is to generate agent classes, intercept, reflect, obtain real agent class methods and execute. So what's the difference between the two ways?
Differences between JDK agent and CGLIB agent
- JDK dynamic agent can only generate agents for classes that implement interfaces, not for classes. It is implemented by Java reflection technology, and the process of generating classes is relatively efficient.
- CGLIB is a proxy for class implementation. It mainly generates a subclass of a specified class and covers its methods. It is implemented using asm bytecode framework. The related execution process is more efficient. The process of generating a class can be compensated by cache. Because it is inheritance, it is better not to declare this class or method as final
- JDK agent does not need the support of a third-party library. It can be used only in the JDK environment
- CGLIB must rely on CGLIB's class library, but it needs classes to implement any interface proxy. What it needs is to generate a subclass from the specified class and override the methods in it. It is an inherited proxy, but it is recommended to use JDK in the environment of interface programming;