Explain the agent mode from simple to complex (handwritten JDK dynamic agent)

1. Brief summary

Proxy mode: refers to providing a proxy for other objects to control access to this object.

The agent mode has two main purposes:

① Protect the target object;

② Enhance the target object.

Its class diagram is as follows:

2. Classification and detailed explanation

2.1 static agent

Take chestnuts for example: the son is looking for a partner, and the parents want their children to find the other half as soon as possible, so they help their son find a partner at the same time:

interface Person{
    void findLove();
}

public class Son implements Person {
    public void findLove() {
        System.out.println("The son asked: it's a woman");
    }
}

Father help find:

public class Father {
    private Son son;

    public Father(Son son){
        this.son = son;
    }

    public void findLove(){
        System.out.println("My father helped me find it");
        this.son.findLove();
    }
}

Client class:

public class Client {
    public static void main(String[] args) {
        Father father = new Father(new Son());
        father.findLove();
    }
}

function:

Now we want our Father to help our cousin find it. At this time, the code needs to modify the Father class, which does not comply with the opening and closing principle, so we do the following optimization:

public class Father {
    private Person person;

    public Father(Person person){
        this.person = person;
    }

    public void findLove(){
        System.out.println("My father helped me find it");
        this.person.findLove();
    }
}

When Son in Father is replaced with Person, we can proxy all classes that implement the Person interface, and the cousin is also a Person, so we only need to add a cousin class. When the Client passes parameters, we can pass in new BiaoMei(). At this time, the code is closed for modification and open for extension, which conforms to the opening and closing principle.

2.2 dynamic agent

If a father wants to help not only his son find objects, but also many people find objects, that is, matchmakers, we can't create hundreds of classes like cousins, so is there a more general solution? Maybe dynamic agents can help us.

2.2.1 # JDK dynamic proxy (handwritten JDK dynamic proxy)

Create a consumer class Customer (consume at the matchmaker):

interface Person{
    void findLove();
}

public class Customer implements Person {
    public void findLove() {
        System.out.println("Just a woman");
    }
}

Create a matchmaker class (Meipo):

public class Meipo implements InvocationHandler {
    //The proxy object is saved for the convenience of this in invoke Call to target
    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Class<?> aClass = target.getClass();
        
        //Create a proxy class and return
        return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        befor();
        Object invoke = method.invoke(this.target, args);
        after();
        return invoke;
    }

    private void befor(){
        System.out.println("Matchmaker help find");
    }

    private void after(){}
}

Create Client class:

public class Client {
    public static void main(String[] args) {
        Person person = (Person) new Meipo().getInstance(new Customer());
        person.findLove();
    }
}

function:

So how is dynamic agent implemented?

In fact, it is through byte reorganization to regenerate the object to replace the original object, so as to achieve the purpose of proxy.

The basic steps of byte code reorganization are as follows:

① Get the reference of the proxy object and use reflection to get all its interfaces;

② JDK dynamic Proxy class Proxy regenerates a new class, which needs to implement all the interfaces just obtained;

③ Dynamically generate Java code of new classes;

④ Compile java files into class file;

⑤ Load compiled class file.

Then modify the Client class and check the generated Class file:

