CGlib与Java Proxy的动态代理

CGlib与Java Proxy的动态代理

一、概述

本文分别介绍如何使用CGLib和Java的Proxy类来实现动态代理功能。

二、动态代理一般用途

1. 数据库连接和事务管理

例如Spring提供一个事务代理帮你创建/提交/回滚事务。此时的调用时序如下:

web controller --> proxy.execute(...);
  proxy --> connection.setAutoCommit(false);
  proxy --> realAction.execute();
    realAction does database work
  proxy --> connection.commit();

2. Unit测试中的动态Mock对象

在单元测试中,如果类A引用了类B,而只想对A进行单元测试,此时可以借助mock框架,基于动态代理传入一个B的mock对象给A,并设置对应方法的返回结果等。例如Mockito框架即默认使用CGLib实现动态代理功能。

3. 依赖注入框架

Spring等依赖注入容器都借助动态代理实现依赖注入的功能。

4. 类AOP框架的方法拦截(Method Interception)

AOP框架要拦截方法一般有几种方式:编译期织入(Compile Time Weaving, CTW)、载入时织入(Load Time Weaving)、运行期动态代理等。
以AspectJ和Spring AOP为例简单介绍下。
AspectJ使用ASM做字节码替换来实现AOP功能。它有3个时间点可以做织入:

  • 编译期:把aspect类(aop切面)和目标类(被aop的类)放在一起用ajc编译
  • 后编译期:目标类可能已被打包成一个jar包,这时也可用ajc命令将jar再织入一次
  • 类加载器:在jvm加载类的时候做字节码替换
    而Spring AOP则直接使用JDK Proxy 或者 CGLib的动态代理来实现AOP功能。

三、CGLib和Java Proxy的简单比较

Java Proxy动态代理的类图:
Java Proxy动态代理

CGLib类图:
CGLib

四、Cglib实现动态代理

Cglib(Code Generation Library),基于ASM,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。支持基于接口(和要代理的类实现同一个接口)和基于类(继承要代理的类)的动态代理。框架如图所示:
CGLib 框架

Hibernate在3.3以后不再使用CGLib,而改用Javassist。Spring则一直坚持使用CGLib,目前Spring4.*版本使用的是CGLib3.2。
Spring对实现了接口的类使用Jdk Proxy代理,对没实现接口的类使用CGLib代理。使用Spring声明式事务时,可以指定TransactionProxyFactoryBean的proxyTargetClass属性,手动指定使用CGLib代理。

1. 基本步骤

  • 使用Enhancer的setSuperclass设置父类,或者使用setInterfaces方法设置要实现的接口
  • 使用enhancer.setCallback()设置interceptor
  • 使用enhancer.create()得到代理类的对象,并赋值为被代理类/接口 调用代理类的方法

2. 举例

被代理类如下:

public class SampleClass {
    public String test(String input) {
        return "Hello world!";
    }

    public final String test2(){
        return "heheda";
    }
}

2.1 使用Enhancer + FixedValue

    @Test(expected = java.lang.ClassCastException.class)
    public void testFixedValue() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new FixedValue() {

            public Object loadObject() throws Exception {
                return "Fuck CGLIB!!";
            }

        }); //生产环境中不要在此处使用匿名内部类,会造成垃圾回收异常
        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test(null)); //test方法被代理覆写
        System.out.println(proxy.toString()); // 所有非final方法(即便是Object中的)都会被代理覆盖,如果调用hashCode(),会因为返回值类型不同而报错
        System.out.println(proxy.getClass()); // getClass()是final方法,不会被代理覆盖
        System.out.println(proxy.test2()); // test2是final方法,不会被代理覆盖
        System.out.println(proxy.hashCode()); //hashCode也被代理,但是它返回int,与FixedValue返回的String不一致,会抛出ClassCastException
    }

返回结果如下:

Fuck CGLIB!!
Fuck CGLIB!!
class net.sf.cglib.samples2.SampleClass$$EnhancerByCGLIB$$4ae579b
heheda

2.2 使用Enhancer + InvocationHandler

 @Test
    public void testInvocationHandler() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // 如果方法是SampleClass内部定义,并且返回String,则返回修改过的字符串
                if (method.getDeclaringClass() != Object.class
                        && method.getReturnType() == String.class) {
                    return "Fuck CGLIB!!";
                } else {
                    // 否则调用原对象的方法返回结果
                    return method.invoke(
                            method.getDeclaringClass().newInstance(), args);
                    // 如果调用method.invoke(proxy, args)会导致无限循环
                }
            }
        });
        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test(null)); //test方法被代理覆写
        System.out.println(proxy.toString()); //toString也被代理覆写,被替换成Object类的toString方法
        System.out.println(proxy.getClass()); //final方法不被覆盖
        System.out.println(proxy.test2()); //final方法不被覆盖
        System.out.println(proxy.hashCode());//hashCode被代理覆写,被替换成Object类的hashCode
    }

结果如下:

Fuck CGLIB!!
java.lang.Object@5d6f64b1
class net.sf.cglib.samples2.SampleClass$$EnhancerByCGLIB$$8701c6cc
heheda
849460928

2.3 使用Enhancer + MethodInterceptor

