Popular Understanding of Java Dynamic Agent

Introduction of Agent Model

Agent pattern is a common design pattern. Its function is to provide additional access to the target object. Without modifying the target object, it expands the additional functions of the target object, such as statistical execution time, printing logs and so on.
There are two kinds of proxy modes: static proxy and dynamic proxy.
Requirement: If you do not want to change the original code, and record the execution time of the user save method. The sample code is as follows:

Interface

public interface UserService {
    public void saveUser(User user);
}

Implementation class

public class UserServiceImpl implements UserService {

    private UserDao userDaoImpl;

    @Override
    public void saveUser(User user) {
        userDaoImpl.save(user); 
    }
    ...
}

Static proxy implementation

Static proxies are generated before a program runs. Generally speaking, proxy classes and target classes implement the same interface or have the same parent class.

proxy class

public class UserServiceProxyImpl implements UserService {

    private UserService UserServiceImpl;//The proxy class holds an object reference to a delegate class

    public UserServiceProxyImpl(UserService UserServiceImpl) {
        this.UserServiceImpl = UserServiceImpl;
    }

    @Override
    public void saveUser(User user) {
        long startTime = System.currentTimeMillis();
        System.out.println("Start recording time");
        delegate.dealTask(taskName);    // Assigning requests to delegate class processing
        long endTime = System.currentTimeMillis();
        System.out.println("The method of saving user information is time-consuming" + (endTime-startTime) + "Millisecond");
    }
}

Static factory class that generates proxy objects

public class UserServiceProxyFactory {
    public static UserService getInstance() {
        return new UserServiceProxyImpl(new UserServiceImpl());
    }
}

Client

public class ClientTest {
    public static void main(String[] args) {
        UserService UserServiceProxy = UserServiceProxyFactory.getInstance();
        UserServiceProxy.saveUser(new User("1","Zhang San"));
    }
}

Operation results

Start recording time
 The method of saving user information takes 0.01 milliseconds

Advantages and disadvantages of static proxy:
Advantage:
1. The business class UserService Impl only needs to pay attention to the business logic itself to ensure the reusability of the business class.
2. There is no direct dependency between Client client and business class UserService Impl, which shields the concrete implementation for customers.
Disadvantages:
1. An interface of a proxy object serves only one type of object. Static proxy can not be used when the program is a little larger.
2. If a method is added to the interface, all proxy classes need to implement this method in addition to all implementation classes. Increased complexity of code maintenance

Dynamic Agent Implementation

Dynamic proxy is a proxy object generated dynamically in the JVM during the running process of a program. The proxy class can realize proxy of many kinds.
Dynamic proxy can be divided into two kinds: JDK dynamic proxy and CGLIB dynamic proxy.

JDK Dynamic Agent

JDK dynamic proxy needs to declare an intermediate class between the proxy class and the target class, which needs to implement an interface InvocationHandler in jdk. The source code is as follows:

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

Generating intermediate classes of proxy objects

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxy implements InvocationHandler {

    private Object target; //References holding the target object
    public JDKProxy(Object target){
        this.target = target;
    }

    //Creating proxy objects
    public Object createProxy(){
         //1. Get the classloader of the target object
        ClassLoader classLoader = target.getClass().getClassLoader();
         //2. Get the class of the implementation interface of the target object []
        Class<?>[] interfaces = target.getClass().getInterfaces();

        //3. The third parameter requires an object that implements the InvocationHandler interface
        //3-1. The first way to write, let the current class implement InvocationHandler, and the third parameter write this
        return Proxy.newProxyInstance(classLoader, interfaces, this);

        //3-2. The second way of writing, the third parameter in the form of anonymous inner classes, is commented out first.
        /*return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.currentTimeMillis();
                System.out.println("Start recording time ";
                Object ret = method.invoke(target, args);   //Execute the target object method, where target is written. If proxy is written, it will loop until memory overflows.
                long endTime = System.currentTimeMillis();
                System.out.println("The method of saving user information takes "+" (endTime-startTime) +"milliseconds".
                return ret;
            }
        });*/
    }
    /* 
    Method of executing target objects on proxy instances 
    Parametric 1 is the proxy object, which is not normally used.
    Parameter 2 The Method object of the method it calls
    Parameters of the method called by parameter 3
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("Start recording time");
        Object ret = method.invoke(target, args);//Execute the target object method, where target is written. If proxy is written, it will loop until memory overflows.
        long endTime = System.currentTimeMillis();
        System.out.println("The method of saving user information is time-consuming" + (endTime-startTime) + "Millisecond");
        return ret;
    }

}

Client

public class ProxyTest {
    @Test
    public void test1() {
        UserService userService = new UserServiceImpl();    //1. Create target objects
        JDKProxy factory = new JDKProxy(userService);   // 2. Creating proxy objects through JKDProxy
        UserService userServiceProxy = (UserService)factory.createProxy();
        userServiceProxy.saveUser(new User("1","Zhang San"));
    }
}

Two points of concern in JDK dynamic proxy are as follows:
1. Proxy. new Proxy Instance (classLoader, interfaces, this); how do the underlying proxy objects be created
2. When does the invoke method execute and who calls it?

How does <font color=red> parse 1> </font> generate proxy objects?

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    throws IllegalArgumentException
    {
    if (h == null) {
        throw new NullPointerException();
    }
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    /*
    * Look up or generate the designated proxy class.
    */
    Class<?> cl = getProxyClass0(loader, intfs);
    /*
    * Invoke its constructor with the designated invocation handler.
    */
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
            // create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return newInstance(cons, ih);
                }
            });
        } else {
            return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

//Continue to look at the new Instance method
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
    try {
        return cons.newInstance(new Object[] {h} );
    } catch (IllegalAccessException | InstantiationException e) {
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString());
        }
    }
}