public class Test {
    public static void main(String[] args) {
        try {
            Person person = (Person) new Meipo().getInstance(new Customer());
            person.findLove();
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
            FileOutputStream fos = new FileOutputStream(new File("$Proxy0.class"));
            fos.write(bytes);
            fos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

function:

View $proxy0 Class (classes starting with $are dynamically generated):

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    ......

    public final void findLove() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    ......

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("cn.xupt.design_pattern.structure.proxy.Dynamic agent.JDK.Person").getMethod("findLove");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

This class implements the Person interface, inherits the Proxy class, and calls the h.invoke(this, m3, (Object[])null) of the parent Proxy class in findmove (). The type of this h is InvocationHandler. Then we use the object of this class to call the findlove () method.

Understand the principle of JDK, and then write its implementation:

Create Person interface:

public interface Person {
    void findLove();
}

Create Customer class:

public class Customer implements Person{
    public void findLove() {
        System.out.println("Just a woman");
    }
}

Create MyInvocationHandler interface:

public interface MyInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

Custom class loader MyClassLoader:

public class MyClassLoader extends ClassLoader {
    private File classPathFile;

    public MyClassLoader(){
        //class file path
        String classpath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classpath);
    }

    /**
     * Find the class file to load and return the class object
     * @param name : Class name
     * @return : Class object found
     * @throws ClassNotFoundException
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if(null != classPathFile){
            File classFile = new File(classPathFile, name + ".class");
            if(classFile.exists()){
                FileInputStream fis = null;
                ByteArrayOutputStream baos = null;

                try{
                    fis = new FileInputStream(classFile);
                    baos = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while((len = fis.read(buff)) != -1){
                        baos.write(buff, 0, len);
                    }
                    return defineClass(className, baos.toByteArray(), 0, baos.size());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    if(null != fis){
                        try {
                            if (null != fis) {
                                fis.close();
                            }
                            if (null != baos) {
                                baos.close();
                            }
                        }catch (IOException e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }
}

Custom Proxy class MyProxy:

package cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MyProxy {
    public static final String ln = "\r\n";

    public static Object newMyProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler handler){
        try {
            String src = generateSrc(interfaces);

            //Export Java files to disk
            String filePath = MyProxy.class.getResource("").getPath() + "$Proxy0.java";
            File file = new File(filePath);
            FileWriter fw = new FileWriter(file);
            fw.write(src);
            fw.flush();
            fw.close();

            //Compile Java files into class files
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterator = manager.getJavaFileObjects(file);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterator);
            task.call();
            manager.close();

            //Load class
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            file.delete();

            return constructor.newInstance(handler);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Construct the following classes
     *  package cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy;
     *  import cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Person;
     *  import java.long.reflect.*;
     *
     *  public class $Proxy0 implements Person;
     *      MyInvocationHandler handler;
     *      public $Proxy0(MyInvocationHandler handler) {
     *          this.handler = handler;
     *      }
     *
     *      public int createOrder(Order order){
     *          try{
     *              Method method = interfaces[0].getMethod("createOrder", new Class[]{Order.class});
     *              return ((Integer) this.handler.invoke(this, m, new Object[]{order})).intValue();
     *          }catch(Error _ex){}
     *          catch(Throwable e){
     *              throw new UndeclaredThrowableException(e);
     *          }
     *          return 0;
     *      }
 *      }
     *
     * @param interfaces
     * @return String Return to the created Java source code
     */
    private static String generateSrc(Class<?>[] interfaces){
        StringBuffer sb = new StringBuffer();
        sb.append("package cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy;" + ln);
        sb.append("import cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Person;" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
            sb.append("MyInvocationHandler handler;" + ln);
            sb.append("public $Proxy0(MyInvocationHandler handler) {" + ln);
                sb.append("this.handler = handler;");
            sb.append("}" + ln);
            for(Method m:interfaces[0].getMethods()){
                //Get the formal parameters of each method
                Class<?>[] params = m.getParameterTypes();

                StringBuffer paramNames = new StringBuffer();
                StringBuffer paramValus = new StringBuffer();
                StringBuffer paramClasses = new StringBuffer();

                for (int i = 0; i < params.length; i++) {
                    Class clazz = params[i];
                    String type = clazz.getName();
                    //Write the first letter of the class name in lowercase order
                    String paramName = toLowerFirstCase(clazz.getSimpleName());
                    //cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Order order
                    paramNames.append(type + " " + paramName);
                    paramValus.append(paramName);
                    //cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Order.class
                    paramClasses.append(clazz.getName() + ".class");

                    if (i > 0 && i < params.length - 1) {
                        paramNames.append(",");
                        paramClasses.append(",");
                        paramValus.append(",");
                    }
                }

                //Construct each method
                sb.append("public " + m.getReturnType().getName() + " " + m.getName() + " " + "(" +
                        paramNames.toString() + "){" + ln);
                    sb.append("try{" + ln);
                        sb.append("Method method = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName()
                                + "\", new Class[]{" + paramClasses.toString() + "});" + ln);
                        sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode(
                                "this.handler.invoke(this, method, new Object[]{" + paramValus + "})", m.getReturnType()) + ";");
                    sb.append("}catch(Error _ex){}");
                    sb.append("catch(Throwable e){" + ln);
                    sb.append("throw new UndeclaredThrowableException(e);" + ln);
                    sb.append("}");
                    sb.append(getReturnEmptyCode(m.getReturnType()));
                sb.append("}");
            }
            sb.append("}" + ln);
            return sb.toString();
    }

    private static Map<Class, Class> mappings = new HashMap<Class, Class>();
    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "return 0";
        }
        else if(returnClass == void.class){
            return "";
        }
        else {
            return "return null";
        }
    }

