之前在java中的反射 一文中讲到InvocationHandler
时就涉及到的JDK的动态代理, 后来在Spring中使用AOP的时候, 发现有的方法拦截不到, 才意识到, Spring的AOP默认是使用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拦截的时候也需要注意
}
}
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
方法, 无法进行代理.