SpringAOP概述(六)
OOP 面向对象编程,AOP(Aspect-Oriented Programming) 面向切面编程。
官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
1. AOP简介
主要有以下AOP技术:
1》AspectJ: 源代码和字节码级别的编织器,用户需要使用不同于Java的新语言
2》AspectWerkz:AOP框架,使用字节码动态编织器和XML配置
3》JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上,以及在AOP中用到的一些相关的技术实现
4》BCEL(Byte-Code Engineering Library): Java字节码操作类 库
5》Javassist:Java字节码操作类型,JBOSS的一个子项目
对应于现有的AOP实现方法,AOP联盟对它们进行了一定程度的抽象,从而定义出AOP体系结构。AOP联盟定义的AOP体系结构如图:
AOP联盟定义的体系结构把与AOP相关的概念大致分为由高到低、从使用到实现的三个层次。从上往下,最高层是语言和开发环境,在这个环境中可以看到几个重要的概念:
(1) 基础(base) 可以视为待增强对象或者说目标对象
(2) 切面(aspect) 通常包含对于基础的增强应用
(3) 配置(configuration)可以看成是一种编织,通过在AOP体系中提供这个配置环境,可以把基础和切面结合起来,从而完成切面对目标对象的编织实现
在SpringAOP的实现中,使用Java语言来实现增强对象与切面增强应用,并为这两者的结合提供了配置环境。对于编织配置,毫无疑问,可以使用IoC容器了完成;对于POJO对象的配置,本身就是Spring核心IoC容器的强项。因此,对于使用SpringAOP开发而言,使用POJO就能完成AOP任务。但是,对于其他AOP实现方案,可能需要使用特定的实现语言、配置环境甚至是特定的编译环境。例如在AspectJ中,尽管切面增强的是Java对象,但却需要使用特定的Aspect语言和Aspect编译器。
AOP体系的第二个层次是为语言和开发环境提供支持的,在这个层次中可以看到AOP框架的高层实现,主要包括配置和编织实现两部分内容。
最底层是编织的具体实现模块,比如反射、程序预处理、拦截器框架、类装载器框架、元数据处理等。
2. Spring AOP简介
AOP与IoC容器的结合使用,为应用开发或Spring自身功能的扩展都提供了许多便利。Spring AOP的实现和其他特性的实现一样,除了可以Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方法AspectJ来供应用使用。这里主要眼界Spring自身的AOP实现原理。Spring充分利用了IoC容器Proxy代理对象以及AOP拦截器的功能特性,通过这些对AOP基本功能的封装机制,为用户提供了AOP的实现框架。所以Java的Proxy机制是Spring AOP的重点。
1. Advice通知
Advice(通知) 定义在连接点做什么,为切面增强提供织入接口。在SpringAOP中,它主要描述SpringAOP围绕方法调用而注入的切面行为。Advice是AOP联盟定义的一个接口,具体的接口在 org.aopalliance.aop 包下。在SpringAOP实现中,使用了这个统一接口,并通过这个接口,为AOP切面增强的植入功能做了更多的细化和扩展,衍生出很多接口,如下:
以 BeforeAdvice 接口为例进行分析。Advice和BeforeAdvice 都是一个空接口。BeforeAdvice继承关系如下:
MethodBeforeAdvice 接口定义了为待增强的目标方法设置的前置增强接口,接口包含一个方法如下:
package org.springframework.aop; import java.lang.reflect.Method; import org.springframework.lang.Nullable; public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method method, Object[] args, @Nullable Object target) throws Throwable; }
作为回调函数,before方法的实现在Advice中被配置到目标方法后,会在调用目标方法前被回调。具体的调用参数有:Method对象,这个参数是目标方法的反射对象;Object[] 对象数组,这个对象数组包含目标方法的输入参数。以CountingBeforeAdvice为例子说明其用法。CountingBeforeAdvice 完成的工作是统计被调用的方法次数。作为切面增强实效,它会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对存入一个map中。源码如下:
package org.springframework.tests.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; @SuppressWarnings("serial") public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice { @Override public void before(Method m, Object[] args, Object target) throws Throwable { count(m); } }
MethodCounter源码如下:
package org.springframework.tests.aop.advice; import java.io.Serializable; import java.lang.reflect.Method; import java.util.HashMap; /** * Abstract superclass for counting advices etc. * * @author Rod Johnson * @author Chris Beams */ @SuppressWarnings("serial") public class MethodCounter implements Serializable { /** Method name --> count, does not understand overloading */ private HashMap<String, Integer> map = new HashMap<>(); private int allCount; protected void count(Method m) { count(m.getName()); } protected void count(String methodName) { Integer i = map.get(methodName); i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1); map.put(methodName, i); ++allCount; } public int getCalls(String methodName) { Integer i = map.get(methodName); return (i != null ? i.intValue() : 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(); } }
Spring提供了与BeforeAdvice 对等的AfterAdvice,类图如下:
以AfterReturningAdvice 为例进行分析:
package org.springframework.aop; import java.lang.reflect.Method; import org.springframework.lang.Nullable; public interface AfterReturningAdvice extends AfterAdvice { void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable; }
afterReturning 方法在目标方法调用结束并成功返回时调用。对于回调参数有 目标方法的返回结果、反射对象、以及调用参数等。有一个实现类CountingAfterReturningAdvice,它的实现与上面CountingBeforeAdvice 基本一样:
package org.springframework.tests.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; @SuppressWarnings("serial") public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable { count(m); } }
AfterAdvice还有一个子接口ThrowsAdvice,它在抛出异常时回调,这个回调是AOP使用反射机制完成的。可以通过查看CountingThrowsAdvice,如下:
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统计的是方法调用次数,这里count方法统计的是抛出异常的次数。
补充:Spring AbstractAspectJAdvice类下面的五个接口代表了常见的五种通知
对应五种通知类型:
@Before 前置通知
@AfterReturning 后置通知
@Around 环绕通知
@After 最终通知
@AfterThrowing 异常抛出通知
2. Pointcut 切点
Pointcut切点决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut通常意味着标识方法,例如:这些需要增强的地方可以由某个正则表达式进行标识,或者根据某个方法名称进行匹配。
为了方便用户使用,Spring AOP提供了具体的切点供用户使用,切点在Spring AOP的类继承体系如下:
Pointcut接口如下:
package org.springframework.aop; public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); Pointcut TRUE = TruePointcut.INSTANCE; }
可以看到需要返回一个Methodmatcher。对于Point的匹配判断功能,具体是由这个MethodMatcher 来完成的,也就是说这个MethodMatcher需要判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。
以JdkRegexpMethodPointcut 为例说明Pointcut的原理。其源码如下:
public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut { /** * Compiled form of the patterns. */ private Pattern[] compiledPatterns = new Pattern[0]; /** * Compiled form of the exclusion patterns. */ private Pattern[] compiledExclusionPatterns = new Pattern[0]; /** * Initialize {@link Pattern Patterns} from the supplied {@code String[]}. */ @Override protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException { this.compiledPatterns = compilePatterns(patterns); } /** * Initialize exclusion {@link Pattern Patterns} from the supplied {@code String[]}. */ @Override protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException { this.compiledExclusionPatterns = compilePatterns(excludedPatterns); } /** * Returns {@code true} if the {@link Pattern} at index {@code patternIndex} * matches the supplied candidate {@code String}. */ @Override protected boolean matches(String pattern, int patternIndex) { Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern); return matcher.matches(); } /** * Returns {@code true} if the exclusion {@link Pattern} at index {@code patternIndex} * matches the supplied candidate {@code String}. */ @Override protected boolean matchesExclusion(String candidate, int patternIndex) { Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate); return matcher.matches(); } /** * Compiles the supplied {@code String[]} into an array of * {@link Pattern} objects and returns that array. */ private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException { Pattern[] destination = new Pattern[source.length]; for (int i = 0; i < source.length; i++) { destination[i] = Pattern.compile(source[i]); } return destination; } }
matches 方法使用正则表达式来对方法名进行匹配。关于AOP框架中对matches 方法的调用会在下面介绍。
在SpringAOP中,还提供了其他的MethodPointcut,比如通过方法名称进行Advice匹配的NameMatchMethodPointcut, 它的匹配是方法名相同或者相匹配。如下:
@Override 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; } protected boolean isMatch(String methodName, String mappedName) { return PatternMatchUtils.simpleMatch(mappedName, methodName); }
3. Advisor 通知器
完成对目标方法的切面增强设计(Advice)和关注点(Pointcut)以后,需要一个对象把他们结合起来,完成这个作用的就是Advisor(通知器)。通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。在SpringAOP中,以一个Advisor的实现DefaultPointcutAdvisor 为例,了解Advisor的工作原理,其源码如下:
package org.springframework.aop.support; import java.io.Serializable; import org.aopalliance.aop.Advice; import org.springframework.aop.Pointcut; import org.springframework.lang.Nullable; @SuppressWarnings("serial") 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接口被定义为:
Pointcut TRUE = TruePointcut.INSTANCE;
TruePointcut.INSTANCE 是一个单例模式的对象,看名字就知道这个匹配器匹配任何方法。在TruePointcut 的MethodMatcher实现中,用TrueMethodMatcher 作为匹配器,同样是一个单例对象。
final class TruePointcut implements Pointcut, Serializable { public static final TruePointcut INSTANCE = new TruePointcut(); /** * Enforce Singleton pattern. */ private TruePointcut() { } @Override public ClassFilter getClassFilter() { return ClassFilter.TRUE; } @Override public MethodMatcher getMethodMatcher() { return MethodMatcher.TRUE; } /** * Required to support serialization. Replaces with canonical * instance on deserialization, protecting Singleton pattern. * Alternative to overriding {@code equals()}. */ private Object readResolve() { return INSTANCE; } @Override public String toString() { return "Pointcut.TRUE"; } }
org.springframework.aop.MethodMatcher#TRUE如下:
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
TrueMethodMatcher 源码如下:
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; } }
补充: SpringAOP和AspectJ的关系
aop是一种思想而不是一种技术。所以说,如果抛开spring,动态代理甚至静态代理都可以算是一种aop。
spring中的aop实现分为两种,基于动态代理的aop和基于AspectJ的aop。AspectJ是完全独立于Spring存在的一个Eclipse发起的项目。AspectJ甚至可以说是一门独立的语言,在java文件编译期间,织入字节码,改变原有的类。
我们常看到的在spring中用的@Aspect注解只不过是Spring2.0以后使用了AspectJ的风格而已,本质上还是Spring的原生实现。