    private static String getCaseCode(String code, Class<?> returnClass){
        if(mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    /**
     * @param clazz
     * @return true Not a void type
     * @return false Is the vodid type
     */
    private static boolean hasReturnValue(Class<?> clazz){
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src){
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}

Create matchmaker class MyMeipo:

public class MyMeipo implements MyInvocationHandler {
    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Class<?> aClass = target.getClass();
        return MyProxy.newMyProxyInstance(new MyClassLoader(), aClass.getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        befor();
        Object invoke = method.invoke(this.target, args);
        after();
        return invoke;
    }

    private void befor(){
        System.out.println("I'm a matchmaker");
    }

    private void after(){
        System.out.println("eureka!");
    }
}

Create Client class:

public class Test {
    public static void main(String[] args) {
        Person person = (Person) new MyMeipo().getInstance(new Customer());
        person.findLove();
    }
}

function:

I put some problems below to avoid old friends stepping on the pit:

1. FileNotFoundException encountered?

Solution: the path cannot be in Chinese. An error will occur if it is in Chinese.

2. Encounter NoClassDefFoundError?

Solution: the jar package is not imported into tools Jar. See how to import https://blog.csdn.net/qq_39514033/article/details/103999277

3. Encountered exception in thread "main" Java lang.IllegalAccessError: class cn. xupt. design_ pattern. structure. proxy. handwriting_ dynamic_ proxy.$ Proxy0 cannot access its superinterface cn. xupt. design_ pattern. structure. proxy. handwriting_ dynamic_ proxy. Person?

Solution: interface Person should be added with public (write an interface separately).

2.2.2 # Cglib implements dynamic agent

Or to find objects for chestnuts:

Create CgMeipo class:

public class CgMeipo implements MethodInterceptor {

    public Object getInstance(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        return obj;
    }

    private void before(){
        System.out.println("I'm a matchmaker");
    }

    private void after(){

    }
}

Create the Customer class (since Cglib implements the dynamic proxy by dynamically inheriting the agent, there is no need to implement the Person interface):

public class Customer {
    public void findLove(){
        System.out.println("Just a woman");
    }
}

Create Client class:

public class Test {
    public static void main(String[] args) {
        try {
            Customer customer = (Customer) new CgMeipo().getInstance(Customer.class);
            customer.findLove();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

function:

We can add:

public class Test {
    public static void main(String[] args) {
        try {
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib_classes/");
            Customer customer = (Customer) new CgMeipo().getInstance(Customer.class);
            customer.findLove();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

To be generated class file is output to disk. After running:

It is found that a class inherits Customer, which means that this class is the proxy class we generated:

public class Customer$$EnhancerByCGLIB$$b0a90fbe extends Customer implements Factory {

    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$findLove$0$Method;
    private static final MethodProxy CGLIB$findLove$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("cn.xupt.design_pattern.structure.proxy.cglib_proxy.Customer$$EnhancerByCGLIB$$b0a90fbe");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$findLove$0$Method = ReflectUtils.findMethods(new String[]{"findLove", "()V"}, (var1 = Class.forName("cn.xupt.design_pattern.structure.proxy.cglib_proxy.Customer")).getDeclaredMethods())[0];
        CGLIB$findLove$0$Proxy = MethodProxy.create(var1, var0, "()V", "findLove", "CGLIB$findLove$0");
    }

    final void CGLIB$findLove$0() {
        super.findLove();
    }

    public final void findLove() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);
        } else {
            super.findLove();
        }
    }

    ......

It can be found that this class overrides all the methods of the parent class Customer, and each method has a MethodProxy corresponding to it, such as CGLIB$clone$Proxy of findLove(), which are called in the findLove() method, such as:

var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);

At this point, you can analyze the calling process of cglib:

The proxy object calls this findLove() --- > call interceptor (execute before and after) -- > methodproxy Invokesuper() method -- > cglib $findlove $0 -- > findLove() method of the agent

How does methodProxy obtain proxy methods through invokeSuper? Click on invokeSuper():

    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

    ......

    private static class FastClassInfo {
        FastClass f1;
        FastClass f2;
        int i1;
        int i2;

        private FastClassInfo() {
        }
    }

We found that invokeSuper() called FCI f2. Invoke (fci.f2, obj, args), click invoke to enter the FastClass class class:

public abstract class FastClass {

    ......

    public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;

Is an abstract class. Remember the three generated earlier Class file? In fact, they are FastClass generated for proxy class and proxy class respectively.

Click one:

public class Customer$$EnhancerByCGLIB$$b0a90fbe$$FastClassByCGLIB$$6a538f19 extends FastClass {

    ......

It is found that it inherits FastClass and implements invoke:

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        b0a90fbe var10000 = (b0a90fbe)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                return new Boolean(var10000.equals(var3[0]));
            case 1:
                return var10000.toString();
            case 2:
                return new Integer(var10000.hashCode());
            case 3:
                return var10000.newInstance((Callback)var3[0]);
            case 4:
                return var10000.newInstance((Callback[])var3[0]);
            case 5:
                return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
            case 6:
                var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
                return null;
            case 7:
                var10000.findLove();
                return null;
            case 8:
        
        ......

It can be seen that the method is distinguished through the first parameter var1. This parameter is actually generated by the FastClass mechanism. It generates a class for the proxy class and the proxy class respectively. This class will assign an index (int) to the methods of the proxy class or the proxy class. This index is used as an input parameter, and FastClass can directly locate the method without using reflection, so the efficiency will be higher. This index is generated in getIndex() (getIndex is in the two generated classes, and the abstract method in FastClass):

    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -2055565910:
            if (var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
                return 11;
            }
            break;
        case -1725733088:
            if (var10000.equals("getClass()Ljava/lang/Class;")) {
                return 24;
            }
            break;
        case -1457535688:
            if (var10000.equals("CGLIB$STATICHOOK1()V")) {
                return 20;
            }
            break;
        ......

FastClass is not generated with the proxy class, but is generated and put into the cache when invokeSuper is executed for the first time.

 

Thanks for watching 0.0

Tags: Design Pattern

Posted by janet287 on Fri, 06 May 2022 23:45:22 +0300