0
点赞
收藏
分享

微信扫一扫

Spring AOP理论总结

星巢文化 2022-03-11 阅读 73

目录

AOP简介

为什么要有AOP?

AOP的优点

AOP的应用场景

AOP的组件及应用

Spring AOP

Spring AOP 和 AspectJ AOP 有什么区别?

Java Proxy和Cglib 的区别

如何使用Spring AOP

AOP的基本实现原理

例子

代理模式

静态代理

动态代理

CGLib 动态代理

cglib 代理类的生成规则

CGlib的Fastclass 机制分析

AOP失效问题

失效样例

失效原因

解决方法


注意:本文参考

漫画:AOP 面试造火箭事件始末

cglib源码分析(四):cglib 动态代理原理分析 - cruze_lee - 博客园

docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md · SnailClimb/JavaGuide - Gitee.com

Spring AOP 是怎么运行的?彻底搞定这道面试必考题 - 云+社区 - 腾讯云

给冰冰看的SpringAOP面试题

Spring AOP的原理、使用场景、好处 - 简书

AOP失效的原因,及解决办法 - 丶Ronnie - 博客园

AOP简介

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

AOP通常叫面向切面编程(Aspect-oriented Programming,简称AOP),它是一种编程范式,通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术。

它要达到的效果是保证开发者在不修改源代码的前提下,为系统中不同的业务组件添加某些通用功能。

通常用来对隔离不同业务逻辑,比如常见的事务管理、日志管理等。

例如

比如上面这个例子,三个 service 对象执行过程中都存在安全,事务,缓存,性能等相同行为,这些相同的行为显然应该在同一个地方管理,有人说我可以写一个统一的工具类,在这些对象的方法前/后都嵌入此工具类,那问题来了,这些行为都属于业务无关的,使用工具类嵌入的方式导致与业务代码紧藕合,很不合工程规范,代码可维护性极差!切面就是为了解决此类问题应运而生的,能做到相同功能的统一管理,对业务代码无侵入

以性能为例,这些对象负责的模块存在哪些相似的功能呢

比如说吧,每个 service 都有不同的方法,我想统计每个方法的执行时间,如果不用切面你需要在每个方法的首尾计算下时间,然后相减

如果我要统计每一个 service 中每个方法的执行时间可想而知不用切面的话就得在每个方法的首尾都加上类似上述的逻辑,显然这样的代码可维护性是非常差的,这还只是统计时间,如果此方法又要加上事务,风控等,是不是也得在方法首尾加上事务开始,回滚等代码,可想而知业务代码与非业务代码严重藕合,这样的实现方式对工程是一种灾难,是不能接受的! 

为什么要有AOP?

假设现在有几个实现方法,需要做日志处理,正常来说我们只需要手动添加一下日志就可以了,我们都知道在真正的业务代码中,代码行数,以及方法数那是一个天文数字,如果都要手动添加那工作量不现实。

本着作为程序员因该想着怎么合理的偷懒的习惯,所以应该想办法提高效率。

AOP因此就产生了,说白了AOP就是通过某种匹配规则去匹配方法,然后再添加对应的日志处理。而AOP本身的实现方式就是通过ASM字节码框架动态生成技术,在程序运行的时候,根据需求(添加文件)动态创建字节码文件,之前讲的设计模式中动态代理模式中也有讲到,大家可以再去复习一下。

AOP的优点

切面的内容可以复用,比如TimeHandler的printTime方法,任何地方需要打印方法执行前的时间与方法执行后的时间,都可以使用TimeHandler的printTime方法。将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

避免使用Proxy、CGLIB生成代理,这方面的工作全部框架去实现,开发者可以专注于切面内容本身

代码与代码之间没有耦合,如果拦截的方法有变化修改配置文件即可

AOP的应用场景

AOP用来封装横切关注点,具体可以在下面的场景中使用:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

AOP的组件及应用

JoinPoint

程序在执行流程中经过的一个个时间点,这个时间点可以是方法调用时,或者是执行方法中异常抛出时,也可以是属性被修改时等时机,在这些时间点上你的切面代码是可以(注意是可以但未必)被注入的

Pointcut

JoinPoints 只是切面代码可以被织入的地方,但我并不想对所有的 JoinPoint 进行织入,这就需要某些条件来筛选出那些需要被织入的 JoinPoint,Pointcut 就是通过一组规则(使用 AspectJ pointcut expression language 来描述) 来定位到匹配的 joinpoint

关于Joinpoint 和 point cut 的区别

在 Spring AOP 中, 所有的方法执行都是 join point. 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 因此 join point 和 point cut 本质上就是两个不同纬度上的东西.advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice。

Advice

代码织入(也叫增强),Pointcut 通过其规则指定了哪些 joinpoint 可以被织入,而 Advice 则指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方,主要有五个织入时机

1 Before Advice: 在 JoinPoints 执行前织入

2 After Advice: 在 JoinPoints 执行后织入(不管是否抛出异常都会织入)

