Spring——AOP浅析

    科技2023-10-27  109

    aop( aspect oriented programming ) 面向切面编程,是对所有对象或者是一类对象编程,核心是横向的对功能增强,aop实现原理是代理。

    使用场景

    多个模块代码重复——》抽出相同代码,各个模块显示调用——》aop可以“动态的”生成一个新的代理类,包含原方法和具有“横切”性质的方法,通过定义切入点而不用显示调用。 广泛应用于处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。

    分类

    静态代理和动态代理

    AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表;而动态代理则以 Spring AOP 为代表。

    静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB (Code Generation Library)等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。

    产生代理的方法

    Java 是一门静态的强类型语言, 代码一旦写好, 编译成java class 以后 ,可以在运行时通过反射(Reflection)来查看类的信息, 但是想对类进行修改非常困难。

    而AOP要求的恰恰就是在不改变业务类的源代码(其实大部分情况下你也拿不到)的情况下, 修改业务类的方法, 进行功能的增强,就像上面给所有的业务类增加事务支持, 现在基本是有这么几种技术:

    在编译的时候, 根据AOP的配置信息,悄悄的把日志,安全,事务等“切面”代码 和业务类编译到一起去。(Aspectj)jdk动态代理:在运行期,业务类加载以后, 通过Java动态代理技术为业务类生产一个代理类, 把“切面”代码放到代理类中, Java 动态代理要求业务类需要实现接口才行。字节码增强:在运行期, 业务类加载以后, 动态的使用字节码构建一个业务类的子类,将“切面”逻辑加入到子类当中去, CGLIB就是这么做的(ASM,javassist,cglib等技术)。

    字节码增强步骤:

    在内存中获取原始的字节码,然后通过一些开源项目(ASM,CGBLIb,javassist)等修改它的byte[]数组,得到一个新的byte[]数组。将这个新的byte[]数组写到PermGen区域,也就是加载它或者替换原来的Class字节码。

    Spring AOP 原理

    AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异:AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。

    纵观 AOP 编程,其中需要程序员参与的只有 3 个部分:

    定义普通业务组件。定义切入点,一个切入点可能横切多个业务组件。定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。

    代理对象的方法 = 增强处理 + 被代理对象的方法

    jdk动态代理

    如果目标对象的实现类有实现接口,则默认使用jdk动态代理,这个可以具体参考我的动态代理的文章

    CGLIB动态代理

    CGLIB(Code Generation Library),简单来说,就是一个代码生成类库。它可以在运行时候动态是生成某个类的子类。如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类, 代理的方法则通过在目标对象的切入点动态地织入增强处理,从而完成了对目标方法的增强。

    使用的设计模式

    代理模式 通过动态代理实现,具体见我的《代理模式》博文策略模式 生成代理对象时,可以使用 JDK 的动态代理和 Cglib 的动态代理,对于不同的需求可以委托给不同的类实现。

    常见问题

    jdk动态代理为什么要求实现接口

    因为jdk动态产生的代理类已经继承了proxy 类,由于 java 不支持多继承,所以代理类只能通过实现代理目标类的接口而获取接口方法

    aop什么时候会失效及解决方法

    失效场景

    在对象内部的方法中调用该对象的其他使用aop机制的方法,被调用方法的aop注解失效。

    原因

    我门知道当方法被代理时,其实是 动态生成了一个代理对象,代理对象去执行 invoke方法,在调用被代理对象的方法的时候执行了一些其他的动作。 所以当在被代理对象的方法中调用被代理对象的其他方法时。其实是没有用代理调用,是用了被代理对象本身调用的。

    解决方法

    方法一:注入自身的实例 自己注入自己,感觉有点象递归/死循环的搞法,但确实可以work,Spring在解决循环依赖上有自己的处理方式,避免了死循环。

    方法二:从Spring上下文获取增强后的实例引用 原理与方法一其实类似

    方法三:利用AopContext 不过这个方法要注意的是,主类入口上,必须加上exporseProxy=true,参考下图:

    spring中aop不生效的几种解决办法

    总结

    不管是那种 AOP 实现,不论是 AspectJ、还是 Spring AOP,它们都需要动态地生成一个 AOP 代理类,区别只是生成 AOP 代理类的时机不同:AspectJ 采用编译时生成 AOP 代理类,因此具有更好的性能,但需要使用特定的编译器进行处理;而 Spring AOP 则采用运行时生成 AOP 代理类,因此无需使用特定编译器进行处理。由于 Spring AOP 需要在每次运行时生成 AOP 代理,因此性能略差一些。

    参考资料

    Spring AOP 实现原理与 CGLIB 应用

    Processed: 0.026, SQL: 8