谈起AOP就不得不说起代理,Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,连接,初始化成 Java 类型,可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机
静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在编译时已经将接口,被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了
动态代理:在程序运行后通过反射创建生成字节码再由 JVM 加载而成
AOP通常叫面向切面编程(Aspect-oriented Programming,简称AOP),它是一种编程范式,通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术,通常用来对隔离不同业务逻辑,比如常见的事务管理、日志管理等
AOP核心概念
静态代理的实现代码在这里做不做赘述,简单来讲就是委托类和代理类实现同一个接口,代理类用于委托类的增强
由于动态代理是程序运行后才生成的,哪个委托类需要被代理到,只要生成动态代理即可,避免了静态代理那样的硬编码,另外所有委托类实现接口的方法都会在 Proxy 的 InvocationHandler.invoke() 中执行,这样如果要统计所有方法执行时间这样相同的逻辑,可以统一在 InvocationHandler 里写, 也就避免了静态代理那样需要在所有的方法中插入同样代码的问题,代码的可维护性极大的提高了
原理:
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h);
实现:
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("卖房");
}
}
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始");
method.invoke(target, args);
System.out.println("结束");
return proxy;
}
});
}
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
System.out.println(realSubject.getClass());
Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance();
System.out.println(subject.getClass());
subject.request();
}
}
结果输出:
代理类的 class 为 com.sun.proxy.$Proxy0,Proxy 是在 java.lang.reflect 反射包下的,Proxy 的 newProxyInstance 签名
由于动态代理是程序运行后才生成的,哪个委托类需要被代理到,只要生成动态代理即可,避免了静态代理那样的硬编码,另外所有委托类实现接口的方法都会在 Proxy 的 InvocationHandler.invoke() 中执行,这样如果要统计所有方法执行时间这样相同的逻辑,可以统一在 InvocationHandler 里写, 也就避免了静态代理那样需要在所有的方法中插入同样代码的问题,代码的可维护性极大的提高了
既然JDK动态代理听起来没问题为什么Spring AOP要使用CGLIB 动态代理呢?
JDK 动态代理虽好,但也有弱点,我们注意到 newProxyInstance 的方法签名,注意第二个参数 Interfaces 是委托类的接口,是必传的, JDK 动态代理是通过与委托类实现同样的接口,然后在实现的接口方法里进行增强来实现的,这就意味着如果要用 JDK 代理,委托类必须实现接口,这样的实现方式看起来有点蠢,更好的方式是什么呢,直接继承自委托类不就行了(superClass),这样委托类的逻辑不需要做任何改动,CGlib 就是这么做的
原理:
开头我们提到的 AOP 就是用的 CGLib 的形式来生成的,JDK 动态代理使用 Proxy 来创建代理类,增强逻辑写在 InvocationHandler.invoke() 里,CGlib 动态代理也提供了类似的 Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,也就是说所有委托类的非 final 方法都会被方法拦截器拦截
实现:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("目标类增强前");
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("目标增强后");
return object;
}
public static void main(String[] args) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(RealSubject.class);
//设置回调函数
enhancer.setCallback(new MyMethodInterceptor());
//这里的creat方法就是正式创建代理类
RealSubject proxyDog = (RealSubject) enhancer.create();
System.out.println(proxyDog.getClass());
//调用代理类的eat方法
proxyDog.request();
}
}
结果输出:
它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,所以 Spring AOP 最终使用了 CGlib 来生成动态代理,只能代理委托类中任意的非 final 的方法,另外它是通过继承自委托类来生成代理的,所以如果委托类是 final 的,就无法被代理了(final 类不能被继承)
总结:
JDK 动态代理的拦截对象是通过反射的机制来调用被拦截方法的
CGlib动态代理通过什么机制来提升了方法的调用效率:由于反射的效率比较低,所以 CGlib 采用了FastClass 的机制来实现对被拦截方法的调用。FastClass 机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法