二、Spring AOP
1、什么是Spring AOP
Spring AOP是Spring核心框架的重要组成部分,采用Java作为AOP的实现语言。与AspectJ实现AOP方式不同之处在于,Spring AOP仅支持方法级别的拦截。
2、Spring AOP的组成
Spring AOP中主要包括:Joinpoint、Pointcut、Advice、Aspect,下面一一介绍。
(1)Joinpoint
当我们将横切逻辑织入到OOP模块当中,需要知道在哪些执行点进行织入操作。这些执行点就被称为Joinpoint。
(通俗点理解:Joinpoint是一个点,规定了在哪个位置插入横切逻辑)
Spring AOP只支持方法级别的Joinpoint织入,所以如果超出这个范围,就需要通过AspectJ等进行操作。
(2)Pointcut
Pointcut通常使用正则表达式来描述多组符合条件的某个方法。
我们来看下Spring AOP中提供的Pointcut接口的定义:
public interface Pointcut {
Pointcut TRUE = TruePointcut.INSTANCE;
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
可以看到,Pointcut 通过ClassFilter 和MethodMatcher 两个过滤来匹配要织入的方法。首先ClassFilter来匹配需要操作的类,然后使用MethodMatcher 匹配类中具体的方法。只有两种类型均匹配后才会执行织入操作。
当然,Pointcut TRUE = TruePointcut.INSTANCE;
表示与class类型无关。
1)ClassFilter
ClassFilter接口作用是对Joinpoint所处对象进行Class级别的类型匹配。
下面进入看下ClassFilter的定义:
public interface ClassFilter {
ClassFilter TRUE = TrueClassFilter.INSTANCE;
boolean matches(Class<?> var1);
}
可以看到ClassFilter通过matches(Class
public interface MethodMatcher {
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
boolean matches(Method var1, Class<?> var2);
boolean isRuntime();
boolean matches(Method var1, Class<?> var2, Object[] var3);
}
MethodMatcher中定义了两个重载的matches()方法,一个不带参数,一个带参数Object[] var3,中间用isRuntime()隔离。
如果不需要检查参数,就调用第一个matches()方法,此时isRuntime()返回false,第二个matches()方法就不会去执行,此时的MethodMatcher被称之为StaticMethodMatcher;
如果需要对方法中的参数进行匹配,就需要调用第二个matches()方法,此时isRuntime()返回true。此时的MethodMatcher被称之为DynamicMethodMatcher。
因此,在MethodMatcher基础上,Pointcut又分为两类:
- StaticMethodMatcher
- DynamicMethodMatcher
两者关系图如下:
Pointcut常见的实现类有如下几种:
- NameMatchMethodPointcut
- JdkRegexpMethodPointcut
- Perl5RegexpMethodPointcut
- AnnotationMatchingPointcut
- ComposablePointcut
- ControlFlowPointcut
1)NameMatchMethodPointcut
最简单的Pointcut实现,是StaticMethodMatcher的子类,可以指定Joinpoint处的方法名称进行匹配。
如:
new NameMatchMethodPointcut().setMappedName("login");
2)JdkRegexpMethodPointcut、Perl5RegexpMethodPointcut
StaticMethodMatcher下可以使用正则表达式对拦截方法进行匹配,
如:
new JdkRegexpMethodPointcut().setPattern(".*doSth().*");
3)AnnotationMatchingPointcut
AnnotationMatchingPointcut根据目标对象中是的存在指定类型的注解来匹配Joinpoint。
如:
new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);
以上代码会对使用类注解ClassLevelAnnotation、方法注解MethodLevelAnnotation所匹配到的方法进行拦截。
4)ComposablePointcut
ComposablePointcut可以进行Pointcut逻辑运算的Pointcut实现。不常用,不赘述。
5)ControlFlowPointcut
假设要织入的Joinpoint处所在的方法为login(),
ControlFlowPointcut可以指定具体的目标对象调用login()才进行拦截,别的目标对象调用login()方法时不会进行拦截。
不常用,不赘述。
(3)Advice
Advice是单一横切关注点逻辑的载体,实现类将被织入到Pointcut规定的Joinpoint位置的横切逻辑。
(通俗点理解就是:Advice就是我们实现的如日志、安全、事务等逻辑操作,这些Advice需要被织入到业务代码中。)
Spring AOP中提供了如下Advice:
主要分为两大类:
- per-class类型的Advice:该类型的Advice可以在目标对象类的所有实例之间共享,只是提供方法拦截的功能,不会为目标对象类添加新的特性;
主要有:BeforeAdvice,ThrowsAdvice,AfterReturningAdvice,AroundAdvice; - per-instance类型的Advice:不可以在目标对象类的所有实例之间共享,可以为不同的实例对象保存各自的状态以及相关逻辑,在不改变目标类定义的情况下,为目标类添加新的属性以及行为;
Introduction是唯一一种per-instance类型的Advice。
1)Before Advice
BeforeAdvice所实现的横切逻辑将在相应的Joinpoint之前执行,BeforeAdvice执行完之后将会从Joinpoint继续执行。
2)ThrowsAdvice
ThrowsAdvice对应AOP中的AfterThrowingAdvice,通常用于对系统中特定的异常情况进行监控,以统一的方式对所发生的异常进行处理。
3)AfterReturningAdvice
在Joinpoint处所在的方法正常执行完成之后执行AfterReturningAdvice。
4)Around Advice
Spring中没有直接定义Around Advice的实现接口,而是用MethodInterceptor来控制对相应Joinpoint的拦截行为。
5)Introduction
Introduction可以在不改变目标类定义的情况下,为目标类添加新的属性以及行为。
(4)Aspect
Aspect是对系统的横切关注点逻辑进行模块化封装的AOP实体。Aspect于AOP对应Class与OOP。
一个Aspect包含多个Pointcut以及相关Advice。
Spring中提供了Advisor代表Aspect,Advisor一般只有一个Pointcut和Advice,可以看作是特殊的Aspect。
Spring中提供的Advisor关系图如下:
主要分为两大类:
- PointcutAdvisor:主要有DefaultPointcutAdvisor,NameMatchMethodPointcutAdvisor,RegexpMethodPointcutAdvisor;
- IntroductionAdvisor:DefaultIntroductionAdvisor。
IntroductionAdvisor与PointcutAdvisor区别:IntroductionAdvisor只能用于类级别的拦截和Introduction类型的Advice;而PointcutAdvisor可以使用任意类型的Pointcut和除Introduction类型以外的Advice。
1)PointcutAdvisor的具体实现如下:
DefaultPointcutAdvisor:最通用的PointcutAdvisor,可以使用任何类型的Pointcut和Advice(除Introduction类型的Advice)。
如:
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);//任何Pointcut类型
advisor.setAdvice();//除Introduction类型的Advice
NameMatchMethodPointcutAdvisor:限定了Pointcut的使用类型,即只能使用NameMatchMethodPointcut类型的Pointcut,其他除Introduction类型外的任何Advice都可以使用。
如:
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
advisor.setMappedName("login");
RegexpMethodPointcutAdvisor:限定了Pointcut的使用类型,即只能使用RegexpMethodPointcut类型的Pointcut,其他除Introduction类型外的任何Advice都可以使用。
如:
2)IntroductionAdvisor的具体实现如下:
IntroductionAdvisor只有一个默认实现类:DefaultIntroductionAdvisor(另外一个通过AspectJ拓展),只能指定Introduction类型的Advice。
3)Ordered接口
可以看到,PointcutAdvisor、IntroductionAdvisor的实现类都实现了Ordered接口。
当多个Advisor接口中的Pointcut匹配了同一个Joinpoint时,就会在这同一个Joinpoint处执行Advice横切逻辑的织入。
但是哪个Advisor中的Advice先执行呢?
Ordered接口就负责规定同一Joinpoint处Advice执行的先后顺序。
Spring在处理同一Joinpoint处的多个Advisor的时候,会按照Ordered接口规定的顺序号来执行,顺序号越小,优先级越高。
如:
假设有两个Advisor,一个进行权限检查的PermissionAuthAdvisor;一个进行异常检测的ExceptionAdvisor。
<bean id="permissionAuthAdvisor" class="....PermissionAuthAdvisor">
<property name="order" value="1"
</bean>
<bean id="exceptionAdvisor" class="....ExceptionAdvisor">
<property name="order" value="0"
</bean>
可以看到通过为order属性赋值,可以规定ExceptionAdvisor首先执行。
3、Spring AOP的实现原理
同上节AOP中介绍的AOP实现原理一样,Spring AOP在实现AOP的过程中使用了代理模式,并提供了以下两种机制分别对实现了接口的目标类和没有实现任何接口的目标类进行代理:
- JDK动态代理;
- CGLIB
Spring AOP框架内使用AopProxy对不同的代理机制进行了抽象并提供了相应的子类实现,相关结构图如下:
AopProxy有CglibAopProxy和JdkDynamicAopProxy两种实现。
Spring AOP会通过策略模式—即根据目标对象是类还是接口来使用CglibAopProxy或JdkDynamicAopProxy完成对目标类进行代理。
不同的AopProxy实现的实例化过程采用工厂模式—即通过AopProxyFactory进行封装(在下一小节织入过程详细介绍AopProxyFactory是如何进行封装的过程)。
Spring AOP采用ProxyFactory来完成织入操作(下小节会详细介绍),下面来看下在Spring AOP中两种机制的代码实现。
(1)基于接口的代理—JDK动态代理
代码实现:
//公共接口
public interface ILogin {
void login();
}
//真正实现类
public class RealLogin implements ILogin {
@Override
public void login() {
System.out.println("登录。。。");
}
}