CGLIB dynamic agent -- example / principle

Posted by FrankA on Sun, 13 Feb 2022 13:06:01 +0100

Original website:

brief introduction

explain

This article introduces the usage of CGLIB dynamic agent with examples.

principle

CGLIB implements proxies for classes.

The principle is to generate a subclass of the specified target class and override its methods to achieve enhancement. However, because inheritance is adopted, the class modified by final cannot be represented.  

limit

The dynamic proxy mechanism of JDK can only proxy the classes that implement the interface, but the classes that cannot implement the interface cannot implement the dynamic proxy of JDK.

Example: count execution time

rely on

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>

Earlier versions of cglib may require this dependency:

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>5.0.1</version>
</dependency>

code

package org.example.a;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

//Proxy class
class ExecutionTimeReality {
    //The task name is printed here, and the simulated task has been executed for a long time after sleeping for 500ms
    public void dealTask(String taskName) {
        System.out.println("Task is running: " + taskName);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //The task name is printed here, and the simulated task has been executed for a long time after sleeping for 500ms
    public void sayHello(String name) {
        System.out.println("hello: " + name);
    }
}

//proxy class
class ExecutionTimeProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        proxy.invokeSuper(obj, args);
        long endTime = System.currentTimeMillis();
        System.out.println("Elapsed time: " + (endTime - startTime) + " ms");

        return null;
    }
}

//Factory method to get proxy object
class DynamicProxyFactory {
    public static Object getInstance() {
        Object target = new ExecutionTimeReality();
        ExecutionTimeProxy proxySubject = new ExecutionTimeProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(proxySubject);
        return enhancer.create();
    }
}

public class Demo {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\CglibProxyClass");
        ExecutionTimeReality subject = (ExecutionTimeReality) DynamicProxyFactory.getInstance();
        subject.dealTask("TestTask");
    }
}

results of enforcement

Task is running: TestTask
Elapsed time: 524 ms

principle

Process Overview

This part should be put to the end as a conclusion. However, in order to quickly understand the overall process, we put it at the front.

Instantiate Enhancer and set the class and callback to proxy (implementation class object of MethodInterceptor interface)
Create a proxy class (inherited from the proxied class)
dealTask method of the proxy class (overridden by the proxy class, actually calling dealTask method of the proxy class)
intercept method of callback (ExecutionTimeProxy.intercept) / / callback is passed in the first step
        proxy.invokeSuper(obj, args); / / invoking the invokesuper method of the parent class is declared by MethodProxy
            this.init(); / / if FastClass does not exist, generate
                this.fastClassInfo.f2.invoke(fci.i2, obj, args);
invoke method of FastClass
Compare the first parameter with the number and execute the corresponding method

Manually generate proxy class source code

Analyzing the source code depends on the generated proxy class source code. Generation method: write this code at the beginning of main:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\CglibProxyClass");

After running, the E:/CglibProxyClass folder will be generated, and its structure is as follows

E:\CGLIBPROXYCLASS
├─net
│  └─sf
│      └─cglib
│          ├─core
│          │      MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class
│          │
│          └─proxy
│                  Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class
│
└─org
    └─example
        └─a
                ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386$$FastClassByCGLIB$$bebee033.class
                ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386.class
                ExecutionTimeReality$$FastClassByCGLIB$$81389d66.class

Because two $are not displayed normally in CSDN, the next $is used to represent two$

ExecutionTimeReality$EnhancerByCGLIB$5a92c386$FastClassByCGLIB$bebee033.class

Fast class 1. This category will not be discussed for the time being. Not analyzed in the code.

ExecutionTimeReality$EnhancerByCGLIB$5a92c386.class

The proxy class will call executiontimereality $enhancerbycglib $5a92c386 class

ExecutionTimeReality$FastClassByCGLIB$81389d66.class

Fast class 2. Called to by proxy class. FastClass is not generated together with the proxy class, but is generated when the MethodProxy invoke/invokeSuper is executed for the first time and placed in the cache.

ExecutionTimeReality$EnhancerByCGLIB$5a92c386.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.example.a;

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

