AOP简介
将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。
这样一来,我们就在写业务时只关心业务代码,而不用关心与业务无关的代码。
Spring 的代理实现有两种:
- 基于 JDK Dynamic Proxy 技术而实现的
- 基于 CGLIB 技术而实现的。
如果该类实现了接口,Java的Proxy做动态代理;如果没有实现,则是CGLIB做的动态代理(以子类的方式)。
JDK 动态代理基于接口,所以只有接口中的方法会被增强,而 CGLIB 基于类继承,需要注意就是如果方法使用了 final 修饰,或者是 private 方法,是不能被增强的。
常见Spring AOP注解有
- @Async,异步
- @Transational,事务
术语
连接点(Join point):
- 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
切点(Poincut):
- 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
增强/通知(Advice):
- 表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
简单来说就定义了是干什么的,具体是在哪干 - Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
织入(Weaving):
- 将增强/通知添加到目标类的具体连接点上的过程。
引入/引介(Introduction):
- 引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!
切面(Aspect):
- 切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
源码
DefaultAopProxyFactory1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
// 如果要代理的类本身就是接口,也会用 JDK 动态代理
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
//如果被代理的目标类实现了一个或多个自定义的接口,那么就会使用 JDK 动态代理
//如果没有实现任何接口,会使用 CGLIB 实现代理
//如果设置了 proxy-target-class="true",那么都会使用 CGLIB
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
// 判断是否有实现自定义的接口
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
内部调用
1 | public class ServiceTest { |
结果:bar是事务执行的,而foo不是事务执行;当foo方法内部调用bar方法后,bar方法的事务是不生效的。
原因
- Spring中通过注解来完成事务的功能,实际是通过SpringAOP来实现的。
- 而SpringAOP中,使用this来调用自身的方法时,此对象引用上的方法直接会被调用,不会调用代理的方法(SpringAOP原理是产生代理类);因此bar方法的事务不会生效。
- 如果不通过foo,而直接调用bar方法,此时事务是生效的。
解决
- 将bar方法放在另一个service类中,类外调用;这种方法简单,但是造成代码的冗余。
- 可以将注解@Transactional放在foo方法上;加入foo方法的一些操作是不需要事务的,这会延长事务执行的时间。
在foo方法中不要直接使用this来调用bar方法,通过调用代理类的bar方法。
1
2
3
4
5
6public void foo(){
if(null != AopContext.currentProxy()){ ((ServiceTest)AopContext.currentProxy()).bar();
}else{
bar();
}
}且配置
1
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
它是ProxyConfig的一个参数,默认是false,如果不设置这个参数,那么上述java代码将无法获取当前线程中的代理对象。
这种方法可以成功触发拦截,但是也带来了其他问题,比如代码的织入,我们的代码将变得复杂而且晦涩,而且严格要求系统针对于当前的bean必须配置拦截器,否则会因为找不到拦截器而抛出异常。