This article focuses on two common dynamic proxy methods in Java: JDK native dynamic proxy and CGLIB dynamic proxy.

What is the proxy pattern

Provides a proxy for other objects to control access to that object. Agents can add additional functionality (extensibility) without changing the target object.

There are three types of roles in proxy mode:

  • Subject(Abstract topic role) : defines the public external method of the proxy class and the real topic, which is also the method of the proxy class to proxy the real topic;
  • RealSubject(Real subject roles) : Classes that actually implement business logic;
  • ProxyProxy topic roles: Used to proxy and encapsulate real topics;

According to the bytecode creation time, it can be classified into static proxies and dynamic proxies:

  • By static, we mean bytecode files with proxy classes that exist before the program runs, and the relationship between proxy classes and real subject roles is determined before the program runs.
  • The source code of the dynamic proxy is dynamically generated by the JVM during the program running based on reflection and other mechanisms, so there is no bytecode file of the proxy class before running

Static agent

Before learning about dynamic proxies, it is necessary to learn about static proxies.

When static proxies are used, interfaces or parent classes need to be defined. The proided object (target object) and Proxy implement the same interface or inherit the same parent class.

Let’s look at an example that simulates the walking time of a kitten.

/ / interface
public interface Walkable {
    void walk(a);
}

/ / implementation class
public class Cat implements Walkable {

