技术标签: spring java Spring AOP 后端
学好路更宽,钱多少加班。 ——小马哥
大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring AOP 编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。
由类图可知:
从类图以及 javadoc 中可知:
invoke(MethodInvocation)
方法来修改原始行为。最重要的 invoke(MethodInvocation invocation) 方法中的 MethodInvocation
参数!实现此方法以在调用之前和之后执行额外的处理。优雅的实现当然希望调用Joinpoint.proceed()(MethodInvocation 也继承了这个接口,详情请参阅我的另外一篇 跟着小马哥学系列之 Spring AOP( ReflectiveMethodInvocation/CglibMethodInvocation 详解 )。
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("文海"));
}
}
该接口可以提供根据 AspectJ 的优先规则(PartiallyComparableAdvisorHolder 搭配 AspectJPrecedenceComparator 和 AnnotationAwareOrderComparator)对 Advice/Advisor 进行排序所需的信息。 这些信息包括 aspect 名称、 在 aspect 中定义的顺序、是前置 advice 还是 后置 advice
此接口是个标记接口,能表明任何类型的 advice,比如拦截器。为什么命名为 Advice ?从 javadoc 里面可知:源码开发者认为运行时 joinpoint 是发生在静态 joinpoint(即程序中的一个位置)上的事件。例如,调用方法(静态 joinpoint)就是运行时 joinpoint。由 pointcut 筛选出满足条件的 joinpoint,然后在 joinpoint 上发生操作(advice)。
继承 Advice 接口,表明是前置 Advice 类型的标记接口。Spring 只支持方法级别的前置 Advice。
继承了 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 详解)
继承了 Advice ,表明是后置 advice 类型公共标记接口,例如 AfterReturningAdvice 和 ThrowsAdvice。
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("文海"));
}
}
继承了 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();
}
}
}
扩展了 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;
}
}
定义一个 aspect(切面)。注解有 value 属性表明创建 aspect 实例模型,默认是空字符串,代表单例。注意光标注 @Aspect 注解不会被 Spring IoC 容器自动识别,可以加上 @Component 注解或者通过 @Bean 等方式把 aspect 声明一个 Bean。
环绕 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);
}
}
前置 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() {
}
最终 advice 类型。value 属性是 pointcut 表达式, argNames 指定表达式中参数名称,用于 advice 方法参数绑定。
返回 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));
}
}
异常 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 方法的有用描述
根据 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;
用于 @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);
}
实参绑定
/**
* 在方法执行 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;
}
通过反射调用 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();
}
}
设置 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);
}
}
}
}
重写 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;
}
如方法名 调用 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:我们需要在这个级别保存异常的名称,以便进行参数绑定计算,这个方法允许 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("文海");
}
}
Spring AOP around adivce(MethodInterceptor) 包装 AspectJ advice 方法 。暴露 ProceedingJoinPoint。
由类图可知:
MethodInterceptor
接口,少一步适配步骤AbstractAspectJAdvice
抽象类实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return false;
}
覆写了父类方法(默认是 false)只有
around advice
参数才能绑定ProceedingJoinPoint
对象,
@Override
protected boolean supportsProceedingJoinPoint() {
return true;
}
实现了
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);
}
Spring AOP advice 包装 AspectJ 前置 advice 方法
由类图可知:
MethodBeforeAdvice
接口MethodInterceptor
接口,多一步适配成 MethodBeforeAdviceInterceptor
AbstractAspectJAdvice
抽象类实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return true;
}
@Override
public boolean isAfterAdvice() {
return false;
}
实现了
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);
}
Spring AOP advice 包装 AspectJ 最终 advice 方法
由类图可知:
MethodInterceptor
接口,少一步适配步骤AbstractAspectJAdvice
抽象类实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return true;
}
实现了
MethodInterceptor#invoke
方法,advice 功能都是基于拦截器实现的。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 先执行拦截链中的方法,再finlly 代码块执行 最终 advice
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
Spring AOP advice 包装 AspectJ 返回 advice 方法
由类图可知:
AfterReturningAdvice
接口MethodInterceptor
接口,多一步适配成 MethodBeforeAdviceInterceptor
AbstractAspectJAdvice
抽象类实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return true;
}
覆盖父类方法,通过 @AfterReturing 注解中的
returing
属性值,与 Advice 方法参数名称进行绑定
@Override
public void setReturningName(String name) {
// 设置 returingName 属性或者设置 discoveredReturingType 属性(在父类已分析)
setReturningNameNoCheck(name);
}
实现了
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);
}
}
Spring AOP advice 包装 AspectJ 异常 advice 方法
由类图可知:
MethodInterceptor
接口,少一步适配步骤AfterAdvice
接口AbstractAspectJAdvice
抽象类实现 AspectJPrecedenceInformation 用于同一个 aspect 排序(
AspectJPrecedenceComparator#comparePrecedenceWithinAspect
)
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return true;
}
覆盖父类方法,通过 @AfterThrowing 注解中的
throwing
属性值,与 advice 方法参数名称进行绑定
@Override
public void setThrowingName(String name) {
// 设置 throwingName 属性或者设置 discoveredThrowingType 属性(在父类已分析)
setThrowingNameNoCheck(name);
}
实现了
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;
}
}
文章浏览阅读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
文章浏览阅读1.4w次,点赞9次,收藏84次。本文在 https://blog.csdn.net/sj063658/article/details/88299577 基础上进行了排版优化处理,使其看起来更清晰有条理。版权所有属于原作者综述ASAP2标准是一个比较复杂的标准,详细的一条一条讲解标准内容并没有太大的价值,我们将主要以一种应用的方式来带领大家认识ASAP2标准理解作为ASAP2表现形式的A2L文件的作用,最后学会如何阅读和修..._a2l变量物理值转换
文章浏览阅读558次,点赞4次,收藏4次。在迁移老项目的数据库时,使用Navicat Premium的数据传输功能同步了表结构和数据。但是,发现某些字段的数据类型出现了错误,例如,租户ID从Oracle的NUMBER类型变成了MySQL的,正确的应该是bigInt(20)。此外,逻辑删除标记DEL_FLAG也出错,应该是int(1),但现在是decimal类型。由于涉及到数百个表,手动更改显然不现实。下面来看看如何实现批量修改这些字段的数据类型。
文章浏览阅读807次,点赞21次,收藏22次。1.背景介绍在当今的数字时代,平台治理开发已经成为一种重要的技术手段,用于确保平台的稳定性和容错性。在大数据、人工智能和云计算等领域,平台治理开发的重要性更加突显。本文将从以下几个方面进行深入探讨:平台治理开发的背景与意义平台治理开发的核心概念与联系平台治理开发的核心算法原理和具体操作步骤平台治理开发的具体代码实例平台治理开发的未来发展趋势与挑战平台治理开发的常见问题与解答...
文章浏览阅读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
文章浏览阅读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类实现先进先出队列的数据操
文章浏览阅读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 自适应波特率
文章浏览阅读504次,点赞16次,收藏2次。在视频创作的世界里,拥有一个可靠的素材来源是成功的关键之一。这些网站涵盖了从自然风光到城市生活,从抽象动画到实际操作的各种视频素材,希望能帮助你找到完美匹配你创意的素材。创作是一个既富有挑战也充满乐趣的过程,愿这些建议能够激发你的创意灵感,帮助你制作出更多精彩的视频作品。无论你是希望通过视频讲述故事,还是通过影像传达信息,高质量的无水印素材都能帮助你,以下这八个全球各地的视频素材网站将为你的创作之旅提供宝贵的资源。优点:提供大量适合中文用户的视频素材,非常适合中国市场。提供免费和高级订阅服务的视频素材。
文章浏览阅读2.5k次。//人脸检测和人脸比对百度ai人脸检测//1.获取access_token//每次更新access_token//获取client_id和client_secret使用百度ai的下面这个已经失效//client_id=YXtYiFxEUU7OBFF4sG6K1v88&client_secret=j1a5FdWp4jvGzwS0n37hzy1kKh9rIQog//uni.request(..._uni-app 人脸对比虹软
文章浏览阅读2.4k次。需求静默安装软件,动态配置参数解决第一步:按引导创建脚本,这部分就不描述了; Script generated by the Inno Setup Script Wizard.; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!#define MyAppName &quot;My Test&quot;#defi..._inno setup如何修改用户信息框说明
文章浏览阅读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
文章浏览阅读551次。demo在线下载地址(完整代码包含插件地址)http://www.shagua.wiki/project/3?p=125_select多选下拉框 源码