动态代理和AOP编程
动态代理
代理模式是设计模式的一种,不展开说了,比较基础。这里介绍一下Java的动态代理。
动态代理的两种方式
基于接口的JDK动态代理
JDK动态代理是通过Java的反射机制实现的。它要求目标对象实现一个InvocationHandler
接口,然后通过java.lang.reflect.Proxy
类创建代理对象。代理对象实现了目标接口,并将方法调用委托给一个InvocationHandler
接口的实现类。通过在InvocationHandler
的实现类中重写invoke()
方法,可以在方法调用前后添加额外的逻辑。
基于类的CGLIB动态代理
CGLIB动态代理不要求目标对象实现接口,而是通过字节码技术生成目标对象的子类作为代理对象。CGLIB(Code Generation Library)使用ASM框架直接操作字节码,在运行时生成代理类。通过继承目标类并重写方法,CGLIB代理对象可以拦截方法调用,并在方法调用前后插入自定义逻辑。
以下是两种代理方式的实例代码:
1 | public class LogProxy { |
动态代理的底层原理
两种都是程序运行期间生成动态代理类的字节码,加载类并创建对象。只不过一个是实现了接口的类,一个是子类。
两者的不同
有以下几点:
- JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
- Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新,Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
- JDK Proxy 是通过拦截器加反射的方式实现的;
- JDK Proxy 只能代理实现接口的类;
- JDK Proxy 实现和调用起来比较简单;
- CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
- CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。
主要还是在设计方面和性能方面:
Proxy 是面向接口的,所有使用 Proxy 的对象都必须定义一个接口,而且用这些对象的代码也必须是对接口编程的,Proxy 生成的对象是接口一致的而不是对象一致的。这对于软件架构设计,尤其对于既有软件系统是有一定掣肘的。
Proxy 毕竟是通过反射实现的,必须在效率上付出代价:有实验数据表明,调用反射比一般的函数开销至少要大 10 倍。而且,从程序实现上可以看出,对 proxy class 的所有方法调用都要通过使用反射的 invoke 方法。因此,对于性能关键的应用,使用 proxy class 是需要精心考虑的,以避免反射成为整个应用的瓶颈。
ASM 能够通过改造既有类,直接生成需要的代码。增强的代码是硬编码在新生成的类文件内部的,没有反射带来性能上的付出。同时,ASM 与 Proxy 编程不同,不需要为增强代码而新定义一个接口,生成的代码可以覆盖原来的类,或者是原始类的子类。它是一个普通的 Java 类而不是 proxy 类,甚至可以在应用程序的类框架中拥有自己的位置,派生自己的子类。
相比于其他流行的 Java 字节码操纵工具,ASM 更小更快。ASM 具有类似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分别有 350k 和 150k。同时,同样类转换的负载,如果 ASM 是 60% 的话,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。
ASM 已经被广泛应用于一系列 Java 项目,如Hibernate 和 Spring 也通过cglib,更高层一些的自动代码生成工具也使用了 ASM。
聊聊 Spring AOP原理
SpringBoot在很多地方都用到了AOP技术,如日志记录、全局异常处理、事务、缓存、性能监控、参数校验、权限校验等。
AOP 思想
基于动态代理思想,对原来目标对象创建代理对象,在不修改原对象代码情况下,通过代理对象调用增强功能的代码,从而对原有业务方法进行增强。
AOP 作用
在不修改源代码的情况下,可以增加额外的功能,实现在原有功能基础上的增强。
AOP 使用场景
- 记录日志(调用方法后记录日志)
- 监控性能(统计方法运行时间)
- 权限控制(调用方法前校验是否有权限)
- 事务管理(调用方法前开启事务,调用方法后提交关闭事务 )
- 缓存优化(第一次调用查询数据库,将查询结果放入内存对象, 第二次调用,直接从内存对象返回,不需要查询数据库 )
AOP 实现原理
Spring AOP 的有两种实现方式:JDK proxy 和 CGLib 动态代理。
当 Bean 实现接口时,Spring 使用 JDK proxy实现。当 Bean 没有实现接口时,Spring 使用 CGlib 代理实现。
通过配置可以强制使用 CGlib 代理(在 spring 配置中加入 aop:aspectj-autoproxy proxy-target-class=“true”)。