3 After returning advice: 在 JoinPoints 执行正常退出后织入(抛出异常则不会被织入)

4 After throwing advice: 方法执行过程中抛出异常后织入

5 Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可织入切面代码,也可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至抛出异常。在这些 advice 里我们就可以写入切面代码了 综上所述,切面(Aspect)我们可以认为就是 pointcut 和 advice,pointcut 指定了哪些 joinpoint 可以被织入,而 advice 则指定了在这些 joinpoint 上的代码织入时机与逻辑

Aspect(切面)

aspect 由 pointcount 和 advice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中. AOP的工作重心在于如何将增强织入目标对象的连接点上, 这里包含两个工作:

1. 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上。

2. 如何在 advice 中编写切面代码。

一般使用 @Aspect 注解一个类或者 <aop:aspect> 来定义一个切面。

introduction

为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现。

目标对象(Target)

织入 advice 的目标对象. 目标对象也被称为 advised object.因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类。

AOP proxy

一个类被 AOP 织入 advice, 就会产生一个结果类, 它是融合了原类和增强逻辑的代理类. 在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象。

织入(Weaving)

将 aspect 和其他对象连接起来, 并创建 adviced object 的过程. 根据不同的实现技术, AOP织入有三种方式:

编译器织入, 这要求有特殊的Java编译器。

类装载期织入, 这需要有特殊的类装载器。

动态代理织入, 在运行期为目标类添加增强(Advice)生成子类的方式. Spring 采用动态代理织入, 而AspectJ采用编译器织入和类装载期织入。

请用你奶奶都能听得懂的语言来解释一下这些概念!

来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系. 首先我们知道, 在 Spring AOP 中 join point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, join point 就相当于 爪哇的小县城里的百姓 , point cut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸 , 而 advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问 . 为什么可以这样类比呢?

join point --> 爪哇的小县城里的百姓: 因为根据定义, join point 是所有可能被织入 advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 join point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人。

point cut --> 男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 advice, 但是我们并不希望在所有方法上都织入 advice, 而 pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问。

advice --> 抓过来审问, advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 join point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓。

aspect: aspect 是 point cut 与 advice 的组合, 因此在这里我们就可以类比: "根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问" 这一整个动作可以被认为是一个 aspect。

或则我们也可以从语法的角度来简单类比一下. 我们在学英语时, 经常会接触什么 定语, 被动句 之类的概念, 那么可以做一个不严谨的类比, 即 joinpoint 可以认为是一个 宾语, 而 pointcut 则可以类比为修饰 joinpoint 的定语, 那么整个 aspect 就可以描述为: 满足 pointcut 规则的 joinpoint 会被添加相应的 advice 操作。

这也难不倒我,比如在餐馆里点菜,菜单有 10 个菜,这 10 个菜就是 JoinPoint,但我只点了带有萝卜名字的菜,那么带有萝卜名字这个条件就是针对 JoinPoint(10 个菜)的筛选条件,即 pointcut,最终只有胡萝卜,白萝卜这两个 JoinPoint 满足条件,然后我们就可以在吃胡萝卜前洗手(before advice),或吃胡萝卜后买单(after advice),也可以统计吃胡萝卜的时间(around advice),这些洗手,买单,统计时间的动作都是与吃萝卜这个业务动作解藕的,都是统一写在 advice 的逻辑里

 public interface TestService {
   // 吃萝卜
   void eatCarrot();

   // 吃蘑菇
   void eatMushroom();

   // 吃白菜
   void eatCabbage();
}
@Component
public class TestServiceImpl implements TestService {
   @Override
   public void eatCarrot() {
       System.out.println("吃萝卜");
   }

   @Override
   public void eatMushroom() {
       System.out.println("吃蘑菇");
   }

   @Override
   public void eatCabbage() {
       System.out.println("吃白菜");
   }
}

假设有以上 TestService, 实现了吃萝卜,吃蘑菇,吃白菜三个方法,这三个方法都用切面织入,所以它们都是 joinpoints,但现在我只想对吃萝卜这个 joinpoints 前后织入 advice,该怎么办呢,首先当然要声明 pointcut 表达式,这个表达式表明只想织入吃萝卜这个 joinpoint,指明了之后再让 advice 应用于此 pointcut 不就完了,比如我想在吃萝卜前洗手,吃萝卜后买单,可以写出如下切面逻辑

@Aspect
@Component
public class TestAdvice {
   // 1. 定义 PointCut
   @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())")
   private void eatCarrot(){}

   // 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中织入增强逻辑
   @Around("eatCarrot()")
   public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {
       System.out.println("吃萝卜前洗手");
       //  原来的 TestServiceImpl.eatCarrot 逻辑,可视情况决定是否执行
       point.proceed();
       System.out.println("吃萝后买单");
   }
}

可以看到通过 AOP 我们巧妙地在方法执行前后执行插入相关的逻辑,对原有执行逻辑无任何侵入!

