AOP失效了...分析
技术来自于需求
组内在做性能测试,要知道每个方法调用时候,外层方法以及内部调用的每个子方法的耗时时长,第一时间呢就想到了在方法前后打印时间,然后做差值打印
但是里面的调用链比较长,然后写了好多好多差值打印代码...
基于上述情况呢,我就想着做个annotation给方法去使用,想打印方法执行时间的方法呢就把我的annotation加上去就行了
先说下额外的一个小选型废弃
- 摒弃threadlocal记录时间,本来想直接用threadlocal记录,然后在全局返回体内拦截的适合去除时间即可,但是由于我们方法内许多方法都是可以异步进行提高性能的,用到了多线程,所以这个方案就废弃了
- 最终方案呢 是用annotation增强我们的方法,将方法执行时间打印到mdc里,然后在全局拦截器(一个对方法返回值再封装的拦截器形如 m,d,e)里加了一个t (map结构),将我们的mdc关于时间打印的都放进去了
AOP失效了啥情况?
同一类里调用别的AOP方法,写下伪代码,类似下面代码
@PostMapping("/timeRecode")
@TimeRecord
public String timeRecode() throws InterruptedException {
time1();
TimeUnit.SECONDS.sleep(1);
time2();
return "ok";
}
@TimeRecord
public void time1() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
@TimeRecord
public void time2() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
按照我的预期应该是三个方法都被拦截到的,但是实验证明只有timeRecode被拦截了,time1().time2()没被拦截,分析下,两者不同的是前者是外部方法,而后面两者是内部调用的.
原因分析:
Spring AOP采用代理的方式实现AOP,我们编写的横切逻辑被添加到动态生成的代理对象中,只要我们调用的是代理对象,则可以保证调用的是被增强的代理方法。而在代理对象中,不管你的横切逻辑是怎样的,也不管你增加了多少层的横切逻辑,有一点可以确定的是,你终归会调用目标对象的同一方法来调用原始的业务逻辑。
如果目标对象中的原始方法依赖于其他对象,那么Spring会注入所依赖对象的代理对象,从而保证依赖的对象的横切逻辑能够被正常织入。而一旦目标对象调用的是自身的其他方法时,问题就来了,这种情况下,目标对象调用的并不是代理对象的方法,故被调用的方法无法织入横切逻辑。
我们这里方法 A 被调用,是基于 AOP 生成的 代理对象 进行的调用;方法 B 调用方法 A ,是 this 目标对象 直接调用,并不是代理对象进行调用
解决方案
通过代理对象调用~
- 同一个类内方法互相调用可以拿到Spring给我们创建的代理,用代理调用就可以解决,解决如下:
final TestController proxy = (TestController) AopContext.currentProxy();
proxy.time2();
- 采用注入的方式把要调用的bean先注入进来,再通过bean去调用,这也适用于同一个类,其实我们也可以把自己的bean注入到自己方法内使用.
//构造器注入其他bean
private final CacheManager cacheManager;
//annotation注入其他bean
@Autowired
private Temp temp;
@Service
public class SomeService {
//注入自己
@Autowired
private SomeService self
}
ps:
另在查阅资料时候发现有人的aop失效是因为没开启cglib,这里也提一下啊,想要使用aop,要做以下配置
-
启动类上加@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true),开启cglib代理,为啥呢?
Spring AOP通过动态代理实现的,不同场景下代理的支持1. 如果要切入的目标对象实现了接口,默认情况下Spring会直接采用JDK的动态代理的方式去实现AOP 2. 如果要切入的目标对象实现了接口,也可以通过代码控制强制要求Spring使用CGLIB代理的方式去实现AOP 3. 如果要切入的目标对象没有实现任何接口,那么此时Spring会自动采用CGLIB库,它会根据实际切入目标的情况,自动在JDK动态代理和CGLIB之间做切换,但是必须在项目中加入CGLIB需要的jar包,否则当Spring判定需要使用CGLIB的时候,而你没有引入CGLIB的jar包,此时就会报错,而当你引入CGLIB的jar包后,不需要额外做任何关于CGLIB配置,Spring直接就可以根据规则自动转换代理模式。
其他失效
-
(1) 在一个类内部调用时,被调用方法的 AOP 声明将不起作用。
-
(2) 对于基于接口动态代理的 AOP 事务增强来说,由于接口的方法都必然是 public ,这就要求实现类的实现方法也必须是 public 的(不能是 protected、private 等),同时不能使用 static 的修饰符。 所以,可以实施接口动态代理的方法只能是使用 public 或 public final 修饰符的方法,其他方法不可能被动态代理,相应的也就不能实施 AOP 增强,换句话说,即不能进行 Spring 事务增强了。
-
(3) 基于 CGLib 字节码动态代理的方案是通过扩展被增强类,动态创建其子类的方式进行 AOP 增强植入的。
由于使用final、static、private
修饰符的方法都不能被子类覆盖,这些方法将无法实施 AOP 增强。所以方法签名必须特别注意这些修饰符的使用,以免使方法不小心成为事务管理的漏网之鱼。