Aspect Oriented Programming(AOP),面向切面编程。
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP在Java里是利用反射机制实现(也是 动态代理设计模式 )。
在Spring里重要的实现手段之一就是注解,例如 事务注解@transactional,异步注解@Async。
@transactional注解的方法,用户无需写任何事务相关代码,方法自动开启事务,提交事务。这就是spring将事务代码已经统一写好,这就是切面。而开发者写的业务代码,加上@transactional注解,就是面向切面编程。程序编译跟运行时,业务代码前会开启事务-这叫前置加强,业务代码后会提交事务-这叫后置加强。
@Async注解的方法,也类似上面,程序编译跟运行时,该方法会在一个被创建的新线程中执行,前置加强 为 new一个runable对象并start它,无需显式的写相关代码。
这里要注意这些注解,有一个失效的坑。。。需要理解了 动态代理 的 原理,才会明白这个坑的原因。
动态代理,就是,业务对象真实实现业务方法(通常是serviceImpl),而外部调用该业务者(通常是控制器)得到的对象实际上只是一个代理对象,该代理对象也实现了业务接口(或者是继承),但是是伪实现,它的本质是通过反射调用业务对象的同名方法,那么在调用的时候,代理对象就可以增加代码来前置加强跟后置加强了,比如开启事务,提交事务。等等。。
那么,以下代码:
public class serviceImpl(){
@transactional
public void aaa(){
...
}
@transactional
public void bbb(){
...
}
public void ccc(){
aaa();
bbb();
}
}
外部分别调用aaa()、bbb()方法时候,俩方法是有事务的,但是,外面调用 ccc方法的时候,通过ccc 方法再调用aaa、bbb,那这俩方法还分别有没有事务了?答案是没有了,这就是注解失效,同理@Async,也会注解失效,不会启动新线程。
原理:外部通过代理对象调用ccc,ccc没有注解,不会启动事务,然后,ccc内部调用aaa,请注意,这时候是谁在调用 aaa ?是业务对象,不是代理对象,那么aaa的注解就没作用了,因为注解是在代理对象起作用的(参考上面动态代理的说明)。
假如外部直接调用aaa,那么就是通过代理对象调用,注解的事务才能生效。
那么怎么避免这个坑了???
使用AopContext类。。。
首先spring配置加上:
<aop:aspectj-autoproxy expose-proxy="true"/>
开启cglib代理,允许对外暴露代理对象。
JDK静态代理:代理类与委托类实现同一接口,并且在代理类中需要硬编码接口。
JDK动态代理:代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对委托类方法进行增强处理。
CGLIB动态代理:代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理。
JDK代理要求被代理的类必须实现接口,有很强的局限性。而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。
代码里,通过
AopContext.currentProxy()
获取代理对象,然后,通过代理对象调用aaa,bbb,注解就又有效了。。。
记住:必须要调用代理对象,注解才会被切进去,才有AOP。
另外直接applicationContext.getBean()也可以,而且配置中的expose-proxy也不用设置成true,但是代码不够优雅。。。。
解决方法不重要,重要的是明白原理,然后,写代码的时候要避免这个坑,也就是不这么写。。。