这是一个困扰我司由来已久的难题,Dubbo 了解过吧,对外提供的服务可能有多个方法,一般我们为了不给调用方埋坑,会在每个方法里把所有异常都 catch 住,只返回一个 result,调用方会根据这个 result 里的 success 判断此次调用是否成功,举个例子

public class ServiceResultTO<T> extends Serializable {
   private static final long serialVersionUID = xxx;
   private Boolean success;
   private String message;
   private T data;
}

public interface TestService {
   ServiceResultTO<Boolean> test();
}

public class TestServiceImpl implements TestService {
   @Override
   public ServiceResultTO<Boolean> test() {
       try {
           // 此处写服务里的执行逻辑
           return ServiceResultTO.buildSuccess(Boolean.TRUE);
       } catch(Exception e) {
         return ServiceResultTO.buildFailed(Boolean.FALSE, "执行失败");            
       }
   }
}

比如现在以上这样的 dubbo 服务(TestService),它有一个 test 方法,为了执行正常逻辑时出现异常,我们在此方法执行逻辑外包了一层「try... catch...」如果只有一个 test 方法,这样做当然没问题,但问题是在工程里我们一般要要提供几十上百个 service,每个 service 有几十个像 test 这样的方法,如果每个方法都要在执行的时候包一层 「try ...catch...」,虽然可行,但代码会比较丑陋,可读性也比较差,你能想想办法改进一下吗?

这就要说到 PointCut 的 AspectJ pointcut expression language 声明式表达式,这个表达式支持的类型比较全面,可以用正则,注解等来指定满足条件的 joinpoint , 比如类名后加 .*(..) 这样的正则表达式就代表这个类里面的所有方法都会被织入,使用 @annotation 的方式也可以指定对标有这类注解的方法织入代码

首先我们先定义一个如下注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {

}

然后将所有 service 中方法里的 「try... catch...」移除掉,在方法签名上加上上述我们定义好的注解

public class TestServiceImpl implements TestService {
   @Override
   @GlobalErrorCatch
   public ServiceResultTO<Boolean> test() {
        // 此处写服务里的执行逻辑
        boolean result = xxx;
        return ServiceResultTO.buildSuccess(result);
   }
}

然后再指定注解形式的 pointcuts 及 around advice

@Aspect
@Component
public class TestAdvice {
   // 1. 定义所有带有 GlobalErrorCatch 的注解的方法为 Pointcut
   @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)")
   private void globalCatch(){}
   // 2. 将 around advice 作用于 globalCatch(){} 此 PointCut 
   @Around("globalCatch()")
   public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable {
       try {
           return point.proceed();
       } catch (Exception e) {
           System.out.println("执行错误" + e);
           return ServiceResultTO.buildFailed("系统错误");
       }
   }

}

通过这样的方式,所有标记着 GlobalErrorCatch 注解的方法都会统一在 handlerGlobalResult 方法里执行,我们就可以在这个方法里统一 catch 住异常,所有 service 方法中又长又臭的 「try...catch...」全部干掉,真香!

Spring AOP

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

Java Proxy和Cglib 的区别

原理区别

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

默认实现方式

1 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 

2 如果目标对象实现了接口,可以强制使用CGLIB实现AOP 

3 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP?

