A brief talk on dynamic agent

Posted by techek on Fri, 21 Jan 2022 19:07:33 +0100

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

  1. Create your own calling handler by implementing the InvocationHandler interface and overriding the invoke method

  2. 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

  3. 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}

  4. 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:

  1. 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)
  2. Class<?> [] interfaces: the interface that the dynamic proxy class needs to implement (that is, the interface to be proxied, such as Teacher)
  3. 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();

Topics: Java