I don't believe you all know the principle of native dynamic agent and CGLIB dynamic agent in Java!

Yunqi information:[ Click to see more industry information]
Here you can find the first-hand cloud information of different industries. What are you waiting for? Come on!

Dynamic proxy is widely used in Java, such as Spring AOP, Hibernate data query, back-end mock and RPC of test framework, Java annotation object acquisition, etc. The proxy relationship of static agent is determined at compile time, while that of dynamic agent is determined at compile time. Static agent is easy to implement, which is suitable for the situation of less and definite agent classes, while dynamic agent gives us more flexibility.

Today, let's explore two common dynamic proxy methods in Java: JDK native dynamic proxy and CGLIB dynamic proxy.

JDK native dynamic agent

Let's start with an intuitive example. Suppose we have an interface Hello and a simple implementation HelloImp:

// Interface
interface Hello{
    String sayHello(String str);
}
// Realization
class HelloImp implements Hello{
    @Override
    public String sayHello(String str) {
        return "HelloImp: " + str;
    }
}

This is a common scenario in Java. It uses interfaces to formulate protocols, and then uses different implementations to implement specific behaviors. Suppose you have obtained the above class library. If we want to log the call to sayHello(), we can do this using the static agent:

// Static agent mode
class StaticProxiedHello implements Hello{
    ...
    private Hello hello = new HelloImp();
    @Override
    public String sayHello(String str) {
        logger.info("You said: " + str);
        return hello.sayHello(str);
    }
}

The static proxy class StaticProxiedHello in the above example, as the proxy of HelloImp, implements the same Hello interface.

You can do this with a Java Dynamic Proxy:

  • First, implement an InvocationHandler, and the method call will be forwarded to the invoke() method of the class.
  • Then, when you need to use Hello, get the proxy object of Hello through JDK dynamic proxy.
// Java Proxy
// 1. Implement an InvocationHandler first, and the method call will be forwarded to the invoke() method of the class.
class LogInvocationHandler implements InvocationHandler{
    ...
    private Hello hello;
    public LogInvocationHandler(Hello hello) {
        this.hello = hello;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("sayHello".equals(method.getName())) {
            logger.info("You said: " + Arrays.toString(args));
        }
        return method.invoke(hello, args);
    }
}
// 2. Then when you need to use Hello, get the proxy object of Hello through JDK dynamic proxy.
Hello hello = (Hello)Proxy.newProxyInstance(
    getClass().getClassLoader(), // 1. Class loader
    new Class<?>[] {Hello.class}, // 2. The interface that the agent needs to implement can have multiple
    new LogInvocationHandler(new HelloImp()));// 3. The actual handler of the method call
System.out.println(hello.sayHello("I love you!"));

Run the above code to output the result:

The key of the above code is the Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler) method, which creates a proxy object dynamically according to the specified parameters. The meanings of the three parameters are as follows:

  • Loader, which specifies the class loader of the proxy object;
  • Interfaces, the interface that the proxy object needs to implement, can specify multiple interfaces at the same time;
  • Handler, the actual handler of the method call, and the method call of the proxy object will be forwarded here (* Note 1).

newProxyInstance() returns a proxy object that implements the specified interface, and all method calls to the object are forwarded to the InvocationHandler.invoke() method. Understanding the above code requires some understanding of the Java reflection mechanism. The magic of dynamic agent is:

  • Proxy objects are generated when the program is running, not at compile time;
  • All the interface method calls to the proxy object will be forwarded to the InvocationHandler.invoke() method. In the invoke() method, we can add any logic, such as modifying the method parameters, adding the logging function, security check function, etc.; after that, we execute the real method body in some way. In the example, we call the corresponding method of the Hello object through reflection, and also through R The PC calls the remote method.

If you dig deep into the object types after the JDK proxy, you can see the following information:

# Type information of Hello proxy object
class=class jdkproxy.$Proxy0
superClass=class java.lang.reflect.Proxy
interfaces: 
interface jdkproxy.Hello
invocationHandler=jdkproxy.LogInvocationHandler@a09ee92

The type of proxy object is jdkproxy.$Proxy0, which is a dynamically generated type. The class name is in the form of $ProxyN. The parent class is java.lang.reflect.Proxy, which will be inherited by all JDK dynamic proxies. At the same time, the Hello interface is implemented, which is the interfaces specified in our interface list. Extension: Java interview question content aggregation
If you are also interested in the specific implementation of jdkproxy.$Proxy0, it looks like this:

// Specific implementation of JDK proxy class
public final class $Proxy0 extends Proxy implements Hello
{
  ...
  public $Proxy0(InvocationHandler invocationhandler)
  {
    super(invocationhandler);
  }
  ...
  @Override
  public final String sayHello(String str){
    ...
    return super.h.invoke(this, m3, new Object[] {str});// Forward method call to invocationhandler
    ...
  }
  ...
}