@Test
    public void testMethodInterceptor() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new MethodInterceptor() {

            // obj是代理类对象(即被增强的对象),method是调用的方法,args是参数,proxy可以用来调用增强对象中未被intercept的方法
            public Object intercept(Object obj, Method method, Object[] args,
                    MethodProxy proxy) throws Throwable {
                if (method.getDeclaringClass() != Object.class
                        && method.getReturnType() == String.class) {
                    return "Fuck CGLIB!!";
                } else {
                    // invokeSuper调用被代理类的方法
                    return proxy.invokeSuper(obj, args);
                }
            }

        });
        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test(null));//test方法被代理覆写
        System.out.println(proxy.toString());//toString返回代理对象(就是proxy对象)的toString方法
        System.out.println(proxy.getClass()); //final方法不被覆写
        System.out.println(proxy.test2()); //final方法不被覆写
        System.out.println(proxy.hashCode()); //hashcode返回代理对象(就是proxy)的hashCode
    }

结果如下:

Fuck CGLIB!!
net.sf.cglib.samples2.SampleClass$$EnhancerByCGLIB$$f42508a4@78e03bb5
class net.sf.cglib.samples2.SampleClass$$EnhancerByCGLIB$$f42508a4
heheda
2027961269

2.4 使用Enhancer + CallbackFilter

@Test
    public void testCallbackFilter() throws Exception {
        Enhancer enhancer = new Enhancer();
        CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class,
                new Class[0]) {
            @Override
            protected Object getCallback(Method method) {
                if (method.getDeclaringClass() != Object.class
                        && method.getReturnType() == String.class) {
                    return new FixedValue() {
                        public Object loadObject() throws Exception {
                            return "Fuck CGLIB!!";
                        }
                    };
                } else {
                    return NoOp.INSTANCE; // A singleton provided by NoOp.
                }
            }
        };
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallbackFilter(callbackHelper);
        enhancer.setCallbacks(callbackHelper.getCallbacks());
        SampleClass proxy = (SampleClass) enhancer.create();
        System.out.println(proxy.test(null));
        System.out.println(proxy.toString());
        System.out.println(proxy.getClass());
        System.out.println(proxy.test2());
        System.out.println(proxy.hashCode());
    }

结果如下:

Fuck CGLIB!!
net.sf.cglib.samples2.SampleClass$$EnhancerByCGLIB$$dafb3c88@78e03bb5
class net.sf.cglib.samples2.SampleClass$$EnhancerByCGLIB$$dafb3c88
heheda
2027961269

3. 注意事项:

  1. 使用enhancer创建一个对象(enhancer.create())以后,它会为每个注册为Callback的interceptor创建私有域。
    这意味着cglib创建的类定义在类创建以后是无法重用的,因为callback的注册并不是在类的初始化阶段,而是cglib在类被
    JVM初始化后手动设置的。创建的私有域根据注册的interceptor的不同而有所区别。例如,注册MethodInterceptor 时,会创建两个静态私有域,一个持有
    反射的Method,一个持有MethodProxy。

  2. 注册interceptor时不要使用非静态的内部类,这是因为匿名内部类持有对代理类的强引用,导致内部类和代理类都无法被垃圾回收,从而可能引起堆溢出。
    不要试图代理Object的finalize方法。垃圾回收器会特殊对待finalize方法的代理类,将其放到JVM的finalization队列,造成垃圾回收异常。

  3. final方法不会被cglib代理,因此Object定义的wati, notify, notifyAll, getClass都不会被代理。虽然clone方法不是final的,但是也不要代理它。

  4. 注册了interceptor后,对代理类所有非final方法的调用都将dispatch给代理类的interceptor方法,因此如果返回值类型不同会造成java.lang.ClassCastException。

五、使用Java Proxy实现动态代理

使用java.lang.reflect.Proxy可以在运行期通过java反射生成接口的动态实现。

1. 基本步骤

  • 定义被代理类和代理类都要实现的接口
  • 实现代理类
  • 使用InvocationHandler实现interceptor(可能需要持有被代理类的对象)
  • 调用Proxy.newProxyInstance方法返回代理类对象,并赋值给接口 通过接口调用方法

2. 举例演示

接口定义如下:

public interface FuckInterface {
    public void fuckit();
}

接口实现如下:

public class FuckInterfaceImpl implements FuckInterface {
   

    @Override
    public void fuckit() {
        System.out.println("Fuck it!");
    }

}

interceptor实现如下:

public class MyInvocationHandler implements InvocationHandler {
   
    private FuckInterface inter;

    public MyInvocationHandler(FuckInterface f) {
        this.inter = f;
    }

    @Override
    //proxy为代理对象,method是调用的方法, args是参数
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("before Fuck");
        Object result = method.invoke(inter, args);
        System.out.println("after fuck");
        return result;
    }

}

在main方法中调用Proxy.newProxyInstance方法:

public class Main {

    public static void main(String[] args) {
        FuckInterface fuck1 = new FuckInterfaceImpl();
        InvocationHandler handler = new MyInvocationHandler(fuck1);
        FuckInterface proxyFuck1 = (FuckInterface) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[] { FuckInterface.class }, handler);
        proxyFuck1.fuckit();
    }

}

输出如下:

before Fuck
Fuck it!
after fuck

参考链接

https://github.com/cglib/cglib/wiki/Tutorial
http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
http://blog.csdn.net/wuliusir/article/details/32916419

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论