Java设计模式之代理模式 - CGLib 动态代理分析

JDK代理一样,CGLib 代理也是一种动态代理方式,而且相比JDK代理更加的灵活,可以代理任何类(除了final修饰的)。

先上代码,还是以买房为例,看下具体实现:

需要被代理的类,这次不是某个接口的实现类,而是一个普通类

public class HouseBuyer {

    public void buy() {
        System.out.println("要买房");
    }

}

代理类

public class CglibHouseCompanyProxy implements MethodInterceptor {

    public Object getInstance(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        return obj;
    }

    private void before() {
        System.out.println("before method ...");
    }

    private void after() {
        System.out.println("after method ...");
    }
}

在被客户端实际调用的时候

HouseBuyer buyer = (HouseBuyer) new CglibHouseCompanyProxy().getInstance(new HouseBuyer());
buyer.buy();

是不是似曾相识,代理类都是实现了某个接口,然后重写一个方法,看着都是利用反射去获取被代理类的方法,经过增强处理然后去执行。

同样的,经过断点调试后会发现,真正执行方法的对象也是也是新的对象,

Java设计模式之代理模式 - CGLib 动态代理分析

可以通过org.springframework.cglib.core包里的DebuggingClassWriter类将代理过程的字节码文件生成到磁盘上来简单分析下,在上面调用方法的前面加上

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "external-file/cglib-debug");

重新跑一遍代码,在项目根目录下会生成external-file文件夹,在里面找到HouseBuyer$$EnhancerByCGLIB$$a1e313be类进行反编译,节选部分代码

public class HouseBuyer$$EnhancerByCGLIB$$a1e313be extends HouseBuyer 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$buy$0$Method;
    private static final MethodProxy CGLIB$buy$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("xxx.xxx.xxx.cglibproxy.HouseBuyer$$EnhancerByCGLIB$$a1e313be");
        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$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$buy$0$Method = ReflectUtils.findMethods(new String[]{"buy", "()V"}, (var1 = Class.forName("xxx.xxx.xxx.cglibproxy.HouseBuyer")).getDeclaredMethods())[0];
        CGLIB$buy$0$Proxy = MethodProxy.create(var1, var0, "()V", "buy", "CGLIB$buy$0");
    }

    final void CGLIB$buy$0() {
        super.buy();
    }

    public final void buy() {
        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$buy$0$Method, CGLIB$emptyArgs, CGLIB$buy$0$Proxy);
        } else {
            super.buy();
        }
    }
}

代理生成的类继承了被代理类,并且里面还有一个被final修饰的buy方法,这个方法里MethodInterceptor获取引用之后就会执行intercept方法,而我们的代理类也是MethodInterceptor接口的实现类,所以在客户端类时间执行的时候最终执行的是代理类的intercept方法。至于CGLib是如何生成这个代理对象的,可以从代理类获取增强实例enhancer.create()那里一步一步分析,这里就不做了。

小结

CGLib 通过为代理类生成一个继承的子类形式来代理,对被代理类没有限制,不需要像JDK代理那样需要实现接口。底层通过字节码处理框架ASM来生成Java的字节码形成新的类,也就是上文新的代理类。简而言之:

  • JDK 代理是实现了被代理对象的接口,而CGLib代理是继承了被代理对象
  • 两者都是在运行期间生成字节码,JDK代理是直接写Class字节码,而CGLib是通过ASM框架来写Class字节码
上一篇:动态代理最全详解系列[5]-Cglib动态代理源码分析


下一篇:Java 动态代理