Spring AOP概述
Advice通知
Advice(通知)定义在连接点做什么,为切面增强提供织人接口。在SpringAOP中,它主要描述SpringAOP围绕方法调用而注入的切面行为。Advice是AOP联盟定义的一个接口,具体的接口定义在org.aopalliance.aop.Advice中。在SpringAOP的实现中,使用了这个统一接口,并通过这个接口,为AOP切面增强的织入功能做了更多的细化和扩展,比如提供了更具体的通知类型,如BeforeAdvice、AfterAdvice、ThrowsAdvice等。作为SpringAOP定义的接口类,具体的切面增强可以通过这些接口集成到AOP框架中去发挥作用。对于这些接口类,下面会逐个进行详细讨论,我们从接口BeforeAdvice开始,首先了解它的类层次关系,如下图所示。
在BeforeAdvice的继承关系中,定义了为待增强的目标方法设置的前置增强接口MethodBeforeAdvice,使用这个前置接口需要实现一个回调函数:
less
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
作为回调函数,before方法的实现在Advice中被配置到目标方法前,会在调用目标方法时被回调。具体的调用参数有:Method对象,这个参数是目标方法的反射对象;Object[]对象数组,这个对象数组中包含目标方法的输入参数。以CountingBeforeAdvice为例来说明BeforeAdvice的具体使用,CountingBeforeAdvice是接口MethodBeforeAdvice的具体实现,代码如下所示。可以看到,它的实现比较简单,完成的工作是统计被调用的方法次数。作为切面增强实现,它会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对放入一个map中。
scala
public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {
@Override
public void before(Method m, Object[] args, Object target) throws Throwable {
count(m);
}
}
这里调用了count方法,使用了目标方法的反射对象作为参数,完成对调用方法名的统计工作。count方法在CountingBeforeAdvice的基类MethodCounter中实现,如下所示。这个切面增强完成的统计实现并不复杂,它在对象中维护一个哈希表,用来存储统计数据。在统计过程中,首先通过自标方法的反射对象得到方法名,然后进行累加,把统计结果放到维护的哈希表中。如果需要统计数据,就到这个哈希表中根据key来获取。
typescript
public class MethodCounter implements Serializable {
/** Method name --> count, does not understand overloading */
private Map<String, Integer> map = new HashMap<>();
private int allCount;
protected void count(Method m) {
count(m.getName());
}
protected void count(String methodName) {
map.merge(methodName, 1, (n, m) -> n + 1);
++allCount;
}
public int getCalls(String methodName) {
return map.getOrDefault(methodName, 0);
}
public int getCalls() {
return allCount;
}
/**
* A bit simplistic: just wants the same class.
* Doesn't worry about counts.
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object other) {
return (other != null && other.getClass() == this.getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
在Advice的实现体系中,Spring还提供了AfterAdvice这种通知类型,它的类接口关系如下所示。
在上图所示的AfterAdvice类接口关系中,可以看到一系列对AfterAdvice的实现和接口扩展,比如AfterReturningAdvice就是其中比较常用的一个。在这里,以AfterReturningAdvice通知的实现为例,分析一下AfterAdvice通知类型的实现原理。在AfterReturningAdvice接口中定义了接口方法,如下所示:
less
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
afterReturning方法也是一个回调函数,AOP应用需要在这个接口实现中提供切面增强的具体设计,在这个Advice通知被正确配置以后,在目标方法调用结策并成功返回的时候,接口会被SpringAOP回调。对于回调参数,有目标方法的返回结果、反射对象以及调用参数(AOP把这些参数都封装在一个对象数组中传递进来)等。与前面分析BeforeAdvice一样,在SpringAOP的包中,同样可以看到一个CountingAfterReturningAdvice,作为熟悉AfterReturningAdvice使用的例子,它的实现基本上与CountingBeforeAdvice是一样的,如下代码所示。
scala
public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable {
count(m);
}
}
了解了BeforeAdvice和AfterAdvice,在SpringAOP中,还可以看到另外一种Advice通知类型,那就是ThrowsAdvice,它的类层次关系如下图所示。
java
public interface ThrowsAdvice extends AfterAdvice {
}
scala
public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {
public void afterThrowing(IOException ex) throws Throwable {
count(IOException.class.getName());
}
public void afterThrowing(UncheckedException ex) throws Throwable {
count(UncheckedException.class.getName());
}
}
在afterThrowing方法中,从输人的异常对象中得到异常的名学并进行统计。这个count方法同样是在MethodCounter中实现的,与前面看到的两个Advice的实现相同,只是前面的CountingBeforeAdvice和CountingAfterReturningAdvice统计的是自标方法的调用次数,在这里,count方法完成的是根据异常名称统计抛出异常的次数。
PointCut切点
Pointcut(切点)决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut通常意味着标识方法,例如,这些需要增强的地方可以由某个正则表达式进行标识或根据某个方法名进行匹配等。为了方便用户使用,SpringAOP提供了具体的切点供用户使用,切点在SpringAOP中的类继承体系如下所示。
在Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher。对于Point的匹配判断功能,具体是由这个返回的MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。在Pointcut的类继承关系中,以正则表达式切点JdkRegexpMethodPointcut的实现原理为例,来具体了解切点Pointcut的工作原理。JdkRegexpMethodPointcut类完成通过正则表达式对方法名进行匹配的功能。在JdkRegexpMethodPointcut的基类StaticMethodMatcherPointcut的实现中可以看到,设置MethodMatcher为StaticMethodMatcher,同时JdkRegexpMethodPointcut也是这个MethodMatcher的子类。
StaticMethodMatcher的类层次关系如下:
可以看到,在Pointcut中,通过这样的类继承关系,MethodMatcher对象实际上是可以被配置成JdkRegexpMethodPointcut来完成方法的匹配判断的。JdkRegexpMethodPointcut中,可以看到一个matches方法,这个matches方法是MethodMatcher定义的接口方法。在JdkRegexpMethodPointcut的实现中,这个matches方法就是使用正则表达式来对方法名进行匹配的地方。关于在AOP框架中对matches方法的调用,会在下面的SpringAOP实现中介绍,这里只是先简单提一下。
org.springframework.aop.support.JdkRegexpMethodPointcut#matches的实现如下:
arduino
protected boolean matches(String pattern, int patternIndex) {
Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
return matcher.matches();
}
在JdkRegexpMethodPointcut中,通过JDK来实现正则表达式的匹配,这在代码中可以看得很清楚。如果要详细了解使用JDK的正则表达式匹配功能,可以参考JDK的API文档。接下来看看其他的Pointcut。 在SpringAOP中,还提供了其他的MethodPointcut,比如通过方法名匹配进行Advice匹配的NameMatchMethodPointcut。它的matches方法实现很简单,匹配的条件是方法名相同或 者方法名相匹配,如下代码所示。
org.springframework.aop.support.NameMatchMethodPointcut#matches实现如下:
typescript
public boolean matches(Method method, Class<?> targetClass) {
for (String mappedName : this.mappedNames) {
if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
return true;
}
}
return false;
}
Advisor通知器
完成对目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)以后,需要一 个对象把它们结合起来,完成这个作用的就是Advisor(通知器)。通过Advisor,可以定义应该使用哪个通知并在哪个关注点使用它,也就是说通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。在SpringAOP中,我们以一个Advisor的实现(DefaultPointcutAdvisor)为例,来了解Advisor的工作原理。在DefaultPointcutAdvisor中,有两个属性,分别是advice和pointcut。通过这两个属性,可以分别配置Advice和Pointcut,DefaultPointcutAdvisor的实现如下代码所示。
java
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
private Pointcut pointcut = Pointcut.TRUE;
public DefaultPointcutAdvisor() {
}
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
public void setPointcut(@Nullable Pointcut pointcut) {
this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public String toString() {
return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
}
}
在DefaultPointcutAdvisor中,pointcut默认被设置为Pointcut.True,这个Pointcut.True在Pointcut接口中被定义为:
ini
Pointcut TRUEE = TruePointcut.INSTANCE;
TruePointcut的INSTANCE是一个单件。在它的实现中,可以看到单件模式的具体应用和典型使用方法,比如使用static类变量来持有单件实例,使用private私有构造函数来确保除 了在当前单件实现中,单件不会被再次创建和实例化,从而保证它的"单件"特性。在TruePointcut的methodMatcher实现中,使用TrueMethodMatcher作为方法匹配器。这个方法匹配器对任何的方法匹配都要求返回true的结果,也就是说对任何方法名的匹配要求,它都会返回匹配成功的结果。和TruePointcut一样,TrueMethodMatcher也是一个单件实现。TruePointcut和TrueMethodMatcher的实现如下代码所示。
typescript
final class TruePointcut implements Pointcut, Serializable {
public static final TruePointcut INSTANCE = new TruePointcut();
private TruePointcut() {
}
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
private Object readResolve() {
return INSTANCE;
}
@Override
public String toString() {
return "Pointcut.TRUE";
}
}
final class TrueMethodMatcher implements MethodMatcher, Serializable {
public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();
private TrueMethodMatcher() {
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return true;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
// Should never be invoked as isRuntime returns false.
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "MethodMatcher.TRUE";
}
private Object readResolve() {
return INSTANCE;
}
}