1 添加CGLIB库,SPRING_HOME/cglib/*.jar

2 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

JDK动态代理和CGLIB字节码生成的区别?

1 JDK动态代理只能对实现了接口的类生成代理,而不能针对类

2 CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final 

性能

关于两者之间的性能的话,JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:

1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了

如何使用Spring AOP

可以通过配置文件或者编程的方式来使用Spring AOP。

配置可以通过xml文件来进行,大概有四种方式:

1.配置ProxyFactoryBean,显式地设置advisors, advice, target等

2.        配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象

3.        通过<aop:config>来配置

4.        通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点

也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象

AOP的基本实现原理

例子

TestServiceImp 这个 bean 所属的类

@Component
public class TestServiceImpl implements TestService {
   @Override
   public void eatCarrot() {
       System.out.println("吃萝卜");
   }
}
@Aspect
@Component
public class TestAdvice {
   // 1. 定义 PointCut
   @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())")
   private void eatCarrot(){}

   // 2. 定义应用于 PointCut 的 advice, 这里我们使用 around advice
   @Around("eatCarrot()")
   public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {
        // 省略相关逻辑
   }
}
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
   public static void main(String[] args) {
       ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
       TestService testService = context.getBean(TestService.class);
       System.out.println("testService = " + testService.getClass());
   }
}

打印后我果然发现了端倪,这个 bean 的 class 居然不是 TestServiceImpl!而是com.example.demo.impl.TestServiceImplEnhancerBySpringCGLIB$$705c68c7! 

我们注意到类名中有一个 EnhancerBySpringCGLIB ,注意 CGLiB,这个类就是通过它生成的动态代理

代理模式

先谈谈啥是代理吧

代理在生活中随处可见,比如说我要买房,我一般不会直接和卖家对接,一般会和中介打交道,中介就是代理,卖家就是目标对象,我就是调用者,代理不仅实现了目标对象的行为(帮目标对象卖房),还可以添加上自己的动作(收保证金,签合同等),

 

Client 是直接和 Proxy 打交道的,Proxy 是 Client 要真正调用的 RealSubject 的代理,它确实执行了 RealSubject 的 request 方法,不过在这个执行前后 Proxy 也加上了额外的 PreRequest(),afterRequest() 方法,注意 Proxy 和 RealSubject 都实现了 Subject 这个接口,这样在 Client 看起来调用谁是没有什么分别的(面向接口编程,对调用方无感,因为实现的接口方法是一样的),Proxy 通过其属性持有真正要代理的目标对象(RealSubject)以达到既能调用目标对象的方法也能在方法前后注入其它逻辑的目的 

代理的类型,代理主要分为两种类型:静态代理和动态代理,动态代理又有 JDK 代理和 CGLib 代理两种,我先解释下静态和动态的含义

要理解静态和动态这两个含义,我们首先需要理解一下 Java 程序的运行机制

首先 Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,连接,初始化成 Java 类型,可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在编译时已经将接口,被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了动态代理:在程序运行后通过反射创建生成字节码再由 JVM 加载而成 

静态代理

public interface Subject {
   public void request();
}

public class RealSubject implements Subject {
   @Override
   public void request() {
       // 卖房
       System.out.println("卖房");
   }
}

public class Proxy implements Subject {

   private RealSubject realSubject;

   public Proxy(RealSubject subject) {
       this.realSubject = subject;
   }


   @Override
   public void request() {
    // 执行代理逻辑
       System.out.println("卖房前");

       // 执行目标对象方法
       realSubject.request();

       // 执行代理逻辑
       System.out.println("卖房后");
   }

   public static void main(String[] args) {
       // 被代理对象
       RealSubject subject = new RealSubject();

       // 代理
       Proxy proxy = new Proxy(subject);

       // 代理请求
       proxy.request();
   }
}

静态代理主要有两大劣势

代理类只代理一个委托类(其实可以代理多个,但不符合单一职责原则),也就意味着如果要代理多个委托类,就要写多个代理(别忘了静态代理在编译前必须确定)

第一点还不是致命的,再考虑这样一种场景:如果每个委托类的每个方法都要被织入同样的逻辑,比如说我要计算前文提到的每个委托类每个方法的耗时,就要在方法开始前,开始后分别织入计算时间的代码,那就算用代理类,它的方法也有无数这种重复的计算时间的代码

该怎么改进,就要提到动态代理了,静态代理的这些劣势主要是是因为在编译前这些代理类是确定的,如果这些代理类是动态生成的呢,是不是可以省略一大堆代理的代码。

动态代理

动态代理分为 JDK 提供的动态代理和 Spring AOP 用到的 CGLib 生成的代理,我们先看下 JDK 提供的动态代理该怎么写

// 委托类
public class RealSubject implements Subject {
   @Override
   public void request() {
       // 卖房
       System.out.println("卖房");
   }
}


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

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 null;
                   }
               });
   }

   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();
   }
}```
打印结果如下:
```shell
原始类:class com.example.demo.proxy.staticproxy.RealSubject
代理类:class com.sun.proxy.$Proxy0
计算开始时间
卖房
计算结束时间

我们注意到代理类的 class 为 com.sun.proxy.$Proxy0,它是如何生成的呢,注意到 Proxy 是在 java.lang.reflect 反射包下的,注意看看 Proxy 的 newProxyInstance 签名

1 loader: 代理类的ClassLoader,最终读取动态生成的字节码,并转成 java.lang.Class 类的一个实例(即类),通过此实例的 newInstance() 方法就可以创建出代理的对象

2 interfaces: 委托类实现的接口,JDK 动态代理要实现所有的委托类的接口

3 InvocationHandler: 委托对象所有接口方法调用都会转发到 InvocationHandler.invoke(),在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的

这样的实现有啥好处呢

由于动态代理是程序运行后才生成的,哪个委托类需要被代理到,只要生成动态代理即可,避免了静态代理那样的硬编码,另外所有委托类实现接口的方法都会在 Proxy 的 InvocationHandler.invoke() 中执行,这样如果要统计所有方法执行时间这样相同的逻辑,可以统一在 InvocationHandler 里写, 也就避免了静态代理那样需要在所有的方法中插入同样代码的问题,代码的可维护性极大的提高了。

那么 Spring AOP 的实现为啥却不用它呢

JDK 动态代理虽好,但也有弱点,我们注意到 newProxyInstance 的方法签名

注意第二个参数 Interfaces 是委托类的接口,是必传的, JDK 动态代理是通过与委托类实现同样的接口,然后在实现的接口方法里进行增强来实现的,这就意味着如果要用 JDK 代理,委托类必须实现接口,这样的实现方式看起来有点蠢,更好的方式是什么呢,直接继承自委托类不就行了,这样委托类的逻辑不需要做任何改动,CGlib 就是这么做的

CGLib 动态代理

好嘞,开头我们提到的 AOP 就是用的 CGLib 的形式来生成的,JDK 动态代理使用 Proxy 来创建代理类,增强逻辑写在 InvocationHandler.invoke() 里,CGlib 动态代理也提供了类似的  Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,也就是说所有委托类的非 final 方法都会被方法拦截器拦截,在说它的原理之前首先来看看它怎么用的

public class MyMethodInterceptor implements MethodInterceptor {
   @Override
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       System.out.println("目标类增强前!!!");
       //注意这里的方法调用,不是用反射哦!!!
       Object object = proxy.invokeSuper(obj, args);
       System.out.println("目标类增强后!!!");
       return object;
   }
}

public class CGlibProxy {
   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();
       //调用代理类的eat方法
       proxyDog.request();
   }
}
打印如下

代理类:class com.example.demo.proxy.staticproxy.RealSubject$$EnhancerByCGLIB$$889898c5
目标类增强前!!!
卖房
目标类增强后!!!

1 创建Enhancer实例

2 通过setSuperclass方法来设置目标类

3 通过setCallback 方法来设置拦截对象

4 create方法生成Target的代理类,并返回代理类的实例

可以看到主要就是利用 Enhancer 这个类来设置委托类与方法拦截器,这样委托类的所有非 final 方法就能被方法拦截器拦截,从而在拦截器里实现增强

底层实现原理是啥

之前也说了它是通过继承自委托类,重写委托类的非 final 方法(final 方法不能重载),并在方法里调用委托类的方法来实现代码增强的,它的实现大概是这样

public class RealSubject {
   @Override
   public void request() {
       // 卖房
       System.out.println("卖房");
   }
}

/** 生成的动态代理类(简化版)**/
public class RealSubject$$EnhancerByCGLIB$$889898c5 extends RealSubject {
   @Override
   public void request() {
       System.out.println("增强前");
       super.request();
       System.out.println("增强后");
   }
}