From this we can see that creating proxy objects is to use reflection to obtain the constructor of the target object first, and then generate the proxy object by constructing reflection.

When is the < font color = Red > parse 2 > </font > invoke method called?
We export the bytecode of the generated proxy object to disk through a tool class, and then decompile it to see what the proxy object contains.
Tool classes are as follows:

public class ProxyGeneratorUtils {
    /**
        * Write the bytecode of the proxy class on the hard disk
        * @param fileName file name
        * @param path Path information
        * @param clazz Interface arrays for target classes
        */
    public static void writeProxyClassToHardDisk(String fileName, String path, Class<?>[] clazz) {
        byte[] classFile = ProxyGenerator.generateProxyClass(fileName, clazz);
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //Main Method
    public static void main(String[] args) {
        ProxyGeneratorUtils.writeProxyClassToHardDisk("$JDKProxy1","F:/$JDKProxy1.class",UserServiceImpl.class.getInterfaces());
    }
}

Run the main method to generate the proxy object bytecode file, and decompile it with JD.exe to open it as follows

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

    public $JDKProxy1(InvocationHandler arg0) throws  {
        super(arg0);
    }
    public final void saveUser(User arg0) throws  {
            try {
                super.h.invoke(this, m3, new Object[]{arg0});
            } catch (RuntimeException | Error arg2) {
                throw arg2;
            } catch (Throwable arg3) {
                throw new UndeclaredThrowableException(arg3);
            }
        }
    ...

Use a diagram to illustrate what happens inside the saveUser() method that calls the userServiceProxy proxy object

CGLIB Dynamic Agent

Cglib dynamic proxy also needs an intermediate class that generates proxy objects, which implements the Method Interceptor interface, which is now integrated by spring in the cglib package and < br > in the spring-core package.
Generating intermediate classes of proxy objects

import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    private Object target; //References holding the target object
    public CglibProxy(Object target){
        this.target = target;
    }
    //Creating proxy objects
    public Object createProxy(){
        Enhancer enhancer = new Enhancer(); //1. Create Enhancer
        enhancer.setSuperclass(target.getClass());  //2. class passing the target object
        enhancer.setCallback(this); //3. Set up callback operation (equivalent to Invocation Hanlder)
        return enhancer.create();
    }
    //Equivalent to invoke in Invocation Hanlder
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("Start recording time");
        Object ret = method.invoke(target, args);//Execute the target object method, where target is written. If proxy is written, it will loop until memory overflows.
        long endTime = System.currentTimeMillis();
        System.out.println("The method of saving user information is time-consuming" + (endTime-startTime) + "Millisecond");
        return ret;
    }
}

Client

public class ProxyTest {
    @Test
    public void test1() {
        UserService userService = new UserServiceImpl();    //1. Create target objects
        CglibProxy factory = new CglibProxy(customerService);   // 2. Creating proxy objects through CglibProxy
        UserService userServiceProxy = (UserService)factory.createProxy();
        userServiceProxy.saveUser(new User("1","Zhang San"));
    }
}

Proxy object bytecode is generated and decompiled with JD.exe as follows

import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class UserServiceImpl$$EnhancerByCGLIB$$1772a9ea extends UserServiceImpl implements Factory {
    ...
    public final void saveUser(User paramUser)
    {
        MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
        if (tmp4_1 == null){
            tmp4_1;
            CGLIB$BIND_CALLBACKS(this);
        }
        if (this.CGLIB$CALLBACK_0 != null) return;
        super.saveUser(paramUser);
    }
    ...
}

The difference between JDK dynamic proxy and CGLIB dynamic proxy is as follows:
1. JDK dynamic proxy is for interface proxy. The target class must implement the interface, and the generated proxy object will also implement the interface.

2. CGLIB proxy adopts inheritance, and the proxy object generated inherits from the target class, so the target class and the target method can not be modified with final.

Tags: Java JDK Spring jvm

Posted on Fri, 30 Aug 2019 01:27:56 -0700 by Jay