These logics are not complicated, but they are generated dynamically at run time and do not need to be written manually. For more details, please refer to:

Java dynamic proxy provides us with a very flexible proxy mechanism, but Java dynamic proxy is based on the interface. If the object does not implement the interface, how can we proxy? CGLIB on stage.

CGLIB dynamic agent

CGLIB(Code Generation Library) is an ASM based bytecode generation library, which allows us to modify and dynamically generate bytecode at runtime. CGLIB implements the proxy by inheritance.

Let's look at the example. Suppose we have a class HelloConcrete that does not implement any interfaces:

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

Because there is no implementation interface for this class, the JDK proxy cannot be used. The implementation of CGLIB proxy is as follows:

  • First, implement a MethodInterceptor, and the method call will be forwarded to the intercept() method of this class.
  • Then, when you need to use HelloConcrete, get the proxy object through CGLIB dynamic proxy.
// CGLIB dynamic agent
// 1. Implement a MethodInterceptor first, and the method call will be forwarded to the intercept() method of this class.
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
// 2. Then when you need to use HelloConcrete, get the proxy object through CGLIB dynamic proxy.
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

Run the above code to output the result:

In the above code, we use the Enhancer of CGLIB to specify the target object to be proxy and the object to actually process the proxy logic. Finally, we call create() to get the proxy object. All calls to non final methods of this object will be forwarded to MethodInterceptor.intercept() method. In the intercept() method, we can add any logic, such as modifying method parameters Add log function, security check function, etc;

By calling methodproxy. Invokeseuper() method, we forward the call to the original object. In this case, the specific method of HelloConcrete. The function of MethodInterceptor in CGLIG is similar to that of InvocationHandler in JDK agent, both of which are transit stations for method calls.

If you dig deep into the object types after CGLIB proxy, you can see the following information:

# Type information for HelloConcrete proxy object
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces: 
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class

We can see that the object type after using CGLIB proxy is cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52, which is the type generated dynamically by CGLIB; the parent class is HelloConcrete, which proves that CGLIB implements proxy through inheritance; at the same time, it implements the net.sf.cglib.proxy.Factory interface, which is added by CGLIB itself and contains some tool methods.
Note that since it is inheritance, we have to consider the issue of final. We know that the final type cannot have subclasses, so CGLIB cannot proxy the final type. In this case, an exception similar to the following will be thrown:
java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete
Similarly, the final method cannot be overloaded, so it cannot be proxied through CGLIB. In this case, it will not throw an exception, but will skip the final method and only proxy other methods.
If you are also interested in the concrete implementation of the proxy class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52, it looks like this:

// Concrete implementation of CGLIB proxy class
public class HelloConcrete$$EnhancerByCGLIB$$e3734e52
  extends HelloConcrete
  implements Factory
{
  ...
  private MethodInterceptor CGLIB$CALLBACK_0; // ~~
  ...

  public final String sayHello(String paramString)
  {
    ...
    MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      // Forward the request to the MethodInterceptor.intercept() method.
      return (String)tmp17_14.intercept(this, 
              CGLIB$sayHello$0$Method, 
              new Object[] { paramString }, 
              CGLIB$sayHello$0$Proxy);
    }
    return super.sayHello(paramString);
  }
  ...
}

As we can see in the above code, when calling the sayHello() method of the proxy object, we will first try to forward it to the MethodInterceptor.intercept() method. If there is no MethodInterceptor, we will execute the sayHello() of the parent class. These logics are not complicated, but they are generated dynamically at run time and do not need to be written manually.
For more information about CGLIB, please refer to:

epilogue

This paper introduces the usage and principle of two common dynamic proxy mechanisms in Java. JDK native dynamic proxy is supported by Java Native and does not need any external dependency, but it can only proxy based on the interface. CGLIB proxy through inheritance, whether the target object has an implementation interface or not, it can proxy, but it can not handle the final situation.

Dynamic agent is the implementation of Spring AOP (aspect oriented programming). Understanding the principle of dynamic agent is very helpful to understand Spring AOP.

[yunqi online class] product technology experts share every day!
Course address: https://yqh.aliyun.com/live

Join the community immediately, face to face with experts, and keep abreast of the latest news of the course!
[yunqi online classroom community] https://c.tb.cn/F3.Z8gvnK

Original release time: April 12, 2020
By Carpenter Lee
This article comes from:“ Internet architect WeChat official account ”, you can pay attention to“ Internet architect"

Tags: Java JDK Spring Hibernate

Posted on Sun, 12 Apr 2020 22:36:30 -0700 by kcpaige89