动态代理

之前在java中的反射 一文中讲到InvocationHandler时就涉及到的JDK的动态代理, 后来在Spring中使用AOP的时候, 发现有的方法拦截不到, 才意识到, Spring的AOP默认是使用JDK动态代理实现的, 必须实现接口才可以拦截到, 因此又写了这边文章



JDK动态代理使用示例

JDK的动态代理是依赖于接口实现的,因此要先定义接口,然后在定义接口的实现类

// Hello.java 定义接口
public interface Hello {
    void hello(String name);
    void sayHello(String name);
}
// HelloImpl.java 接口的实现类
public class HelloImpl implements Hello {
    @Override
    public void hello(String name) {
        sayHello(name);   // 注意这里调用了另一个方法
    }
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
    public void test() {  // 这个方法没有实现任何接口
        System.out.println("test");
    }

}

使用JDK的动态代理, 还需要一个InvocationHandler接口的实现类

// JDKProxy.java 定义接口
public class JDKProxy implements InvocationHandler {
    private Object target;  // 被代理对象
    public JDKProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before invoke");
        Object obj = method.invoke(target, args);
        System.out.println("after invoke");
        return obj;
    }
    // 获取通过JDK动态代理出来的对象
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

最后是测试类

// JDKProxyTest.java 定义接口
public class JDKProxyTest {
    public static void main(String[] args) throws Exception {
        HelloImpl hello = new HelloImpl();
        JDKProxy handler = new JDKProxy(hello);
        Hello helloProxy = (Hello) handler.getProxy();  // 无法把代理对象转换成 HelloImpl
        // 调用代理对象的方法, 打印出before和after
        helloProxy.sayHello("Tom");
        /**
         * 只会打印一次before和after
         * 虽然hello方法中调用了sayHello方法, 但调用的是被代理对象(target)的方法, 而不是代理对象(proxy)的方法
         * 所以写AOP拦截的时候要特别注意这里
         */
        helloProxy.hello("Tom");
//        proxy.test(); // proxy是无法调用test方法的, 因为接口中没有该方法, AOP拦截的时候也需要注意
    }
}

CGLib动态代理

JDK动态代理与CGLib动态代理均是实现Spring AOP的基础, CGLib使用的是字节码增强技术, 原理是创建被代理对象的一个子类, 并在子类中采用方法拦截的技术拦截所有父类方法的调用, 所以它不依赖于接口. 跟JDK动态代理对比, 被代理对象target在CGLib中相当于一个SuperClass, 而代理对象proxy相当于这个SuperClass的一个子类.

使用CGLib动态代理要用到的类有:MethodInterceptor, Enhancer, MethodProxy, 在之前Spring的AOP 中已经提到过, Spring3.2以后已经把cglib.jar包包含进了spring-core.jar. 所以这几个类可以直接使用Spring中提供的.

代理类可以这样写:

// CGLibProxy.java 定义接口
public class CGLibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    /**
     * @param clazz 被代理对象对应的Class
     * @return 代理对象 proxy
     */
    public Object getProxy(Class clazz) {
        // 设置需要被代理的类(被代理对象的类型)
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        // 通过字节码技术动态创建子类实例(代理对象)
        return enhancer.create();
    }
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        //通过代理类调用父类中的方法
        Object result = methodProxy.invokeSuper(target, args);
        System.out.println("after");
        return result;
    }
}

下面是测试类:

// CGLibProxyTest.java 定义接口
public class CGLibProxyTest {
    public static void main(String[] args) {
        CGLibProxy proxy = new CGLibProxy();
        HelloImpl helloProxy = (HelloImpl) proxy.getProxy(HelloImpl.class);
        // 调用代理对象的方法, 打印出before和after
        helloProxy.sayHello("Tom");
        /**
         * 打印两次before和after
         * hello方法中调用了sayHello方法, 两个方法都会被拦截到而执行代理对象的方法
         */
        helloProxy.hello("Tom");
        // 调用代理对象的方法, 打印出before和after
        helloProxy.test();
    }
}

JDK的动态代理是通过接口实现机制, CGLib的动态代理是利用字节码增强技术通过父类方法拦截机制. CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高, 但是CGLib在创建代理对象时所花费的时间却比JDK多得多, 所以对于单例的对象, 无需频繁创建对象, 用CGLib合适, 反之, 使用JDK方式要更为合适一些. 同时, 由于CGLib由于是采用动态创建子类的方法, 对于final方法, 无法进行代理.