    @Override
    public void walk(a) {
        System.out.println("cat is walking...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

What if I want to know the walking time? You can change the implementation class Cat to:

public class Cat implements Walkable {

    @Override
    public void walk(a) {
        long start = System.currentTimeMillis();
        System.out.println("cat is walking...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("walk time = "+ (end - start)); }}Copy the code

This is an intrusion into the source code, and if the source code is immutable, you can’t write it this way, so you can introduce the time proxy class CatTimeProxy.

public class CatTimeProxy implements Walkable {
    private Walkable walkable;

    public CatTimeProxy(Walkable walkable) {
        this.walkable = walkable;
    }

    @Override
    public void walk(a) {
        long start = System.currentTimeMillis();

        walkable.walk();

        long end = System.currentTimeMillis();
        System.out.println("Walk time = "+ (end - start)); }}Copy the code

If we want to add the usual logging functionality, we also need to create a logging proxy class, CatLogProxy.

public class CatLogProxy implements Walkable {
    private Walkable walkable;

    public CatLogProxy(Walkable walkable) {
        this.walkable = walkable;
    }

    @Override
    public void walk(a) {
        System.out.println("Cat walk start...");

        walkable.walk();

        System.out.println("Cat walk end..."); }}Copy the code

If we need to log first and then get the travel time, we can do so at the call:

public static void main(String[] args) {
    Cat cat = new Cat();
    CatLogProxy p1 = new CatLogProxy(cat);
    CatTimeProxy p2 = new CatTimeProxy(p1);

    p2.walk();
}
Copy the code

In this case, the timing includes the time to log.

Static proxy problems

If we need to calculate the runtime of 100 methods in the SDK, the same code needs to be repeated at least 100 times and at least 100 proxy classes created. On a smaller scale, if the Cat class has multiple methods, we need to know how long the other methods are running, and the same code needs to be repeated at least several times. Therefore, static proxies have at least two limitations:

  • Proxying multiple classes at the same time still results in unrestricted class expansion
  • If there are multiple methods in a class, the same logic needs to be implemented repeatedly

Therefore, we need a common proxy class to proxy all methods of all classes, which requires dynamic proxy technology.

A dynamic proxy

In learning any skill, you must ask yourself what is the use of it. In fact, in the course of this article, we have stated its main uses. Have you noticed that with dynamic proxy we can insert custom logic directly into methods without changing the source code? This is somewhat inconsistent with our one-line programming logic, a programming model known professionally as AOP. So-called AOP, like a knife, takes the opportunity to insert itself.

Jdk dynamic proxy

The JDK implementation proxy only needs to use the newProxyInstance method, but this method takes three arguments:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    finalClass<? >[] 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 {
        if(sm ! =null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
        if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    cons.setAccessible(true);
                    return null; }}); }return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
        throw newInternalError(e.toString(), e); }}Copy the code

Method is a static method in the Proxy class, and the three parameters received are as follows:

  • ClassLoader loader// Specify that the current target object uses a class loader
  • Class<? >[] interfaces// The type of the interface implemented by the target object
  • InvocationHandler h// Event handler

The main task is to complete the writing of InvocationHandler H.

Interface class UserService:

public interface UserService {

    public void select(a);

    public void update(a);
}
Copy the code

Interface implementation class, the class to be proxied, UserServiceImpl:

public class UserServiceImpl implements UserService {
    @Override
    public void select(a) {
        System.out.println("Query selectById." ");
    }

    @Override
    public void update(a) {
        System.out.println("Update update"); }}Copy the code

Event handler LogHandler:

public class LogHandler implements InvocationHandler{

    Object target;

    // Target is the class to be proxied
    public LogHandler(Object target){
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before(a) {     // execute before executing the method
        System.out.println(String.format("log start time [%s] ".new Date()));
    }
    private void after(a) {      // execute after executing the method
        System.out.println(String.format("log end time [%s] ".newDate())); }}Copy the code

Main program class:

public class UserServiceProxyJDKMain {
    public static void main(String[] args) {

        // 1. Create the proxied object, the implementation class of UserService
        UserServiceImpl userServiceImpl = new UserServiceImpl();

        // 2. Obtain the corresponding classLoader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();

        UserServiceImpl implements only one interface, UserService.
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();

        // 4. Create a call request handler that will be passed to the proxy class to handle method calls on all proxy objects
        // Create a custom log handler and pass in the actual execution object, userServiceImpl
        InvocationHandler logHandler = new LogHandler(userServiceImpl);

        In this process, A.dk dynamically creates bytecode B in memory equivalent to the.class file based on the passed parameter information. C. Then call newInstance() to create a proxy instance */

        // The UserServiceProxy proxy class is dynamically generated and LogHandler is instantiated with the proxy object by invoking the.invoke() method of the proxy object
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);

        // Call the proxy's method
        proxy.select();
        proxy.update();
        
        // Generate the name of the class file
        ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceJDKProxy"); }}Copy the code

Here you can save the proxy object generated by the proxy that implements the interface:

public class ProxyUtils {
    /* * Saves binary bytecode dynamically generated based on class information to disk, * default is in clazz directory * params :clazz needs to generate dynamic proxy class * proxyName: for the name of dynamically generated proxy class */
    public static void generateClassFile(Class clazz, String proxyName) {

        // Generate bytecode based on the class information and the provided proxy class name
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;

        try {
            // Save to hard disk
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch(IOException e) { e.printStackTrace(); }}}}Copy the code

Dynamic proxy implementation process

  1. throughgetProxyClass0()Generate proxy classes.JDKThe resulting true proxy class, which inherits fromProxyAnd implements the interface we defined.
  2. throughProxy.newProxyInstance()Generates an instance object of the proxy class, passed in when the object is createdInvocationHandlerType.
  3. Call the method of the new instance, the originalInvocationHandlerIn the classinvoke()Methods.

The proxy object does not need to implement the interface, but the target object must implement the interface otherwise dynamic proxies cannot be used

Cglib dynamic proxy

Cglib implements JDK dynamic proxies for classes that implement interfaces. Cglib implements JDK dynamic proxies for classes that do not implement interfaces. Its principle is to generate a subclass of the specified target class and override the method implementation enhancement. Therefore, final modified classes cannot be propped up.

The Cglib proxy, also known as a subclass proxy, builds a subclass object in memory to extend the functionality of the target object.

Cglib subclass proxy implementation method:

  1. Need to introducecglibthejarFile, butSpringIs already included in the core packageCglibFunction, so directly introducedSpring-core.jarCan.
  2. Once feature packs are introduced, subclasses can be built dynamically in memory
  3. The proxy class cannot befinalOtherwise, an error is reported
  4. If the target object’s method isfinal/staticThat is, no additional business methods of the target object are executed.

The basic use

<! -- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
</dependency>
Copy the code

Method interceptor

public class LogInterceptor implements MethodInterceptor{

    /* * @param o The object to be enhanced * @param method the method to be intercepted * @Param Objects Parameter list, the basic data type needs to be passed in its wrapper class * @Param methodProxy proxy for the method, * @return Execution result * @throws Throwable */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;

    }

    private void before(a) {
        System.out.println(String.format("log start time [%s] ".new Date()));
    }
    private void after(a) {
        System.out.println(String.format("log end time [%s] ".newDate())); }}Copy the code

The test case

This is where the.class file for the proxy class is saved

public class CglibMain {

    public static void main(String[] args) {
        // Create an Enhancer object, similar to the JDK Proxy class for dynamic proxies
        Enhancer enhancer = new Enhancer();
        // Set the bytecode file of the target class
        enhancer.setSuperclass(UserDao.class);
        // Set the callback function
        enhancer.setCallback(new LogInterceptor());
        // create creates the proxy classUserDao userDao = (UserDao)enhancer.create(); userDao.update(); userDao.select(); }}Copy the code

The results of

Log start time [Mon Nov 30 17:26:39 CST 2020] UserDao Update Update log end time [Mon Nov 30 17:26:39 CST 2020] log start Time [Mon Nov 30 17:26:39 CST 2020] UserDao Query selectById log end time [Mon Nov 30 17:26:39 CST 2020]Copy the code

Compare JDK dynamic proxies with CGLIB dynamic proxies

JDK dynamic proxy

  • In order to solve the static proxy, generate a large number of proxy classes caused by the redundancy;
  • JDKDynamic proxies need only be implementedInvocationHandlerInterface, overrideinvokeMethod to implement the proxy,
  • JDK proxies use reflection to generate proxy classesProxyxx.classProxy class bytecode and generate objects
  • JDK dynamic proxies can only proxy interfaces because the proxy classes themselves are alreadyextendstheProxyJava does not allow multiple inheritance, but does allow multiple interfaces

Advantages: Solve the problem of redundant proxy implementation classes in static proxy.

Disadvantages: JDK dynamic proxy is based on interface design implementation, if there is no interface, will throw exceptions.

Additional agent

  • Due to theJDKDynamic proxies limit design to interfaces, and in the absence of interfaces,JDKCan’t solve;
  • CGLibA very low-level bytecode technology is adopted. Its principle is to create a subclass for a class by bytecode technology, and use method interception technology in the subclass to intercept all the calls of the parent class methods, and then weave crosscutting logic to complete the implementation of dynamic proxy.
  • Implementation mode ImplementationMethodInterceptorInterface, overrideinterceptBy means ofEnhancerClass callback method to implement.
  • butCGLibIt takes much more time to create proxy objects than the JDK, so for singleton objects, because you don’t need to create objects frequently, useCGLibSuitable, conversely, useJDKThe way is more appropriate.
  • At the same time, due toCGLibSince it is using the method of dynamically creating subclasses, forfinalMethod, unable to broker.

Advantages: Dynamic proxy can be realized without interface, and bytecode enhancement technology, performance is also good.

Disadvantages: The technical implementation is relatively difficult to understand.