Cglib, Javassist, JDK dynamic proxy

Keywords: Programming JDK Java Spring Mybatis

1. Introduction

Java's dynamic proxy is really important, especially when you want to understand the principles of some frameworks. If you are not familiar with Java dynamic proxy or even know Java dynamic proxy, you can basically just say "I'm too hard".

For example, to know why MyBatis doesn't need an implementation, you only need an interface and a corresponding xml file to perform database operations.

Why do we use a @Transactional annotation to automatically roll back transactions in Spring?

Of course, knowing about Java's dynamic proxy, we can also implement some additional features that are non-intrusive, transparent and user-friendly.

So to understand these principles, let's start with a look at Java dynamic proxy.

2. JDK Dynamic Agent

First, let's see how some dynamic proxies for JDK are implemented.

To understand the dynamic proxy of JDK, you only need to understand the Proxy class and the InvocationHandler interface.

But before that, let's introduce two concepts: Target object: The object being proxied Proxy objects: Proxy dynamically generated and loaded objects

2.1 InvocationHandler

Let's start with InvocationHandler, the InvocationHandler interface is a very simple method, as follows:

public Object invoke(Object proxy, Method method, Object[] args)

This method is where the JDK proxy logic is implemented, and the proxy is the proxy object, which is created in the Proxy class.

Method is the method that the target object will execute, and args is the parameter passed in when the Method is called.

Our logic in invoke is generally the same;

//Proxy logic before original method execution
method.invoke(target, args);//Original method
//Proxy logic after execution of the original method

As we can see, the proxy object is not used at all, but the target object is used, because the proxy part will generally execute the logic of the original object.

Note: Do not use proxy on method.invoke methods, but use a target object

2.2 Proxy

InvocationHandler implements, where will it be called?

Let's take a look at some Proxy, and it's clear.

Proxy focuses on an important static method:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

The above method is to create a proxy method for the proxy object.

Think carefully about the class before creating the object. What class is the proxy object Proxy generates using newProxyInstance?

This concludes directly that the classes used by Proxy to create objects using newProxyInstance are dynamically generated and created

This class inherits the Proxy class and implements the interfaces interface passed in as the second parameter of the newProxyInstance method

The byte code is generated by the ProxyGenerator.generateProxyClass method, and it is interesting to follow the getProxyClass0 method of the newProxyInstance method.

The loader used by the byte code is the ClassLoader passed in as the first parameter of the newProxyInstance method.

So what do I need this InvocationHandler for?Don't worry, let's see what the methods in the generated proxy object will look like

public final int insert(User paramUser) throws {
    try{
      return ((Integer)this.h.invoke(this, m3, new Object[] { paramUser })).intValue();
    }
    catch (Error|RuntimeException localError){
      throw localError;
    }
    catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

this.h above is the parameter InvocationHandler passed in by newProxyInstance to create the proxy object, and we can see that the methods in the interface that implements interfaces are actually invoke methods that call InvocationHandler.

m3 is a Method object, obtained through Class.forName, and the parameter is the fully qualified name of the interfaces.

m3 = Class.forName("xxxx.UserMapper").getMethod("insert", new Class[] { Class.forName("xxxx.User") });

There are no specific examples. You can refer to other articles later, or you can read BeanPostProcessor and Spring Non-intrusive Extensions By the way, you can also learn about BeanPostProcessor.

3. cglib

The dynamic proxy for cglib and JDK is very similar. The most interesting part of cglib is that it has no documentation, but that's okay. Let's just look at the MethodInterceptor interface.

3.1 Proxy Logic

Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy)

First look at the parameters:

  1. Proxy:cglib generated proxy object
  2. Method: the proxy object method, that is, the target object method
  3. args:proxy method
  4. methodProxy: Proxy method
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class DoSomethingMethodInterceptor implements MethodInterceptor {

    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//        return method.invoke(o,objects);
//        System.out.println(proxy);
        return methodProxy.invokeSuper(proxy,objects);
    }
}

It is confusing to call the target object in MethodInterceptor using the proxy method methodProxy on the proxy object and invokeSuper instead of invoke.

In my English teacher's words, here are the fixed usages, just remember them:

methodProxy.invokeSuper(proxy,objects);

3.2 Creating proxy objects

