A brief talk on dynamic agent
What is the agent model?
Provide a proxy for other objects to control access to an object.
Proxy mode is a commonly used java design mode. Its feature is that the proxy class has the same interface with the delegate class. The proxy class is mainly responsible for preprocessing messages for the delegate class, filtering messages, forwarding messages to the delegate class, and post-processing messages.
There is usually an association relationship between proxy class and delegate class. The object of a proxy class is associated with the object of a delegate class. The object of the proxy class itself does not really realize the service, but provides a specific service by calling the relevant methods of the object of the delegate class.
In fact, the agent class preprocesses messages for the proxy class, filters messages, forwards messages to the proxy class, and then carries out post-processing of messages
There is usually an association between the proxy class and the proxied class (that is, the references held from the object mentioned above). The proxy class itself does not implement services, but provides services by calling methods in the proxied class.
In short, when we access the actual object, we access it through the proxy object. The proxy mode introduces a certain degree of indirectness when accessing the actual object. Because of this indirectness, we can add a variety of purposes, such as logging, etc
What are the agent models
About the dynamic proxy in Java, the first thing we need to know is a common design pattern - proxy pattern
The agent can be divided into static agent and dynamic agent according to the time point when the agent class is created
- Static proxy: it is created by programmers or automatically generated by specific tools, that is, the interface, proxy class and proxy class have been determined at compile time. The name of the proxy class before the program runs The class file has been generated.
- Dynamic proxy: the proxy class is created only when the program is running. Proxy classes are not defined in Java code, but are dynamically generated at runtime according to our "instructions" in Java code
Implementation of each agent mode
Front scenario description
Let's take the scene in life as an example
Suppose there is a Teacher interface, Teacher Zhang and Teacher Wang
public interface Teacher { void speak(); void rest(); void standup(String studentName); } public class Wang implements Teacher { @Override public void speak() {System.out.println("Mr. Wang speaks...");} @Override public void rest() {System.out.println("Mr. Wang has a rest...");} @Override public void standup(String studentName) { System.out.println("Miss Wang rang"+studentName+"Students stand up"); } } public class Zhang implements Teacher { @Override public void speak() {System.out.println("Mr. Zhang speaks...");} @Override public void rest() {System.out.println("Miss Zhang has a rest...");} @Override public void standup(String studentName) { System.out.println("Teacher Zhang rang"+studentName+"Students stand up"); } } Copy code
We usually let it execute by using the implementation class of the interface, and then instantiate it for execution
System.out.println("----------Interface implementation----------"); Teacher zhang = new Zhang(); zhang.speak(); zhang.rest(); zhang.standup("Xiao Zhang"); Wang wang = new Wang(); wang.speak(); wang.rest(); wang.standup("Xiao Wang"); Copy code
This makes it very convenient to execute the method
But this will also cause some problems
Question raised
What should I do if I want the method to be logged before and after execution?
As the saying goes, adding one layer can solve 90% of the problems. If it still can't be solved, add another layer!
It is not advisable to perform a record method before and after each method specific code
Because there are 6 methods in two implementation classes, 12 duplicate codes need to be written
If there are 10 classes with 100 methods, don't you need to write 100 duplicate codes, which is obviously not easy to maintain
Static proxy
We add a proxy class for both zhang and wang
Take ZhangProxy as an example
In this way, the problem of logging before and after can be solved, but the problem of repeated code still exists: solve it with generics!
public class ZhangProxy implements Teacher { private Zhang zhang; public ZhangProxy(Zhang zhang) { this.zhang = zhang; } @Override public void speak() { System.out.println("before speak"); zhang.speak(); System.out.println("after speak"); } @Override public void rest() { System.out.println("before rest"); zhang.rest(); System.out.println("after rest"); } @Override public void standup(String studentName) { System.out.println("before standup"); zhang.standup(studentName); System.out.println("after standup"); } } System.out.println("----------Static proxy----------"); ZhangProxy zhangProxy = new ZhangProxy(new Zhang()); zhangProxy.speak(); zhangProxy.rest(); zhangProxy.standup("Xiao Jing"); WangProxy wangProxy = new WangProxy(new Wang()); wangProxy.speak(); wangProxy.rest(); wangProxy.standup("Xiao Wang"); Copy code
Generic static proxy
In this way, there is no need to write multiple proxy classes and the method of logging before and after
A lot of duplicate code is reduced
public class AllProxy<T> implements Teacher { private T t; private Method[] methods; private Map<String,Method> temp; public AllProxy(T t) { this.t = t; this.methods = t.getClass().getMethods(); temp=new HashMap<>(); for (Method method : methods) { temp.put(method.getName(),method); } } @Override public void speak() { System.out.println("before speak"); try{ temp.get("speak").invoke(t); }catch(Exception e){ e.printStackTrace(); } System.out.println("after speak"); } @Override public void rest() { System.out.println("before rest"); try{ temp.get("rest").invoke(t); }catch(Exception e){ e.printStackTrace(); } System.out.println("after rest"); } @Override public void standup(String studentName) { System.out.println("before standup"); try{ temp.get("standup").invoke(t,studentName); }catch(Exception e){ e.printStackTrace(); } System.out.println("after standup"); } } System.out.println("----------Generic static proxy----------"); AllProxy<Wang> wangAllProxy = new AllProxy<>(new Wang()); wangAllProxy.speak(); wangAllProxy.rest(); wangAllProxy.standup("Xiao Wang"); AllProxy<Zhang> zhangAllProxy = new AllProxy<>(new Zhang()); zhangAllProxy.speak(); zhangAllProxy.rest(); zhangAllProxy.standup("Xiao Zhang"); Copy code
New problems
In this way, the problem of repeatedly writing proxy classes is solved
However, after multiple interfaces are implemented, there will be more and more rewriting methods, which do not meet the opening and closing principle: the program is closed to modification and open to expansion
Then we should use dynamic proxy to solve this problem
Dynamic agent
Specific steps
-
Create your own calling handler by implementing the InvocationHandler interface and overriding the invoke method
-
Create a dynamic Proxy Class instance by specifying the ClassLoader object of the Class loader, a group of Class arrays of the proxied interface and the instantiation object of the calling processor for the newProxyInstance method of the Proxy Class
-
Get the constructor var5 of the dynamic proxy class through the reflection mechanism Getconstructor (constructorparams), whose only parameter type is the calling processor interface type constructorParams = new Class[]{InvocationHandler.class}
-
Create a dynamic proxy class instance through the constructor. When constructing, the calling processor object is passed into VAR6 as a parameter newInstance(var2)
Create proxy class
Create a dynamic proxy class DynamicProxy, implement the InvocationHandler interface, and override the invoke method
Object res = method.invoke(obj, objects); Execute the method through reflection, where obj is the instantiated object of the proxy class, that is, the instantiated object of Zhang or Wang above
All the methods executed by the proxy class are replaced by the invoke method, which reduces the duplicate code by 99%
public class DynamicProxy<T> implements InvocationHandler { private T obj; public DynamicProxy(T obj) { this.obj = obj; } @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("before "+method.getName()); Object res = method.invoke(obj, objects); System.out.println("after "+method.getName()); return res; } } Copy code
Proxy.newProxyInstance
Proxy. The newproxyinstance method has three parameters:
- Classloader: which class loader is used to load the proxy object (later, use the Factory class to instantiate the proxy, that is, the Factory class is used as the loader)
- Class<?> [] interfaces: the interface that the dynamic proxy class needs to implement (that is, the interface to be proxied, such as Teacher)
- InvocationHandler proxy: the implementation class of InvocationHandler, that is, the proxy class
//Instantiate the object of the proxied class Zhang zhang = new Zhang(); //Pass the object of the proxy class to the object of the proxy class DynamicProxy<Zhang> zhangDynamicProxy = new DynamicProxy<>(zhang); //Instantiate the proxied interface class (must be declared as the parent interface type in polymorphic form) Teacher teacher = (Teacher)Proxy.newProxyInstance( Main.class.getClassLoader(), test.getClass().getInterfaces(), zhangDynamicProxy); //Execution method teacher.standup("Xiaodai"); Copy code
before standup Mr. Zhang asked Xiao Dai to stand up after standup
Dynamic agent factory
It's a bit troublesome to instantiate the proxy class above. What if we use an agent factory to instantiate the proxy in the form of generics?
classLoader uses the of factory itself: this getClass(). getClassLoader()
Class<?> [] interfaces use generic T: t.getclass() getInterfaces()
InvocationHandler still uses our custom proxy class template: new DynamicProxy(t). Don't forget to pass in the generic object
public class ProxyFactory { public <T> T getInstance(T t) { //Class loader, just use the factory's own ClassLoader classLoader = this.getClass().getClassLoader(); //Array of proxied interfaces Class<?>[] interfaces = t.getClass().getInterfaces(); //Dynamic proxy class template DynamicProxy proxy = new DynamicProxy(t); //Instantiate and return the proxy object return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy); } } Copy code
Then when we instantiate the proxy, we only need one sentence
Teacher mrZhang = new ProxyFactory().getInstance(new Zhang()); Copy code
In fact, this proxy factory class can not only instantiate Teacher's proxy, but other classes can also proxy
Because generics are used without specifying specific types
So if we create a new Student interface and interface implementation class Chen
It can also be executed by proxy, meeting the opening and closing principle: there is no need to modify any code, just pass in the proxy object when calling
//Factory mode Teacher mrZhang = new ProxyFactory().getInstance(new Zhang()); mrZhang.speak(); mrZhang.rest(); mrZhang.standup("Xiao Zhang"); Teacher mrWang = new ProxyFactory().getInstance(new Wang()); mrWang.speak(); mrWang.rest(); mrWang.standup("Xiao Wang"); //You can proxy not only the Teacher interface and its implementation classes, but also other interfaces Student chen = new ProxyFactory().getInstance(new Chen()); chen.discuss();