跟着小马哥学系列之 Spring AOP( Advice 组件详解)_afterreturningadvice-程序员宅基地

技术标签: spring  java  Spring AOP  后端  

学好路更宽,钱多少加班。 ——小马哥

版本修订

  • 2021.5.19:去除目录,修正定义中不正确的地方

简介

大家好,我是小马哥成千上万粉丝中的一员!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)

相关接口/类/注解介绍

MethodInterceptor

在这里插入图片描述

从类图以及 javadoc 中可知:

  1. MethodInterceptor 继承了 Interceptor ,而 Interceptor 又继承了 Advice。
  2. 这三个接口均来在 AOP 联盟中的包(org.aopalliance)。
  3. Advice: 接口是个标记接口,能表明任何类型的 Advice,比如拦截器
  4. Interceptor: 接口继承了 Advice 接口,但它也是一个标记接口。这个接口表示一个通用拦截器。通用拦截器可以拦截发生在基于程序中的运行时事件。这些事件是通过连接点具体化的。运行时连接点可以是调用、字段访问、异常……
  5. 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 筛选出满足条件的 joinpoint,然后在 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 属性是表达式中带有参数,指定参数名称用于 advice 方法参数绑定

示例

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 指定表达式中参数名称,用于 advice 方法参数绑定。

示例
// 通过 @ProductPush 注解筛选并把注解元信息当成参数带过来
@Before(value = "@annotation(productPush)", argNames = "productPush")
public void pushProductBefore(ProductPush productPush) {
    
    
}
// 没有 @ProductPush 元信息,只通过 @ProductPush 注解筛选
@Before(value = "@annotation(ProductPush)")
public void pushProductBefore() {
    

}

@After

最终 advice 类型。value 属性是 pointcut 表达式, argNames 指定表达式中参数名称,用于 advice 方法参数绑定。

@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.JoinPointorg.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;
}

字段说明
// 当前 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 方法(实参绑定)。
 * 
 * @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 advice 以返回正在进行的 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 类似 setThrowingNameNoCheck

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;
	}
}

AspectJ 大总结(一张图搞定)

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42419406/article/details/116811597

智能推荐

虚拟机中安装的CentOS8无法启动网卡使用网络_because device is strictly unmanaged-程序员宅基地

文章浏览阅读9.3k次,点赞43次,收藏84次。之前都用得好好的CentOS8系统,突然不能上网了,图形界面也没有有线连接选项。首先需要查看网卡名:通过查看/etc/sysconfig/network-scripts目录下的文件确定网卡名为ens33通过ifconfig命令查看知道网卡名了,使用命令激活nmcli c up ens33出现错误:Connection 'ens33' is not available on device ens33 because device is strictly unmanaged有一种临时方案_because device is strictly unmanaged

A2L文件解析_a2l变量物理值转换-程序员宅基地

文章浏览阅读1.4w次,点赞9次,收藏84次。本文在 https://blog.csdn.net/sj063658/article/details/88299577 基础上进行了排版优化处理,使其看起来更清晰有条理。版权所有属于原作者综述ASAP2标准是一个比较复杂的标准,详细的一条一条讲解标准内容并没有太大的价值,我们将主要以一种应用的方式来带领大家认识ASAP2标准理解作为ASAP2表现形式的A2L文件的作用,最后学会如何阅读和修..._a2l变量物理值转换

手摸手系列之批量修改MySQL数据库所有表中某些字段的类型-程序员宅基地

文章浏览阅读558次,点赞4次,收藏4次。在迁移老项目的数据库时,使用Navicat Premium的数据传输功能同步了表结构和数据。但是,发现某些字段的数据类型出现了错误,例如,租户ID从Oracle的NUMBER类型变成了MySQL的,正确的应该是bigInt(20)。此外,逻辑删除标记DEL_FLAG也出错,应该是int(1),但现在是decimal类型。由于涉及到数百个表,手动更改显然不现实。下面来看看如何实现批量修改这些字段的数据类型。

平台治理开发的容错性与稳定性-程序员宅基地

文章浏览阅读807次,点赞21次,收藏22次。1.背景介绍在当今的数字时代,平台治理开发已经成为一种重要的技术手段,用于确保平台的稳定性和容错性。在大数据、人工智能和云计算等领域,平台治理开发的重要性更加突显。本文将从以下几个方面进行深入探讨:平台治理开发的背景与意义平台治理开发的核心概念与联系平台治理开发的核心算法原理和具体操作步骤平台治理开发的具体代码实例平台治理开发的未来发展趋势与挑战平台治理开发的常见问题与解答...