可以看到它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,所以 Spring AOP 最终使用了 CGlib 来生成动态代理

CGlib 动态代理使用上有啥限制吗

第一点之前已经已经说了,只能代理委托类中任意的非 final 的方法,另外它是通过继承自委托类来生成代理的,所以如果委托类是 final 的,就无法被代理了(final 类不能被继承)

cglib 代理类的生成规则

我们通过打印类名的方式知道了 cglib 生成了 RealSubjectEnhancerByCGLIB$$889898c5 这样的动态代理,那么有反编译过它的 class 文件来了解 cglib 代理类的生成规则吗

在示例代码中我们通过设置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY的属性值来获取cglib生成的代理类。通过之前分析的命名规则我们可以很容易的在F:\\code下面找到生成的代理类 Target$$EnhancerByCGLIB$$788444a0.class 。

使用jd-gui进行反编译(由于版本的问题,此处只能显示部分代码,可以结合javap的反编译结果来进行分析),由于cglib会代理Object中的finalize,equals, toString,hashCode,clone方法,为了清晰的展示代理类我们省略这部分代码,反编译的结果如下:

 1 public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
 2 {
 3     private boolean CGLIB$BOUND;
 4     private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
 5     private static final Callback[] CGLIB$STATIC_CALLBACKS;
 6     private MethodInterceptor CGLIB$CALLBACK_0;
 7     private static final Method CGLIB$g$0$Method;
 8     private static final MethodProxy CGLIB$g$0$Proxy;
 9     private static final Object[] CGLIB$emptyArgs;
10     private static final Method CGLIB$f$1$Method;
11     private static final MethodProxy CGLIB$f$1$Proxy;
12     
13     static void CGLIB$STATICHOOK1()
14     {
15       CGLIB$THREAD_CALLBACKS = new ThreadLocal();
16       CGLIB$emptyArgs = new Object[0];
17       Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
18       Class localClass2;
19       Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "g", "()V", "f", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.Target")).getDeclaredMethods());
20       CGLIB$g$0$Method = tmp60_57[0];
21       CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
22       CGLIB$f$1$Method = tmp60_57[1];
23       CGLIB$f$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "f", "CGLIB$f$1");
25     }
26     
27     final void CGLIB$g$0()
28     {
29       super.g();
30     }
31     
32     public final void g()
33     {
34       MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
35       if (tmp4_1 == null)
36       {
37           CGLIB$BIND_CALLBACKS(this);
38           tmp4_1 = this.CGLIB$CALLBACK_0;
39       }
40       if (this.CGLIB$CALLBACK_0 != null) {
41           tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
42       }
43       else{
44           super.g();
45       }
46     }
47 }

代理类(Target$$EnhancerByCGLIB$$788444a0)继承了目标类(Target),至于代理类实现的factory接口与本文无关,残忍无视。代理类为每个目标类的方法生成两个方法,例如针对目标类中的每个非private方法,代理类会生成两个方法,以g方法为例:一个是@Override的g方法,一个是CGLIB$g$0(CGLIB$g$0相当于目标类的g方法)。我们在示例代码中调用目标类的方法t.g()时,实际上调用的是代理类中的g()方法。接下来我们着重分析代理类中的g方法,看看是怎么实现的代理功能。

