浅谈 Spring AOP 原理
AOP 底层是采用 动态代理 机制实现的:接口+实现类
- 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用
JDK Proxy
,去创建代理对象。 - 没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用
Cglib
生成一个被代理对象的子类来作为代理。
就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这样就不需要修改源代码。
AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !
应用场景:
- 记录日志
- 监控方法运行时间(监控性能)
- 权限控制
- 缓存优化
- 事务管理
JDK 动态代理
JDK中的动态代理是通过反射类Proxy
以及InvocationHandler
回调接口实现的,但是JDK中所有要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中有一定的局限性,而且使用反射的效率也不高。
背景:
动态代理是为了解决静态代理的缺点:
- 当目标类增加了,代理类可能也需要成倍的增加,代理类数量过多。
- 当你的接口中功能增加了,或者修改了,会影响众多的实现类,厂家类,代理都需要修改,影响比较多。
JDK 动态代理中目标类即使很多:代理类数量可以很少,当你修改了接口中的方法时,不会影响代理类。
原理:
利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。
JDK 动态代理为什么必须实现接口?
- 在需要继承proxy类获得有关方法和InvocationHandler构造方法传参的同时, java不能同时继承两个类,我们需要和想要代理的类建立联系,只能实现一个接口。
- 需要反射获得代理类的有关参数,必须要通过某个类,反射获取有关方法。
- 成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个代理类。
- 对具体实现的方法内部并不关心,这个交给InvocationHandler.invoke那个方法里去处理就好了,我只想根据你给我的接口反射出对我有用的东西。
- 考虑到设计模式,以及proxy编者编写代码的逻辑使然。
CGLib 动态代理
CGLib 是一个功能强大,高性能的代码生成包。 它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。 通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
背景:
CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?
答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是, 只能对接口进行代理。 如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。
原理:
动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
缺点:
对于final方法,无法进行代理。
应用场景:
- 回调过滤器CallbackFilter:在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
- 延迟加载对象
- 接口生成器 InterfaceMaker:InterfaceMaker会动态生成一个接口,该接口包含指定类定义的所有方法。
JDK 动态代理 VS CGLib 动态代理
区别:
- Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- Cglib动态代理:利用 ASM 框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理。
怎么选择?
- 目标对象生成了接口 默认用JDK动态代理
- 如果目标对象使用了接口,可以强制使用cglib
- 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
Spring如何选择是用JDK还是cglib?
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)
评论