webRTC(二):Nodejs搭建服务器_webrtc nodejs-程序员宅基地

文章浏览阅读2.9k次,点赞2次,收藏8次。一、搭建http服务器'use strict'var http =require('http');var app=http.createServer(function(req,res){ res.writeHead(200,{'Content-Type':'text/plain'}); res.end('Http:Hello World\n');}).listen(8081..._webrtc nodejs

用java来实现FIFO先进先出的队列_java请编写类fifoqueue实现接口collection,fifoqueue类实现先进先出队列-程序员宅基地

文章浏览阅读6.8k次,点赞7次,收藏20次。简单实现队列先进先出:package com;import java.util.LinkedList;public class MyQueue{ private LinkedList list = new LinkedList(); public void put(Object t){ //加入数据 list.addFirst(t); }..._java请编写类fifoqueue实现接口collection,fifoqueue类实现先进先出队列的数据操

随便推点

CAN 总线波特率的自适应算法设计_stm32 can 自适应波特率-程序员宅基地

文章浏览阅读635次,点赞8次,收藏14次。通常,原有 CAN 网络的节点都是会通过总线向网络广播报文的,因此,加入网络的新节点也可以根据接收报文的状态来修正波特率,从而达到自适应网络波特率的目的。基于式(1)~(5)及采样点的设置规则,确定实验所用的波特率列表,列表中共包含15个常用波特率,取值分别为:20kb/s、33.33kb/s、40kb/s、50kb/s、66.66kb/s、 80kb/s、100kb/s、125kb/s、200kb/s、250kb/s、400kb/s、 500kb/s、666kb/s、800kb/s和1000kb/s。_stm32 can 自适应波特率

情感视频素材从哪里找?8个视频素材网站免费高清-程序员宅基地

文章浏览阅读504次,点赞16次,收藏2次。在视频创作的世界里,拥有一个可靠的素材来源是成功的关键之一。这些网站涵盖了从自然风光到城市生活,从抽象动画到实际操作的各种视频素材,希望能帮助你找到完美匹配你创意的素材。创作是一个既富有挑战也充满乐趣的过程,愿这些建议能够激发你的创意灵感,帮助你制作出更多精彩的视频作品。无论你是希望通过视频讲述故事,还是通过影像传达信息,高质量的无水印素材都能帮助你,以下这八个全球各地的视频素材网站将为你的创作之旅提供宝贵的资源。优点:提供大量适合中文用户的视频素材,非常适合中国市场。提供免费和高级订阅服务的视频素材。

uni-app人脸检测和人脸比对_uni-app 人脸对比虹软-程序员宅基地

文章浏览阅读2.5k次。//人脸检测和人脸比对百度ai人脸检测//1.获取access_token//每次更新access_token//获取client_id和client_secret使用百度ai的下面这个已经失效//client_id=YXtYiFxEUU7OBFF4sG6K1v88&client_secret=j1a5FdWp4jvGzwS0n37hzy1kKh9rIQog//uni.request(..._uni-app 人脸对比虹软

Inno Setup 系列之自定义窗口动态修改配置参数_inno setup如何修改用户信息框说明-程序员宅基地

文章浏览阅读2.4k次。需求静默安装软件,动态配置参数解决第一步:按引导创建脚本,这部分就不描述了; Script generated by the Inno Setup Script Wizard.; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!#define MyAppName &amp;quot;My Test&amp;quot;#defi..._inno setup如何修改用户信息框说明

OpenMVG与OpenMVS安装配置、简单使用_openmvg+openmvs-程序员宅基地

文章浏览阅读2.1w次,点赞14次,收藏140次。关于OpenMVG与OpenMVS之间的关系可见下图。关于目前常用的三维重建系统的对比见网址:http://leohope.com/%E8%A7%A3%E9%97%AE%E9%A2%98/2018/03/06/compare-re3d-system/可见OpenMVG与OpenMVS搭配使用,可以实现一个完美的三维重建流程。下面开始讲解两者的配置与简单使用: 1. 编译 o..._openmvg+openmvs

layui select下拉框实现多选功能(附代可运行源码)_select多选下拉框 源码-程序员宅基地

文章浏览阅读551次。demo在线下载地址(完整代码包含插件地址)http://www.shagua.wiki/project/3?p=125_select多选下拉框 源码