当调用代理类的g方法时,先判断是否已经存在实现了MethodInterceptor接口的拦截对象,如果没有的话就调用CGLIB$BIND_CALLBACKS方法来获取拦截对象,CGLIB$BIND_CALLBACKS的反编译结果如下:

private static final void CGLIB$BIND_CALLBACKS(java.lang.Object);
  Code:
   0:   aload_0
   1:   checkcast       #2; //class net/sf/cglib/test/Target$$EnhancerByCGLIB$$788444a0
   4:   astore_1
   5:   aload_1
   6:   getfield        #212; //Field CGLIB$BOUND:Z
   9:   ifne    52
   12:  aload_1
   13:  iconst_1
   14:  putfield        #212; //Field CGLIB$BOUND:Z
   17:  getstatic       #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal;
   20:  invokevirtual   #215; //Method java/lang/ThreadLocal.get:()Ljava/lang/Object;
   23:  dup
   24:  ifnonnull       39
   27:  pop
   28:  getstatic       #210; //Field CGLIB$STATIC_CALLBACKS:[Lnet/sf/cglib/proxy/Callback;
   31:  dup
   32:  ifnonnull       39
   35:  pop
   36:  goto    52
   39:  checkcast       #216; //class "[Lnet/sf/cglib/proxy/Callback;"
   42:  aload_1
   43:  swap
   44:  iconst_0
   45:  aaload
   46:  checkcast       #48; //class net/sf/cglib/proxy/MethodInterceptor
   49:  putfield        #36; //Field CGLIB$CALLBACK_0:Lnet/sf/cglib/proxy/MethodInterceptor;
   52:  return

为了方便阅读,等价的代码如下:

private static final void CGLIB$BIND_CALLBACKS(Object o){
        Target$$EnhancerByCGLIB$$788444a0 temp_1 = (Target$$EnhancerByCGLIB$$788444a0)o;
        Object temp_2;
        Callback[] temp_3
        if(temp_1.CGLIB$BOUND == true){
            return;
        }
        temp_1.CGLIB$BOUND = true;
        temp_2 = CGLIB$THREAD_CALLBACKS.get();
        if(temp_2!=null){
            temp_3 = (Callback[])temp_2;
        }
        else if(CGLIB$STATIC_CALLBACKS!=null){
            temp_3 = CGLIB$STATIC_CALLBACKS;
        }
        else{
            return;
        }
        temp_1.CGLIB$CALLBACK_0 = (MethodInterceptor)temp_3[0];
        return;
    }

CGLIB$BIND_CALLBACKS 先从CGLIB$THREAD_CALLBACKS中get拦截对象,如果获取不到的话,再从CGLIB$STATIC_CALLBACKS来获取,如果也没有则认为该方法不需要代理。

那么拦截对象是如何设置到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?

在Jdk动态代理中拦截对象是在实例化代理类时由构造函数传入的,在cglib中是调用Enhancer的firstInstance方法来生成代理类实例并设置拦截对象的。firstInstance的调用轨迹为:

1 Enhancer:firstInstance

2 Enhancer:createUsingReflection

3 Enhancer:setThreadCallbacks

4 Enhancer:setCallbacksHelper

5 Target$$EnhancerByCGLIB$$788444a0 : CGLIB$SET_THREAD_CALLBACKS

 在第5步,调用了代理类的CGLIB$SET_THREAD_CALLBACKS来完成拦截对象的注入。下面我们看一下CGLIB$SET_THREAD_CALLBACKS的反编译结果:

public static void CGLIB$SET_THREAD_CALLBACKS(net.sf.cglib.proxy.Callback[]);
  Code:
   0:   getstatic       #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal;
   3:   aload_0
   4:   invokevirtual   #207; //Method java/lang/ThreadLocal.set:(Ljava/lang/Object;)V
   7:   return

在CGLIB$SET_THREAD_CALLBACKS方法中调用了CGLIB$THREAD_CALLBACKS的set方法来保存拦截对象,在CGLIB$BIND_CALLBACKS方法中使用了CGLIB$THREAD_CALLBACKS的get方法来获取拦截对象,并保存到CGLIB$CALLBACK_0中。这样,在我们调用代理类的g方法时,就可以获取到我们设置的拦截对象,然后通过  tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy)  来实现代理。这里来解释一下intercept方法的参数含义:

@para1 obj :代理对象本身

@para2 method : 被拦截的方法对象

@para3 args:方法调用入参

@para4 proxy:用于调用被拦截方法的方法代理对象

这里会有一个疑问,为什么不直接反射调用代理类生成的(CGLIB$g$0)来间接调用目标类的被拦截方法,而使用proxy的invokeSuper方法呢?这里就涉及到了另外一个点— FastClass 。

