跟着小马哥学系列之 Spring AOP( Advice 组件详解)
- 简介
- Advice 类图
- 相关接口/类/注解介绍
-
- MethodInterceptor
- AspectJPrecedenceInformation
- Advice
- BeforeAdvice
- MethodBeforeAdvice
- AfterAdvice
- ThrowsAdvice
- AfterReturningAdvice
- Spring 整合 AspectJ 注解
学好路更宽,钱多少加班。 ——小马哥
简介
大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring AOP 编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。
Advice 类图
由类图可知:
- advice 分类以及代表接口:
- around(环绕):无代表接口(底层是通过 MethodInterceptor 实现)
- before(前置):BeforeAdvice
- after(后置)
- 最终:AfterAdvice
- 返回:AfterReturningAdvice
- 异常:ThrowsAdvice
- 实现:
- Spring 实现:
- around(环绕):MethodInterceptor
- before(前置):MethodBeforeAdvice + MethodBeforeAdviceInterceptor
- after(后置)
- AfterAdvice:MethodInterceptor
- AfterReturningAdvice:AfterReturningAdviceInterceptor
- ThrowsAdvice:ThrowsAdviceInterceptor
- 整合 AspectJ 实现:
- around(环绕):@Around 注解 (AspectJAroundAdvice)
- before(前置):@Before 注解(AspectJMethodBeforeAdvice 会被适配成 MethodBeforeInterceptor )
- after(后置):
- 最终:@After(AspectJAfterAdvice)
- 返回:@AfterReturning(AspectJAfterReturningAdvice 会通过 AdvisorAdapterRegistry 被适配成 AfterReturningAdviceInterceptor )
- 异常:@AfterThrowing(AspectJAfterThrowingAdvice)
- Spring 实现:
相关接口/类/注解介绍
MethodInterceptor
从类图以及 javadoc 中可知:
- MethodInterceptor 继承了 Interceptor ,而 Interceptor 又继承了 Advice。
- 这三个接口均来在 AOP 联盟中的包(org.aopalliance)。
- Advice: 接口是个标记接口,能表明任何类型的 Advice,比如拦截器
- Interceptor: 接口继承了 Advice 接口,但它也是一个标记接口。这个接口表示一个通用拦截器。通用拦截器可以拦截发生在基于程序中的运行时事件。这些事件是通过连接点具体化的。运行时连接点可以是调用、字段访问、异常……
- MethodInterceptor: 拦截对接口的调用在接口到达目标的过程中,它们嵌套在目标的“顶部”。用户应该实现
invoke(MethodInvocation)
方法来修改原始行为。最重要的 invoke(MethodInvocation invocation) 方法中的MethodInvocation
参数!实现此方法以在调用之前和之后执行额外的处理。优雅的实现当然希望调用Joinpoint.proceed()(MethodInvocation 也继承了这个接口,详情请参阅我的另外一篇 跟着小马哥学系列之 Spring AOP( ReflectiveMethodInvocation/CglibMethodInvocation 详解 )。
使用 MethodInterceptor 来达到环绕(around)类型的 advice
public class Something {
public String doSomething(String param) {
try {
System.out.println("参数是:" + param);
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return param;
}
}
public class AroundEffectAdviceDemo implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch sw = new StopWatch();
sw.start(invocation.getMethod().getName());
Object[] arguments = invocation.getArguments();
arguments[0] = "大叔文海";
// 控制目标方法要不要执行
Object returnValue = invocation.proceed();
sw.stop();
cost(invocation, sw.getTotalTimeMillis());
// 也可以对返回值进行修改
return returnValue + "返回值已被修改";
}
private void cost(MethodInvocation invocation, long ms) {
Method method = invocation.getMethod();
Object target = invocation.getThis();
Object[] arguments = invocation.getArguments();
System.out.printf("执行方法:%s, 目标对象:%s, 参数:%s, 耗时:%s ms\n", method,
target.getClass().getName(),
Arrays.toString(arguments),
ms);
}
public static void main(String[] args) {
Something something = new Something();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(something);
pf.addAdvice(new AroundEffectAdviceDemo());
Something proxy = (Something) pf.getProxy();
System.out.println(proxy.doSomething("文海"));
}
}
AspectJPrecedenceInformation
该接口可以提供根据 AspectJ 的优先规则(PartiallyComparableAdvisorHolder 搭配 AspectJPrecedenceComparator 和 AnnotationAwareOrderComparator)对 Advice/Advisor 进行排序所需的信息。 这些信息包括 aspect 名称、 在 aspect 中定义的顺序、是前置 Advice 还是 后置 Advice
Advice
此接口是个标记接口,能表明任何类型的 Advice,比如拦截器。为什么命名为 Advice ?从 javadoc 里面可知:源码开发者认为运行时 joinpoint 是发生在静态 joinpoint(即程序中的一个位置)上的事件。例如,调用方法(静态 joinpoint)就是运行时 joinpoint。由 Pointcut 筛选出满足条件的方法(由于 Spring 只支持方法级别)构成 joinpoint 然后进行 Advice
BeforeAdvice
继承 Advice 接口,表明是前置 Advice 类型的标记接口。Spring 只支持方法级别的前置 Advice。
MethodBeforeAdvice
继承了 BeforeAdvice 接口,主要提供了一个 before 方法。在调用方法之前调用的 Advice。这样的 Advice 不能阻止方法调用继续进行,除非它们抛出一个 Throwable。
示例
public interface EchoService {
String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return message;
}
}
public class SimpleBeforeAdvice implements MethodBeforeAdvice {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new SimpleBeforeAdvice());
pf.setTarget(echoService);
EchoService proxy = (EchoService) pf.getProxy();
// 控制台返回的是 文海大叔
System.out.println(proxy.echo("文海"));
}
@Override
public void before(@NonNull Method method, @NonNull Object[] args, Object target) throws Throwable {
// 方法调用参数
System.out.println(Arrays.toString(args));
// 如果放开注释则会报错阻止 echo 方法调用
// throw new Exception("MethodBeforeAdvice Exception");
// 修改方法参数
args[0] = "文海大叔";
}
}
源码解读
由于 Advice 是基于拦截器进行实现的,Spring 只支持方法级别的拦截。所以最终都会转换为方法拦截。
由于 Advice 是基于拦截器进行实现的,Spring 只支持方法级别的拦截。所以最终都会转换为方法拦截。
由于 Advice 是基于拦截器进行实现的,Spring 只支持方法级别的拦截。所以最终都会转换为方法拦截。
重要的事说三遍。
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 调用前置 advice 再执行调用链下一个方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
细节可以参阅我的另外写的 跟着小马哥学系列之 Spring AOP(AdvisorChainFactory 详解) 和 跟着小马哥学系列之 Spring AOP(AspectJAdvisorFactory 详解)
AfterAdvice
继承了 Advice ,表明是后置 advice 类型公共标记接口,例如 AfterReturningAdvice 和 ThrowsAdvice。
使用 MethodInterceptor 来达到最终(after)类型的 advice
public interface EchoService {
String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
// 有一半几率报错
if (new Random().nextBoolean()) {
throw new RuntimeException("运行出错啦:" + message);
}
return message;
}
}
public class AfterAdviceDemo implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocation.proceed();
} finally {
// 放入 finally 代码块,不管怎么样都会被执行,也可以通过 invocation 取出元信息进行处理
invokeAdviceMethod();
}
}
private void invokeAdviceMethod() {
System.out.println("不管有没有异常我都会被调用到");
}
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new AfterAdviceDemo());
pf.setTarget(echoService);
EchoService proxy = (EchoService) pf.getProxy();
System.out.println(proxy.echo("文海"));
}
}
ThrowsAdvice
继承了 AfterAdvice 接口,是 throws 类型的 advice 标记接口。这个接口上没有任何方法,因为方法是由反射调用的。实现类必须满足格式:
public void afterThrowing([Method, args, target], ThrowableSubclass)
该接口为什么没有任何方法?
因为 Java 异常类型太多,不可能每个异常类型都来一个重载的方法,怎么解决自定义异常。所以 Spring 规定了格式:public void afterThrowing([Method, args, target], ThrowableSubclass)
通过反射来调用。
示例
public class CustomThrowsAdviceDemo implements ThrowsAdvice {
// 处理 Exception 类型的异常
public void afterThrowing(Exception ex) {
System.out.println("***");
System.out.println("Caught:" + ex.getClass().getName());
System.out.println("***");
}
// 处理 IllegalArgumentException 类型的异常
public void afterThrowing(IllegalArgumentException ex) {
System.out.println("***");
System.out.println("Caught:" + ex.getClass().getName());
System.out.println("***");
}
// 处理 IllegalArgumentException 类型的异常并把一些元信息传过来
public void afterThrowing(Method method, Object[] args, Object target, IllegalArgumentException ex) {
System.out.println("***");
System.out.println("Caught:" + ex.getClass().getName());
System.out.println("Method: " + method.getName());
System.out.println("***");
}
public static void main(String[] args) {
ErrorClass errorClass = new ErrorClass();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(errorClass);
proxyFactory.addAdvice(new CustomThrowsAdviceDemo());
ErrorClass proxy = (ErrorClass) proxyFactory.getProxy();
try {
proxy.exceptionMethod();
} catch (Exception e) {
}
try {
proxy.illegalArgumentExceptionMethod();
} catch (IllegalArgumentException e) {
}
}
public static class ErrorClass{
public void exceptionMethod() throws Exception {
throw new Exception("Generic Exception");
}
public void illegalArgumentExceptionMethod() throws IllegalArgumentException {
throw new IllegalArgumentException("IllegalArgument Exception");
}
}
}
源码解读
// 怎样获取 ThrowsAdviceInterceptor 流程图在 MethodBeforeAdvice 中已说明
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
// 这里限制了方法名必须是 afterThrowing
private static final String AFTER_THROWING = "afterThrowing";
// advice 对象
private final Object throwsAdvice;
// 异常类型与方法映射(这也导致了同一种异常类型注册多个只有一个方法起效果)
private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();
public ThrowsAdviceInterceptor(Object throwsAdvice) {
Assert.notNull(throwsAdvice, "Advice must not be null");
this.throwsAdvice = throwsAdvice;
Method[] methods = throwsAdvice.getClass().getMethods();
for (Method method : methods) {
// 方法名必须是 afterThrowing 并且方法参数个数只能有 1 个或者 4个
if (method.getName().equals(AFTER_THROWING) &&
(method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
// 限制异常类型参数必须是参数列表的最后一位
if (Throwable.class.isAssignableFrom(throwableParam)) {
// 异常类型与方法进行映射,以便在发生异常时,直接找出对应的方法进行处理
this.exceptionHandlerMap.put(throwableParam, method);
if (logger.isDebugEnabled()) {
logger.debug("Found exception handler method on throws advice: " + method);
}
}
}
}
if (this.exceptionHandlerMap.isEmpty()) {
throw new IllegalArgumentException(
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
}
}
public int getHandlerMethodCount() {
return this.exceptionHandlerMap.size();
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 先调用拦截链中的方法。
return mi.proceed();
}
catch (Throwable ex) {
// 如果有异常则去异常映射方法(会递归往上找异常类型) Map 中取出对应方法进行处理,如果没找对应方法则把异常往上抛
Method handlerMethod = getExceptionHandler(ex);
if (handlerMethod != null) {
invokeHandlerMethod(mi, ex, handlerMethod);
}
throw ex;
}
}
@Nullable
private Method getExceptionHandler(Throwable exception) {
Class<?> exceptionClass = exception.getClass();
if (logger.isTraceEnabled()) {
logger.trace("Trying to find handler for exception of type [" + exceptionClass.getName() + "]");
}
Method handler = this.exceptionHandlerMap.get(exceptionClass);
while (handler == null && exceptionClass != Throwable.class) {
exceptionClass = exceptionClass.getSuperclass();
handler = this.exceptionHandlerMap.get(exceptionClass);
}
if (handler != null && logger.isTraceEnabled()) {
logger.trace("Found handler for exception of type [" + exceptionClass.getName() + "]: " + handler);
}
return handler;
}
private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
Object[] handlerArgs;
if (method.getParameterCount() == 1) {
handlerArgs = new Object[] {
ex};
}
else {
handlerArgs = new Object[] {
mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
}
try {
method.invoke(this.throwsAdvice, handlerArgs);
}
catch (InvocationTargetException targetEx) {
throw targetEx.getTargetException();
}
}
}
AfterReturningAdvice
扩展了 AfterAdvice,提供了
afterReturning
方法。只有在不抛出异常的普通方法正常返回之后被调用的 Advice(类型为返回 Advice
) 。这样的 Advice 可以看到返回值,但不能更改它。
示例
public interface EchoService {
String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
if (new Random().nextBoolean()) {
throw new RuntimeException("运行出错啦:" + message);
}
return message;
}
}
public class CustomAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, @NonNull Method method, @NonNull Object[] args, Object target) throws Throwable {
System.out.printf("返回值:%s, 方法: %s, 参数:%s, 目标对象:%s\n", returnValue, method.getName(), Arrays.toString(args), target.getClass().getSimpleName());
}
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new CustomAfterReturningAdvice());
pf.setTarget(echoService);
EchoService proxy = (EchoService) pf.getProxy();
// 如果 echo方法有异常,则返回 Advice 就不会执行
proxy.echo("文海");
}
}
源码分析
// 怎样获取 ThrowsAdviceInterceptor 流程图在 MethodBeforeAdvice 中已说明
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
private final AfterReturningAdvice advice;
public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 先执行拦截链
Object retVal = mi.proceed();
// 如果没有异常,将会调用返回 advice
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
}
Spring 整合 AspectJ 注解
@Aspect
定义一个 aspect(切面)。注解有 value 属性表明创建 aspect 实例方式,默认是空字符串,代表单例。注意在光标注 @Aspect 注解不会被 Spring IoC 容器自动识别,可以加上 @Component 注解或者通过 @Bean 等方式把 aspect 声明一个 Bean
@Around
环绕 advice 类型。value 属性是绑定 pointcut 表达式, argNames 属性是表达式中带有参数,指定参数名称
示例
public interface EchoService {
String echo(String message);
default Integer echo(Integer integer){
return integer;
}
@ProductPushAspect.ProductPush(ProductPushAspect.ProductStatusEnum.AUDIT)
default Integer productPushAnnotation(Integer integer) {
return integer;
}
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
if (new Random().nextBoolean()) {
throw new RuntimeException("发生异常啦!!!参数 message 为:" + message);
}
return message;
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProductPush {
ProductStatusEnum value();
}
public enum ProductStatusEnum {
// 提报审核,编辑,归档
AUDIT, EDIT, ARCHIVE
}
// 切面
@Aspect
public class AspectJAnnotationAspect {
// 框架会默认传递一个 ProceedingJoinPoint 类型的参数
@Around(value = "@annotation(productPush)", argNames = "joinPoint,productPush")
public Object around(ProceedingJoinPoint joinPoint, ProductPushAspect.ProductPush productPush) throws Throwable {
System.out.println("AspectJ around advice start ");
System.out.println("productPush 元信息: " + productPush.value().name());
Object retVal = joinPoint.proceed();
System.out.println("AspectJ around advice end ");
return retVal;
}
}
public class AspectJAnnotationDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(echoService);
factory.addAspect(AspectJAnnotationAspect.class);
EchoService proxy = factory.getProxy();
proxy.productPushAnnotation(2);
}
}
@Before
前置 Advice。value 属性是绑定 pointcut 表达式, argNames 属性是表达式中带有参数,指定参数名称
示例
// 通过 @ProductPush 注解筛选并把注解元信息当成参数带过来
@Before(value = "@annotation(productPush)", argNames = "productPush")
public void pushProductBefore(ProductPush productPush) {
}
// 没有 @ProductPush 元信息,只通过 @ProductPush 注解筛选
@Before(value = "@annotation(ProductPush)")
public void pushProductBefore() {
}
@After
最终 adivce 类型。value 属性是绑定 pointcut 表达式, argNames 属性是表达式中带有参数,指定参数名称。
@AfterReturning
返回 advice 类型。除了 value/pointcut 和 argNames 属性是之外有个 returning 属性,表示 Advice 签名中要绑定返回值的参数的名称
示例
public interface EchoService {
String echo(String message);
default Integer echo(Integer integer){
return integer;
}
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
if (new Random().nextBoolean()) {
throw new RuntimeException("运行出错啦:" + message);
}
return message;
}
}
@Aspect
public class AspectJAnnotationAspect {
@AfterReturning(value = "execution(public * com.wenhai.spring.aop.features.service.*.*(..)))", returning = "retVal")
public void afterReturning(String retVal) {
System.out.println("返回值是:" + retVal);
}
}
public class AspectJAnnotationDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(echoService);
factory.addAspect(AspectJAnnotationAspect.class);
EchoService proxy = factory.getProxy();
System.out.println(proxy.echo("文海"));
System.out.println(proxy.echo(1));
}
}
@AfterThrowing
异常 advice 类型,除了 value/pointcut 和 argNames 属性是之外有个 throwing 属性,表示 Advice 签名中要绑定抛出异常的参数的名称
示例
public interface EchoService {
String echo(String message);
default Integer echo(Integer integer){
return integer;
}
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
if (new Random().nextBoolean()) {
throw new RuntimeException("发生异常啦!!!参数 message 为:" + message);
}
return message;
}
}
@Aspect
public class AspectJAnnotationAspect {
@AfterReturning(value = "execution(public * com.wenhai.spring.aop.features.service.*.*(..)))", returning = "retVal")
public void afterReturning(String retVal) {
System.out.println("afterReturning 返回值是:" + retVal);
}
@AfterThrowing(value = "execution(public * com.wenhai.spring.aop.features.service.*.*(..)))", throwing = "ex")
public void afterThrowing(RuntimeException ex) {
System.out.println("afterThrowing 抛出的异常为:" + ex);
}
}
public class AspectJAnnotationDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(echoService);
factory.addAspect(AspectJAnnotationAspect.class);
EchoService proxy = factory.getProxy();
System.out.println(proxy.echo("文海"));
System.out.println(proxy.echo(1));
}
}
注意
任何 AspectJ Advice 注解声明的 advice 方法,都会把类型为 org.aspectj.lang.JoinPoint
或 org.aspectj.lang.JoinPoint.StaticPart
(@Around 是ProceedingJoinPoint ) 作为第一次参数传递。这个很有用可以获得以下元信息:
-
getArgs(): 返回方法参数
-
getThis(): 返回 proxy 对象
-
getTarget(): 返回目标对象
-
getSignature(): 返回方法签名
-
toString(): 打印所 advice 方法的有用描述
AbstractAspectJAdvice
根据 AOP 联盟
Advice
类包装标注 AspectJ 注解@Aspect
的类或方法上标注 AspectJ 注解@Around、@Before、@After 、@AfterReturning、@AfterThrowing
的基础类。
类图
构造器说明
public AbstractAspectJAdvice(
Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
// 标注 AspectJ 注解的方法的类即 aspect
this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
// 获取方法名称
this.methodName = aspectJAdviceMethod.getName();
// 获取 advice 方法参数类型
this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
// 获取 advice 方法
this.aspectJAdviceMethod = aspectJAdviceMethod;
// pointcut
this.pointcut = pointcut;
// aspect 实例工厂
this.aspectInstanceFactory = aspectInstanceFactory;
}
- Method:aspectJadviceMethod 标注 AspectJ Advice 类型注解方法
- AspectJExpreesionPointcut: AspectJ 语法表达式
- AspectInstanceFactory:aspectInstanceFactory 详情请参阅 跟着小马哥学系列之 Spring AOP(AspectJAdvisorFactory 详解)
字段说明
// 当前 joinpoint 的 ReflectiveMethodInvocation userAttributes 映射中使用的键。
protected static final String JOIN_POINT_KEY = JoinPoint.class.getName();
// advice 方法所在的类即 aspect 类
private final Class<?> declaringClass;
// advice 方法名称
private final String methodName;
// advice 方法参数
private final Class<?>[] parameterTypes;
// advice 方法
protected transient Method aspectJAdviceMethod;
// AspectJ 表达式的 pointcut
private final AspectJExpressionPointcut pointcut;
// aspect 实例工厂
private final AspectInstanceFactory aspectInstanceFactory;
// aspect 名称(在确定 advice 优先级时使用,以便我们可以确定两条 advice 是否来自同一个 aspect)
private String aspectName = "";
// 同一个 aspect 声明的顺序
private int declarationOrder;
// 如果此 advice 对象的创建者知道参数名并显式地设置它们,则该参数将是非空的(对应注解中 argNames 属性 )。
private String[] argumentNames;
// 对应 @AfterThrowing 注解 throwing 属性
private String throwingName;
// 对应 @AfterReturning 注解 returning 属性
private String returningName;
// 对应 @AfterReturning 注解 returning 属性的具体类型
private Class<?> discoveredReturningType = Object.class;
// 对应 @AfterThrowing 注解 throwing 属性的具体类型
private Class<?> discoveredThrowingType = Object.class;
// JoinPoint/ProceedingJoinPoint 参数的索引(当前仅支持索引0如果存在)。
private int joinPointArgumentIndex = -1;
// JoinPoint.StaticPart 参数的索引(如果存在的话,目前只支持索引0)。
private int joinPointStaticPartArgumentIndex = -1;
// 参数名称对应的参数顺序
private Map<String, Integer> argumentBindings;
// 参数是否已绑定
private boolean argumentsIntrospected = false;
// 返回泛型类型
private Type discoveredReturningGenericType;
方法解读
calculateArgumentBindings
用于 @AspectJ Advice 注解元信息到 Advice 方法参数绑定
public final synchronized void calculateArgumentBindings() {
// 参数绑定过了或者不需要绑定参数
if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
return;
}
// 参数绑定的个数
int numUnboundArgs = this.parameterTypes.length;
// 参数绑定的类型
Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
// advice 方法第一个参数类型是 JoinPoint 或者 ProceedingJoinPoint(必须是 Around 类型的 Advice) 或者是 JoinPoint.StaticPart 则把对应的参数索引(joinPointArgumentIndex 或 joinPointStaticPartArgumentIndex)设置为 0
if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||
maybeBindJoinPointStaticPart(parameterTypes[0])) {
// 个数减一
numUnboundArgs--;
}
// 如果大于 0 则需要绑定额外的参数
if (numUnboundArgs > 0) {
// 根据参数名称进行绑定参数
bindArgumentsByName(numUnboundArgs);
}
// 设置参数绑定标记为 true,下次再绑定直接跳过
this.argumentsIntrospected = true;
}
private void bindArgumentsByName(int numArgumentsExpectingToBind) {
// 如果在 advice 注解上没有明确指定 argNames 属性值则由 ParameterNameDiscoverert 自动找出
if (this.argumentNames == null) {
this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod);
}
if (this.argumentNames != null) {
// 参数名称已确定之后则根据是不同类型(returning、throwing 和 pointcut)进行绑定
bindExplicitArguments(numArgumentsExpectingToBind);
}
else {
throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " +
"requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " +
"the argument names were not specified and could not be discovered.");
}
}
private void bindExplicitArguments(int numArgumentsLeftToBind) {
Assert.state(this.argumentNames != null, "No argument names available");
this.argumentBindings = new HashMap<>();
// 需要绑定的个数跟 advice 方法上的参数数量不一样则报错
int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount();
if (this.argumentNames.length != numExpectedArgumentNames) {
throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames +
" arguments to bind by name in advice, but actually found " +
this.argumentNames.length + " arguments.");
}
// 这里主要是排除方法第一个参数是 JoinPoint 或者 ProceedingJoinPoint(必须是 Around 类型的 Advice) 或者是 JoinPoint.StaticPart
int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;
// 方法名称与方法位置进行缓存
for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {
this.argumentBindings.put(this.argumentNames[i], i);
}
// 如果指定了 returning 或者 throwing 参数名称,则找到对应的参数类型
if (this.returningName != null) {
if (!this.argumentBindings.containsKey(this.returningName)) {
throw new IllegalStateException("Returning argument name '" + this.returningName +
"' was not bound in advice arguments");
}
else {
Integer index = this.argumentBindings.get(this.returningName);
this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index];
this.discoveredReturningGenericType = this.aspectJAdviceMethod.getGenericParameterTypes()[index];
}
}
if (this.throwingName != null) {
if (!this.argumentBindings.containsKey(this.throwingName)) {
throw new IllegalStateException("Throwing argument name '" + this.throwingName +
"' was not bound in advice arguments");
}
else {
Integer index = this.argumentBindings.get(this.throwingName);
this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index];
}
}
// 如果参数不来自 returning 或者 throwing 则来自 pointcut 表达式中,要相应地配置 pointcut 表达式。
configurePointcutParameters(this.argumentNames, argumentIndexOffset);
}
private void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) {
int numParametersToRemove = argumentIndexOffset;
if (this.returningName != null) {
// 排除 retuning
numParametersToRemove++;
}
if (this.throwingName != null) {
// 排除 throwing
numParametersToRemove++;
}
String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];
Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];
Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();
int index = 0;
for (int i = 0; i < argumentNames.length; i++) {
if (i < argumentIndexOffset) {
continue;
}
// 排除 retuning 和 throwing
if (argumentNames[i].equals(this.returningName) ||
argumentNames[i].equals(this.throwingName)) {
continue;
}
// 根据方法参数名称的顺序设置 pointcut 表达式参数名称和对应的参数类型
pointcutParameterNames[index] = argumentNames[i];
pointcutParameterTypes[index] = methodParameterTypes[i];
index++;
}
// 设置 pointcut 表达式参数名
this.pointcut.setParameterNames(pointcutParameterNames);
// 设置 pointcut 表达式参数类型
this.pointcut.setParameterTypes(pointcutParameterTypes);
}
argBinding
在方法执行 joinpoint 获取参数,并将一组参数输出到 advice 方法(实参绑定)。
/**
* 在方法执行 joinpoint 获取参数,并将一组参数输出到 advice 方法(实参绑定)。
*
* @param jp 是当前 JoinPoint
* @param jpMathch 是匹配此执行的 joinpoint 的 JoinPointMatch
* @param returnValue 是方法执行返回值 (可能为 null)
* @param ex 是方法执行抛出的异常(可能为 null)
* @return 如果没有参数,则为空数组
*/
protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
@Nullable Object returnValue, @Nullable Throwable ex) {
// 上面已分析(参数名称绑定)
calculateArgumentBindings();
Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
int numBound = 0;
// 第一个参数需要绑定 JoinPoint 或者 ProceedingJoinPoint(必须是 Around 类型的 Advice) 或者是 JoinPoint.StaticPart
if (this.joinPointArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
numBound++;
}
else if (this.joinPointStaticPartArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
numBound++;
}
// 除了 JoinPoint/ProceedingJoinPoint/JoinPoint.StaticPart 之外的其他参数需要绑定
if (!CollectionUtils.isEmpty(this.argumentBindings)) {
// 从 pointcut match 进行绑定
if (jpMatch != null) {
// 从 jpMatch 中获取 PointcutParameter 列表,然后通过参数名称获取实参
PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();
for (PointcutParameter parameter : parameterBindings) {
String name = parameter.getName();
Integer index = this.argumentBindings.get(name);
adviceInvocationArgs[index] = parameter.getBinding();
numBound++;
}
}
// 绑定 returning
if (this.returningName != null) {
Integer index = this.argumentBindings.get(this.returningName);
adviceInvocationArgs[index] = returnValue;
numBound++;
}
// 绑定 throwing
if (this.throwingName != null) {
Integer index = this.argumentBindings.get(this.throwingName);
adviceInvocationArgs[index] = ex;
numBound++;
}
}
if (numBound != this.parameterTypes.length) {
throw new IllegalStateException("Required to bind " + this.parameterTypes.length +
" arguments, but only bound " + numBound + " (JoinPointMatch " +
(jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");
}
return adviceInvocationArgs;
}
invokeAdviceMethodWithGivenArgs
通过反射调用 Advice 方法
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 0) {
actualArgs = null;
}
try {
// 设置方法访问权限(即使是私有的也能访问)
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
// 这里就要用到 AspectInstanceFactory 对象了,我们通常把 Aspect 定义成一个类,
// 类中包括 Pointcut 和 Advice,要调用 Advice,就得拿到 Aspect 对象
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("Mismatch on arguments to advice method [" +
this.aspectJAdviceMethod + "]; pointcut expression [" +
this.pointcut.getPointcutExpression() + "]", ex);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
setArgumentNamesFromStringArray
设置 Advice 注解中 argNames 属性
public void setArgumentNamesFromStringArray(String... args) {
this.argumentNames = new String[args.length];
for (int i = 0; i < args.length; i++) {
// 去除空格
this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);
// 验证参数名是不是符合 Java 命名规则的变量名
if (!isVariableName(this.argumentNames[i])) {
throw new IllegalArgumentException(
"'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +
this.argumentNames[i] + "' that is not a valid Java identifier");
}
}
if (this.argumentNames != null) {
// 这里处理在注解 argNames 属性中没有指定
// 但是在方法参数列表第一个参数又是 JoinPoint/ProceedingJoinPoint/JoinPoint.StaticPart 情况
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
if (firstArgType == JoinPoint.class ||
firstArgType == ProceedingJoinPoint.class ||
firstArgType == JoinPoint.StaticPart.class) {
String[] oldNames = this.argumentNames;
this.argumentNames = new String[oldNames.length + 1];
this.argumentNames[0] = "THIS_JOIN_POINT";
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
}
}
}
}
getJoinPoint
重写 around 通知以返回正在进行的 joinPoint。
protected JoinPoint getJoinPoint() {
return currentJoinPoint();
}
public static JoinPoint currentJoinPoint() {
// 在获取 Advisor 的时候会添加这个 ExposeInvocationInterceptor(org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#extendAdvisors)
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
if (jp == null) {
// 新建一个 MethodInvocationProceedingJoinPoint 对象放入 ProxyMethodInvocation 用户属性中
jp = new MethodInvocationProceedingJoinPoint(pmi);
pmi.setUserAttribute(JOIN_POINT_KEY, jp);
}
return jp;
}
invokeAdviceMethod
如方法名 调用 advice 方法
// 非 Around advice 调用,里面的方法都已经分析过了
protected Object invokeAdviceMethod(
@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}
// Around advice 调用,里面的方法都已经分析过了
protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
@Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
}
setThrowingNameNoCheck/setReturningNameNoCheck
setThrowingNameNoCheck:我们需要在这个级别保存异常的名称,以便进行参数绑定计算,这个方法允许 afterThrowing advice 子类设置名称。
setReturningNameNoCheck:类似
protected void setThrowingNameNoCheck(String name) {
// 如果不是变量名称,当成类的全限定名来处理
if (isVariableName(name)) {
this.throwingName = name;
}
else {
try {
this.discoveredThrowingType = ClassUtils.forName(name, getAspectClassLoader());
}
catch (Throwable ex) {
throw new IllegalArgumentException("Throwing name '" + name +
"' is neither a valid argument name nor the fully-qualified " +
"name of a Java type on the classpath. Root cause: " + ex);
}
}
}
示例
public interface EchoService {
String echo(String message);
}
public class DefaultEchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
if (new Random().nextBoolean()) {
throw new RuntimeException("发生异常啦!!!参数 message 为:" + message);
}
return message;
}
}
@Aspect
public class AspectJAnnotationAspect {
@AfterThrowing(value = "execution(public * com.wenhai.spring.aop.features.service.*.*(..)))", throwing = "java.lang.RuntimeException")
public void afterThrowing() {
System.out.println("afterThrowing 抛出的异常为" );
}
}
public class AspectJAnnotationDemo {
public static void main(String[] args) {
EchoService echoService = new DefaultEchoServiceImpl();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(echoService);
factory.addAspect(AspectJAnnotationAspect.class);
EchoService proxy = factory.getProxy();
proxy.echo("文海");
}
}
AspectJAroundAdvice
Spring AOP around adivce(MethodInterceptor) 包装 AspectJ advice 方法 。暴露 ProceedingJoinPoint。
类图
由类图可知:
- 实现了
MethodInterceptor
接口,少一步适配步骤 - 继承了
AbstractAspectJAdvice
抽象类
方法解读
isBeforeAdvice/isAfterAdvice
实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return false;
}
supportsProceedingJoinPoint
覆写了父类方法(默认是 false)只有
around advice
参数才能绑定ProceedingJoinPoint
对象,
@Override
protected boolean supportsProceedingJoinPoint() {
return true;
}
invoke
实现了
MethodInterceptor#invoke
方法,advice 功能都是基于拦截器实现的。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
// 新建 ProceedingJoinPoint 对象绑定到 Around Advice 方法参数上
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
// 调用父类 getJoinPointMatch 方法获取 JoinPointMatch(在 AbstractAspectJAdvice 中已分析))
JoinPointMatch jpm = getJoinPointMatch(pmi);
// 调用父类 invokeAdviceMethod 方法(在 AbstractAspectJAdvice 中已分析)
return invokeAdviceMethod(pjp, jpm, null, null);
}
AspectJMethodBeforeAdvice
Spring AOP advice 包装 AspectJ 前置 advice 方法
类图
由类图可知:
- 实现了
MethodBeforeAdvice
接口 - 没有实现
MethodInterceptor
接口,多一步适配成MethodBeforeAdviceInterceptor
- 继承了
AbstractAspectJAdvice
抽象类
方法解读
isBeforeAdvice/isAfterAdvice
实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return true;
}
@Override
public boolean isAfterAdvice() {
return false;
}
before
实现了
MethodBeforeAdvice
中的 before 方法。会由MethodBeforeAdviceInterceptor#invoke
来调用
MethodBeforeAdviceInterceptor 源码
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 在拦截链执行之前调用 advice 之前 adivce 方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
AspectJMethodBeforeAdvice#before 方法源码
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
// 调用父类 getJoinPointMatch 和 invokeAdviceMethod (在 AbstractAspectJAdvice 中已分析)
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
AspectJAfterAdvice
Spring AOP advice 包装 AspectJ 最终 advice 方法
类图
由类图可知:
- 实现了
MethodInterceptor
接口,少一步适配步骤 - 继承了
AbstractAspectJAdvice
抽象类
方法解读
isBeforeAdvice/isAfterAdvice
实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return true;
}
invoke
实现了
MethodInterceptor#invoke
方法,advice 功能都是基于拦截器实现的。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 先执行拦截链中的方法,再finlly 代码块执行 最终 advice
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
AspectJAfterReturningAdvice
Spring AOP advice 包装 AspectJ 返回 advice 方法
类图
由类图可知:
- 实现了
AfterReturningAdvice
接口 - 没有实现
MethodInterceptor
接口,多一步适配成MethodBeforeAdviceInterceptor
- 继承了
AbstractAspectJAdvice
抽象类
方法解读
isBeforeAdvice/isAfterAdvice
实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return true;
}
setReturningName
覆盖父类方法,通过 @AfterReturing 注解中的
returing
属性值,与 Advice 方法参数名称进行绑定
@Override
public void setReturningName(String name) {
// 设置 returingName 属性或者设置 discoveredReturingType 属性(在父类已分析)
setReturningNameNoCheck(name);
}
afterReturning
实现了
AfterReturningAdvice#afterReturning
,会由AfterReturningAdviceInterceptor#invoke
来调用
AfterReturningAdviceInterceptor 源码
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
private final AfterReturningAdvice advice;
public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 调用拦截连中的方法
Object retVal = mi.proceed();
// 在调用 advice 返回 advice 方法,如果有异常则不知道
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
}
AspectJAfterReturningAdvice#afterReturning 源码
@Override
public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
// 返回值类型是否与 discoveredReturningType 或者 discoveredReturningGenericType 匹配
if (shouldInvokeOnReturnValueOf(method, returnValue)) {
invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
}
}
AspectJAfterThrowingAdvice
Spring AOP advice 包装 AspectJ 异常 advice 方法
类图
由类图可知:
- 实现了
MethodInterceptor
接口,少一步适配步骤 - 实现
AfterAdvice
接口 - 继承了
AbstractAspectJAdvice
抽象类
方法解读
isBeforeAdvice/isAfterAdvice
实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return true;
}
setThrowingName
覆盖父类方法,通过 @AfterThrowing 注解中的
throwing
属性值,与 Advice 方法参数名称进行绑定
@Override
public void setThrowingName(String name) {
// 设置 throwingName 属性或者设置 discoveredThrowingType 属性(在父类已分析)
setThrowingNameNoCheck(name);
}
invoke
实现了
MethodInterceptor#invoke
方法,advice 功能都是基于拦截器实现的。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 拦截链放在 try catch 语法块里面执行,如果有异常了,就执行 异常 advice
return mi.proceed();
}
catch (Throwable ex) {
// 异常类型是否与绑定的异常类型相匹配
if (shouldInvokeOnThrowing(ex)) {
// 调用 advice 方法
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}