public class ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386 extends ExecutionTimeReality implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$dealTask$0$Method;
    private static final MethodProxy CGLIB$dealTask$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$sayHello$1$Method;
    private static final MethodProxy CGLIB$sayHello$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("org.example.a.ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$2$Method = var10000[0];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[1];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[2];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[3];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        var10000 = ReflectUtils.findMethods(new String[]{"dealTask", "(Ljava/lang/String;)V", "sayHello", "(Ljava/lang/String;)V"}, (var1 = Class.forName("org.example.a.ExecutionTimeReality")).getDeclaredMethods());
        CGLIB$dealTask$0$Method = var10000[0];
        CGLIB$dealTask$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "dealTask", "CGLIB$dealTask$0");
        CGLIB$sayHello$1$Method = var10000[1];
        CGLIB$sayHello$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "sayHello", "CGLIB$sayHello$1");
    }

    final void CGLIB$dealTask$0(String var1) {
        super.dealTask(var1);
    }

    public final void dealTask(String var1) {
        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$dealTask$0$Method, new Object[]{var1}, CGLIB$dealTask$0$Proxy);
        } else {
            super.dealTask(var1);
        }
    }

    final void CGLIB$sayHello$1(String var1) {
        super.sayHello(var1);
    }

    public final void sayHello(String var1) {
        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$sayHello$1$Method, new Object[]{var1}, CGLIB$sayHello$1$Proxy);
        } else {
            super.sayHello(var1);
        }
    }

    final boolean CGLIB$equals$2(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$2$Method, new Object[]{var1}, CGLIB$equals$2$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$3() {
        return super.toString();
    }

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

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$4() {
        return super.hashCode();
    }

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

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$5() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -1165719922:
            if (var10000.equals("dealTask(Ljava/lang/String;)V")) {
                return CGLIB$dealTask$0$Proxy;
            }
            break;
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$5$Proxy;
            }
            break;
        case 771401912:
            if (var10000.equals("sayHello(Ljava/lang/String;)V")) {
                return CGLIB$sayHello$1$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$2$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$3$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$4$Proxy;
            }
        }

        return null;
    }

    public ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386 var1 = (ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386 var10000 = new ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386 var10000 = new ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386 var10000 = new ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386;
        switch(var1.length) {
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

It can be seen from the source code of the proxy class that the proxy class will obtain all the methods inherited from the parent class, and there will be a MethodProxy corresponding to it, such as:

private static final Method CGLIB$dealTask$0$Method;
private static final MethodProxy CGLIB$dealTask$0$Proxy;
private static final Method CGLIB$sayHello$1$Method;
private static final MethodProxy CGLIB$sayHello$1$Proxy;

In addition, from the perspective of the type of proxy class, it directly inherits the class to be proxy, so it can proxy the class without interface. In fact, it has nothing to do with interfaces, and interfaces will not be used.

public class ExecutionTimeReality$$EnhancerByCGLIB$$5a92c386 extends ExecutionTimeReality implements Factory

Method call

final void CGLIB$dealTask$0(String var1) {
    super.dealTask(var1);
}

public final void dealTask(String var1) {
    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$dealTask$0$Method, new Object[]{var1}, CGLIB$dealTask$0$Proxy);
    } else {
        super.dealTask(var1);
    }
}

FastClass mechanism

The reason why the efficiency of Cglib dynamic agent executing proxy method is higher than that of JDK is that Cglib adopts the FastClass mechanism. Its principle is simply to generate a Class for the proxy Class and the proxy Class respectively. This Class will assign an index(int type) to the methods of the proxy Class or the proxy Class. Using this index as an input parameter, FastClass can directly locate the method to be called and call it directly, which eliminates the reflection call, so the calling efficiency is higher than that of JDK dynamic agent through reflection call.

ExecutionTimeReality$FastClassByCGLIB$81389d66

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.example.a;

import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;

public class ExecutionTimeReality$$FastClassByCGLIB$$81389d66 extends FastClass {
    public ExecutionTimeReality$$FastClassByCGLIB$$81389d66(Class var1) {
        super(var1);
    }

    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -1165719922:
            if (var10000.equals("dealTask(Ljava/lang/String;)V")) {
                return 0;
            }
            break;
        case 771401912:
            if (var10000.equals("sayHello(Ljava/lang/String;)V")) {
                return 1;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return 2;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return 3;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return 4;
            }
        }

        return -1;
    }

    public int getIndex(String var1, Class[] var2) {
        switch(var1.hashCode()) {
        case -2012993625:
            if (var1.equals("sayHello")) {
                switch(var2.length) {
                case 1:
                    if (var2[0].getName().equals("java.lang.String")) {
                        return 1;
                    }
                }
            }
            break;
        case -1776922004:
            if (var1.equals("toString")) {
                switch(var2.length) {
                case 0:
                    return 3;
                }
            }
            break;
        case -1295482945:
            if (var1.equals("equals")) {
                switch(var2.length) {
                case 1:
                    if (var2[0].getName().equals("java.lang.Object")) {
                        return 2;
                    }
                }
            }
            break;
        case 147696667:
            if (var1.equals("hashCode")) {
                switch(var2.length) {
                case 0:
                    return 4;
                }
            }
            break;
        case 510300177:
            if (var1.equals("dealTask")) {
                switch(var2.length) {
                case 1:
                    if (var2[0].getName().equals("java.lang.String")) {
                        return 0;
                    }
                }
            }
        }

        return -1;
    }

    public int getIndex(Class[] var1) {
        switch(var1.length) {
        case 0:
            return 0;
        default:
            return -1;
        }
    }

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

        try {
            switch(var10001) {
            case 0:
                var10000.dealTask((String)var3[0]);
                return null;
            case 1:
                var10000.sayHello((String)var3[0]);
                return null;
            case 2:
                return new Boolean(var10000.equals(var3[0]));
            case 3:
                return var10000.toString();
            case 4:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
        ExecutionTimeReality var10000 = new ExecutionTimeReality;
        ExecutionTimeReality var10001 = var10000;
        int var10002 = var1;

        try {
            switch(var10002) {
            case 0:
                var10001.<init>();
                return var10000;
            }
        } catch (Throwable var3) {
            throw new InvocationTargetException(var3);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public int getMaxIndex() {
        return 4;
    }
}

Other websites

Differences between JDK dynamic agent and CGLIB agent_ knowledge has no limit! Endless- CSDN blog

Implementation principle of Cglib dynamic agent - sugar Bean Bun - blog Garden

Topics: Java Dynamic Proxy