CGlib的Fastclass 机制分析

JDK 动态代理的拦截对象是通过反射的机制来调用被拦截方法的,CGlib 呢,它通过什么机制来提升了方法的调用效率。

由于反射的效率比较低,所以 CGlib 采用了FastClass 的机制来实现对被拦截方法的调用。FastClass 机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

public class test10 {
    public static void main(String[] args){
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{
    public void f(){
        System.out.println("f method");
    }
    
    public void g(){
        System.out.println("g method");
    }
}
class Test2{
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
    
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
}

上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke。在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。代理类(Target$$EnhancerByCGLIB$$788444a0)中与生成Fastclass相关的代码如下:

Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
localClass2 = Class.forName("net.sf.cglib.test.Target");
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");

MethodProxy中会对localClass1和localClass2进行分析并生成FastClass,然后再使用getIndex来获取方法g 和 CGLIB$g$0的索引,具体的生成过程将在后续进行介绍,这里介绍一个关键的内部类:

private static class FastClassInfo
    {
        FastClass f1; // net.sf.cglib.test.Target的fastclass
        FastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclass
        int i1; //方法g在f1中的索引
        int i2; //方法CGLIB$g$0在f2中的索引
    }

MethodProxy 中invokeSuper方法的代码如下:

    FastClassInfo fci = fastClassInfo;
    return fci.f2.invoke(fci.i2, obj, args);

当调用invokeSuper方法时,实际上是调用代理类的CGLIB$g$0方法,CGLIB$g$0直接调用了目标类的g方法。所以,在第一节示例代码中我们使用invokeSuper方法来调用被拦截的目标类方法。

AOP失效问题

失效样例

在bean中不要使用this来调用被@Async、@Transactional、@Cacheable等注解标注的方法,this下注解是不生效的。

以@Async注解为例,@Async注解标记的方法,在执行时会被AOP处理为异步调用,调用此方法处直接返回,@Async标注的方法使用其他线程执行。

@SpringBootApplication
@EnableAsync
public class Starter {
public static void main(String[] args) {
SpringApplication.run(Starter.class, args);
}
}
 
@Component
public class AsyncService {
public void async1() {
System.out.println('1:' + Thread.currentThread().getName());
this.async2();
}

@Async
public void async2() {
System.out.println('2:' + Thread.currentThread().getName());
}
} 

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Starter.class)
public class BaseTest {

@Autowired
AsyncService asyncService; 

@Test
public void testAsync() {
asyncService.async1();
asyncService.async2();
}
}

输出内容为:

1:main
2:main
2:SimpleAsyncTaskExecutor-2

第一行第二行对应async1()方法,第三行对应async2()方法,可以看到直接使用asyncService.async2()调用时使用的线程为SimpleAsyncTaskExecutor,

而在async1()方法中使用this调用,结果却是主线程,原调用线程一致。这说明@Async在this调用时没有生效。

失效原因

已知对于AOP动态代理,非接口的类使用的是基于CGLIB的动态代理,而CGLIB的动态代理,是基于现有类创建一个子类,并实例化子类对象。

在调用动态代理对象方法时,都是先调用子类方法,子类方法中使用方法增强Advice或者拦截器MethodInterceptor处理子类方法调用后,选择性的决定是否执行父类方法。

那么假设在调用async1方法时,使用的是动态生成的子类的实例,那么this其实是基于动态代理的子类实例对象,this调用是可以被Advice或者MethodInterceptor等处理逻辑拦截的,那么为何理论和实际不同呢?

其实async1方法中的this不是动态代理的子类对象,而是原始的对象,故this调用无法通过动态代理来增强。

 

一个基于CGLIB的AOP动态代理bean,真实的执行逻辑是在DynamicAdvisedInterceptor中:

	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
	{

		Object oldProxy = null;
		boolean setProxyContext = false;
		Class targetClass = null;
		Object target = null;

		try
		{
			if (this.advised.exposeProxy)
			{
				// 需要则暴露
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			// May be null. Get as late as possible to minimize the time we
			// 'own' the target, in case it comes from a pool...
			// 重点:获取被代理的目标对象
			target = getTarget();

			if (target != null)
			{
				targetClass = target.getClass();
			}

			// 获取拦截器链
			List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
			Object retVal;

			// Check whether we only have one InvokerInterceptor: that is,
			// no real advice, but just reflective invocation of the target.
			if (chain.isEmpty() && Modifier.isPublic(method.getModifiers()))
			{

				// We can skip creating a MethodInvocation: just invoke the
				// target directly.
				// Note that the final invoker must be an InvokerInterceptor, so
				// we know
				// it does nothing but a reflective operation on the target, and
				// no hot
				// swapping or fancy proxying.

				// 如果链是空且是public方法,则直接调用
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = methodProxy.invoke(target, argsToUse);

			} else
			{
				// We need to create a method invocation...
				// 否则创建一个CglibMethodInvocation以便驱动拦截器链
				retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)
						.proceed();
			}
			// 处理返回值,同JDK动态代理
			retVal = processReturnType(proxy, target, method, retVal);
			return retVal;
		}

		finally
		{
			if (target != null)
			{
				releaseTarget(target);
			}

			if (setProxyContext)
			{
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

注意上面真实调用的部分,在没有advisor的情况下,使用的其实是:

methodProxy.invoke(target, argsToUse)

在有代理的情况下,使用的是:

new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

而在CglibMethodInvocation中,检查到调用链执行完之后,会调用真实的方法:invokeJoinpoint。在CglibMethodInvocation中,该方法的实现是


	// CglibMethodInvocation中的实现

	protected Object invokeJoinpoint() throws Throwable
	{

		if (this.publicMethod)
		{

			return this.methodProxy.invoke(this.target, this.arguments);

		} else
		{

			return super.invokeJoinpoint();

		}

	}

	// 父类实现是

	protected Object invokeJoinpoint() throws Throwable
	{

		return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);

	}

可以看到调用方法时,传入的实例都是target,这个target是从DynamicAdvisedInterceptor的getTarget方法中获得的,代码如下

protected Object getTarget() throws Exception {

return this.advised.getTargetSource().getTarget();

}

而这个advised的target则是在ProxyFactory的实例方法中设置的:proxyFactory.setTarget(bean);

也就是说这个target其实是真实的被代理的bean。

通过上面的分析,我们可以得到结论,在一个被动态代理的对象,在执行完AOP所有的增强逻辑之后,最终都会使用被代理对象作为实例调用真实的方法,即相当于调用了:target.method()方法。由此得出结论,在target.method()方法中,this引用必然是target自身,而不是生成的动态代理对象实例。

补充一下,Spring在创建一个Bean之后,对其包装并生成动态代理对象都是后置的举动,故会先生成真实类的实例bean,再动态创建动态代理bean,在动态代理bean中,会持有真实的bean的实例。

就拿最上面的@Async代码实例举例,我们可以看到this其实是AsyncService的原始实例,而不是代理对象实例:

总结: 因为AOP动态代理的方法真实调用,会使用真实被代理对象实例进行方法调用,故在实例方法中通过this获取的都是被代理的真实对象的实例,而不是代理对象自身。

解决方法

1 通过ApplicationContext来获得动态代理对象。同理是用BeanFactoryAware可达到同样的效果。

 2 用Autowired 注入自身的实例

这个方法,第一眼看上去感觉有些怪,自己注入自己,感觉有点象递归/死循环的搞法,但确实可以work,Spring在解决循环依赖上有自己的处理方式,避免了死循环。

3 利用AopContext 

但是在默认情况下是不起作用的! 因为AopContext中拿不到currentProxy,会报空指针。

if (this.advised.exposeProxy) {

// Make invocation available if necessary.

oldProxy = AopContext.setCurrentProxy(proxy);

setProxyContext = true;

}

而在ProxyConfig类中,有如下注释用来说明exposeProxy的作用,就是用于在方法中获取动态代理的对象的。

即只有exposeProxy为true时,才会把proxy动态代理对象设置到AopContext上下文中,这个配置默认是false。那么这个配置怎么修改呢?

在xml时代,我们可以通过配置:

来修改全局的暴露逻辑。

在基于注解的配置中,我们需要使用

@EnableAspectJAutoProxy(proxyTargteClass = true, exposeProxy = true)

来配置。

因为@Transactional注解和AspectJ相关注解的生成动态代理类都是使用的同一个Bean即上面的AutoProxyCreator处理的,

该bean的name是org.springframework.aop.config.internalAutoProxyCreator,他们公用相同的属性,故对于@Transactional来说,@EnableAspectJAutoProxy的属性exposeProxy=true也是生效的。

但是@Async的注解生成的代理类并不是通过这个autoProxyCreator来生成的,故不能享受到上面的配置。

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

 

 4 在某个切入时机,手动执行AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);静态方法,当然前提是有一个BeanDefinitionRegistry,且时机要在BeanDefinition已经创建且动态代理对象还没有生成时调用。

使用这种方式,无需使用@EnableAspectJAutoProxy即可。

这种方式同样不适用于@Async,适用于@Transactional。

5 手动修改各种BeanPostProcessor的属性

 以@Async为例,其通过AsyncAnnotationBeanPostProcessor来生成动态代理类,我们只要在合适时机即该BPP已创建,

但是还未被使用时,修改其中的exposeProxy属性,使用AsyncAnnotationBeanPostProcessor.setExposeProxy(true)即可。

这种方式要针对性的设置特定的bean的exposeProxy属性true。适用于@Async,观察原理可以知道3和4其实核心都是相同的,就是设置AutoProxyCreater的exposed属性为true。

AsyncAnnotationBeanPostProcessor其实也是一个AutoProxyCreater,他是ProxyProcessorSupport的子类。

对于@Async可以使用1、5方式,对于@Transactional则可以使用任意方式。

举报

相关推荐

0 条评论