@Test
public void testBusinessDoSomethingMethodInterceptor() {
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\tmp\\code");
    BusinessServiceImpl target = new BusinessServiceImpl();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass());
    enhancer.setCallback(new DoSomethingMethodInterceptor());
    BusinessService bs = (BusinessService) enhancer.create();
    System.out.println(bs.doSomething(1, "curitis"));
}

The system properties are set so that cglib writes the generated classes to the specified directory so that you can see the logic of the generated classes when debugging or analyzing.

Important class Enhancer, use Enhancer in three steps:

  1. setSuperclass Set Target Class
  2. setCallback Sets MethodInterceptor to handle proxy logic
  3. create generates proxy classes

3.3 Summary

  1. JDK dynamic proxy implements the interface of the proxy object, Cglib inherits the proxy object
  2. Both JDK and Cglib generate byte codes at run time, and Cglib writes Class byte codes using the ASM framework
  3. Cglib proxy class is less efficient than JDK
  4. Cglib is more efficient than JDK
  5. Cglib is not capable of proxying final-modified methods
  6. MethodInterceptor#methodProxy.invokeSuper(proxy,objects)
  7. Enhancer Sets Proxy Object, Sets Proxy Logic, Creates Proxy Object

4. javassist

The power of javassist lies in its ability to manipulate byte codes, dynamically modify classes, load classes, add delete fields, methods, and so on.

Of course, dynamic proxy can also be implemented. Here we will introduce how to implement dynamic proxy through javassist.

4.1 Proxy Logic (MethodHandler)

The javassist proxy logic can be in the MethodHandler interface, where MethodHandler has only one method:

Object invoke(Object self, Method thisMethod, Method proceed,Object[] args)

self: generated proxy class thisMethod: The method of the target class proceed: Method of proxy class args: parameters to execute method

The execution logic of javassist is similar to cglib in that it calls the proxy method on the proxy class instance:

proceed.invoke(self, args)
import javassist.util.proxy.MethodHandler;

import java.lang.reflect.Method;

public class DoSomethingMethodHandler implements MethodHandler {

    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println(self.getClass());
        System.out.println(thisMethod.getName());
        System.out.println(proceed.getName());
        Object result = proceed.invoke(self, args);
        return result;
    }
}

4.2 Creating proxy classes

javassist creates a proxy class through the ProxyFactory in four steps:

  1. Create ProxyFactory Instance
  2. Set Parent Class
  3. Set Proxy Logical Processing (MethodHandler)
  4. Create proxy class instance (proxyFactory.createClass().newInstance())
import javassist.util.proxy.ProxyFactory;

public class JavassistProxyFactory<T> {

    private T target;

    public JavassistProxyFactory(T target) {
        this.target = target;
    }

    @SuppressWarnings( {"deprecation","uncheked"})
    public T getProxy() throws InstantiationException, IllegalAccessException {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(target.getClass());
        proxyFactory.setHandler(new DoSomethingMethodHandler());
        return (T) proxyFactory.createClass().newInstance();
    }

    @SuppressWarnings( {"deprecation","uncheked"})
    public static <T> T getProxy(Class<T> clazz) throws InstantiationException, IllegalAccessException {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(clazz);
        proxyFactory.setHandler(new DoSomethingMethodHandler());
        return (T) proxyFactory.createClass().newInstance();
    }
}

4.3 Test

import org.curitis.service.BusinessService;
import org.curitis.service.impl.BusinessServiceImpl;
import org.junit.Test;

public class JavasistTest {

    @Test
    public void test() throws IllegalAccessException, InstantiationException {
        BusinessService proxy = JavassistProxyFactory.getProxy(BusinessServiceImpl.class);
        proxy.doSomething(1,"curitis");
    }

}

5. Appendix

5.1 pom

<properties>
    <javassist.version>3.12.1.GA</javassist.version>
    <cglib.version>3.2.5</cglib.version>
</properties>
<dependency>
    <groupId>javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>${javassist.version}</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>${cglib.version}</version>
</dependency>

5.2 Business classes used for testing

public interface BusinessService {
    String doSomething(Integer id,String name);
}
import org.springframework.stereotype.Service;

@Service("businessService")
public class BusinessServiceImpl implements BusinessService{
    @Override
    public String doSomething(Integer id, String name) {
        return id + " " + name;
    }
}

6. Reference

proxy pattern

Byte code generation using the ProxyGenerator class

Exploration of Java Dynamic Proxy

javassist

Posted by mharju on Sun, 10 Nov 2019 17:04:46 -0800