Spring AOP编程官方文档解读之Advice API篇


Spring AOP编程官方文档解读目录



前言

上一章节当中,我们详细探讨了切入点在Spring AOP中对应的接口org.springframework.aop.Pointcut,通过该接口我们锁定了目标类或者方法。同样,在Spring AOP当中,Advice(增强)也有对应的接口org.aopalliance.aop.Advice。对应接口定义如下:

package org.aopalliance.aop;

/**
 * Tag interface for Advice. Implementations can be any type
 * of advice, such as Interceptors.
 *
 * @author Rod Johnson
 * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $
 */
public interface Advice {

}

该接口一个方法都没有。这是一个标识接口。切点虽然表现形式多样,但最终作用时是一样的,要么类匹配,要么方法匹配。而增强本质就多样,方法执行前,方法执行后,环绕方法,甚至不作用与方法只作用于类(引介增强),很难用一个方法来代表所有情况。下面我们依次来学习这些增强在Spring AOP中对应的接口

一、项目准备

在maven项目引入如下的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.27.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.27.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.27.RELEASE</version>
</dependency>

二、生命周期

在Spring当中每个Advice都是一个Spring的bean。一个Advice的实例可以在所有被增强实例中共享,也可以每个被增强实例独享,取决于perClass和perInstance参数。但是很奇怪,这个参数并不是属于Advice接口上面,而是在org.springframework.aop.Advisor接口当中,定义如下所示

/**
 * Return whether this advice is associated with a particular instance
 * (for example, creating a mixin) or shared with all instances of
 * the advised class obtained from the same Spring bean factory.
 * <p><b>Note that this method is not currently used by the framework.</b>
 * Typical Advisor implementations always return {@code true}.
 * Use singleton/prototype bean definitions or appropriate programmatic
 * proxy creation to ensure that Advisors have the correct lifecycle model.
 * @return whether this advice is associated with a particular target instance
 */
boolean isPerInstance();

Each advice is a Spring bean. An advice instance can be shared across all advised objects, or unique to each advised object. This corresponds to per-class or per-instance advice.

如果不保存状态的话,进行共享是最普遍的。但是在源码当中,这个参数都是返回true,但是这个参数配置目前在Spring当中是没有使用到的。所以暂时可以不关注。

Per-class advice is used most often. It is appropriate for generic advice such as transaction advisors. These do not depend on the state of the proxied object or add new state; they merely act on the method and arguments.

Per-instance advice is appropriate for introductions, to support mixins. In this case, the advice adds state to the proxied object.

It’s possible to use a mix of shared and per-instance advice in the same AOP proxy.

三、主要接口

1. 拦截器环绕接口 Interception around advice

Spring当中最基本的增强就是拦截器环绕接口。Spring与AOP联盟接口兼容,可使用方法拦截获得环绕增强。 实现环绕增强应该实现以下接口org.aopalliance.intercept.MethodInterceptor.

public interface MethodInterceptor extends Interceptor {
	/**
	 * Implement this method to perform extra treatments before and
	 * after the invocation. Polite implementations would certainly
	 * like to invoke {@link Joinpoint#proceed()}.
	 * @param invocation the method invocation joinpoint
	 * @return the result of the call to {@link Joinpoint#proceed()};
	 * might be intercepted by the interceptor
	 * @throws Throwable if the interceptors or the target object
	 * throws an exception
	 */
	Object invoke(MethodInvocation invocation) throws Throwable;
}

该接口继承了接口org.aopalliance.intercept.Interceptor,后者又继承了org.aopalliance.aop.Advice.实现该接口只需要继承一个invoke方法,这个方法的参数就跟前面我们在Aspect切面定义中的JointPoint是一样的,可以获取到目标方法、方法参数等重要信息。
在这里插入图片描述
从继承结构上看,其实它也实现了org.aopalliance.intercept.Joinpoint接口。

创建一个切面类并使用环绕增强拦截器

@Component
public class SimpleAdvisor implements PointcutAdvisor {

    @Override
    public Advice getAdvice() {
        return new SimpleMethodInteceptor();
    }

    @Override
    public boolean isPerInstance() {
        return true;
    }

    @Override
    public Pointcut getPointcut() {
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression("execution(public * *(..)) && within(com.example.aop.advice.service..*)");
        return aspectJExpressionPointcut;
    }


    static class SimpleMethodInteceptor implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("方法参数 arguments = " + Arrays.toString(invocation.getArguments()));
            System.out.println("目标方法 method = " + invocation.getMethod());
            System.out.println("当前对象 this = " + invocation.getThis());
            System.out.println("----------" + invocation.getMethod().getName() + "增强-------------");
            Object obj = invocation.proceed();
            System.out.println("返回对象 obj = " + obj);
            return obj;
        }
    }
}

执行结果如下所示
在这里插入图片描述
需要注意的时,跟其他环绕增强一样,该接口可以通过拦截返回参数并修改返回参数、也可以捕获异常并按需处理。但是除非你有充分的理由,否则都不建议这样做。

MethodInterceptors offer interoperability with other AOP Alliance-compliant AOP implementations. The other advice types discussed in the remainder of this section implement common AOP concepts, but in a Spring-specific way. While there is an advantage in using the most specific advice type, stick with MethodInterceptor around advice if you are likely to want to run the aspect in another AOP framework. Note that pointcuts are not currently interoperable between frameworks, and the AOP Alliance does not currently define pointcut interfaces.

2. 前置增强 Before advice

前置增强对应的接口为org.springframework.aop.MethodBeforeAdvice.

public interface MethodBeforeAdvice extends BeforeAdvice {

	/**
	 * Callback before a given method is invoked.
	 * @param method method being invoked
	 * @param args arguments to the method
	 * @param target target of the method invocation. May be {@code null}.
	 * @throws Throwable if this object wishes to abort the call.
	 * Any exception thrown will be returned to the caller if it's
	 * allowed by the method signature. Otherwise the exception
	 * will be wrapped as a runtime exception.
	 */
	void before(Method method, Object[] args, Object target) throws Throwable;
}

该方法在目标方法执行执行被触发,参数包含了目标方法、参数以及目标对象。简单的实现如下:

static class SimpleBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("方法参数 args = " + Arrays.toString(args));
        System.out.println("目标方法 method = " + method);
        System.out.println("目标对象 target = " + target);
        System.out.println("----------" + method.getName() + "增强-------------");
    }
}

执行结果如下
在这里插入图片描述
由于该拦截器是在目标方法执行执行的,所以不需要传递MethodInvocation参数,也不需要为方法执行传递负责,这样就避免了因为编码疏忽忘记方法传递的风险。

请注意,返回类型为void。 通知可以在联接点执行之前插入自定义行为,但不能更改返回值。 如果之前的建议引发异常,则会中止拦截器链的进一步执行。 异常将传播回拦截链。 如果是非受检异常,它将被直接传递给客户端。 否则它将被AOP代理包装在非检查异常中。

  • 受检异常进行包装为UndeclaredThrowableException异常
    在这里插入图片描述
  • 非受检异常
    在这里插入图片描述

If a before advice throws an exception, this will abort further execution of the interceptor chain. The exception will propagate back up the interceptor chain. If it is unchecked, or on the signature of the invoked method, it will be passed directly to the client; otherwise it will be wrapped in an unchecked exception by the AOP proxy.

具体执行逻辑参考源码:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor

3. 异常增强 Throws advice

对应的接口为org.springframework.aop.ThrowsAdvice,该接口定义如下

public interface ThrowsAdvice extends AfterAdvice {

}

这个结果继承了org.springframework.aop.AfterAdvice,而后者继承了org.aopalliance.aop.Advice,但是这三个接口都没有任何的接口方法。它是一个标签接口,用于标识给定对象实现了一个或多个类型化的throws通知方法。 这些形式应为:

afterThrowing([Method, args, target], subclassOfThrowable)

仅最后一个参数是必需的。 方法签名可以具有一个或四个参数,具体取决于增强方法是否对该方法和参数感兴趣。 以下类是异常增强的示例。这个增强类在RemoteException抛出时被触发,当然也包括这个异常的子类。

public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

由于没有明确的接口方法定义,实际查找处理方法是如何处理的呢?对应的源码在org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor当中。
首先看一下构造函数

private static final String AFTER_THROWING = "afterThrowing";

/** Methods on throws advice, keyed by exception class */
private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<Class<?>, Method>();


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
		if (method.getName().equals(AFTER_THROWING)) {
			Class<?>[] paramTypes = method.getParameterTypes();
			// 参数长度必须为1或者4
			if (paramTypes.length == 1 || paramTypes.length == 4) {
				// 而且最后一个参数必须是用于接收异常类型的
				Class<?> throwableParam = paramTypes[paramTypes.length - 1];
				if (Throwable.class.isAssignableFrom(throwableParam)) {
					// An exception handler to register...
					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() + "]");
	}
}

从源代码不难看出
1. 首先必须包含有效方法存放到exceptionHandlerMap当中,否则会抛出IllegalArgumentException异常
2. 有效方法名称必须为afterThrowing
3. 方法参数个数为1个或和4个
4. 方法参数的最后一个必须为异常类型
5. 可以有多个有效方法
6. 如果多个方法包含相同的异常类型,那么会发生覆盖的问题。因为exceptionHandlerMap是以异常类型作为key的。相同异常类型,后面的方法会覆盖前面的方法。

如果执行过程中连接点抛出异常,那么会首先根据异常类型获取处理方法(getExceptionHandler

public Object invoke(MethodInvocation mi) throws Throwable {
	try {
		return mi.proceed();
	}
	catch (Throwable ex) {
		Method handlerMethod = getExceptionHandler(ex);
		if (handlerMethod != null) {
			invokeHandlerMethod(mi, ex, handlerMethod);
		}
		throw ex;
	}
}

根据异常类型查询异常处理方法

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.isDebugEnabled()) {
		logger.debug("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.getParameterTypes().length == 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();
	}
}
  1. 如果是4个参数的方法,那么前三个参数类型为Method、Object[]、Object
    因此可以定义如下异常增强类
public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        System.out.println("--------RemoteException--afterThrowing-------------" + ex);
    }

    public void afterThrowing(Method m, Object[] args, Object target, Exception ex) {
        // Do something with all arguments
        System.out.println("----------" + m.getName() + "afterThrowing-------------" + ex);
        // 此时会覆盖原有的异常
        throw new RuntimeException("-------afterThrowing-Exception----------");
    }
}

在这里插入图片描述
如果异常增强方法自己抛出了异常,将会使问题变得复杂,原来的异常将被覆盖。
在这里插入图片描述

If a throws-advice method throws an exception itself, it will override the original exception (i.e. change the exception thrown to the user). The overriding exception will typically be a RuntimeException; this is compatible with any method signature. However, if a throws-advice method throws a checked exception, it will have to match the declared exceptions of the target method and is hence to some degree coupled to specific target method signatures. Do not throw an undeclared checked exception that is incompatible with the target method’s signature!

在这里插入图片描述

4. 返回结果增强 AfterReturningAdvice

对应的接口为org.springframework.aop.AfterReturningAdvice

public interface AfterReturningAdvice extends AfterAdvice {

	void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;

}

这个增强和前置增强一样都比较简单。能够获取到返回的结果,但是不能进行修改。但是如果抛出异常,它将被抛出拦截器链,而不是返回值。
一个简单示例如下

public static class CountingAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        // This advice doesn’t change the execution path. If it throws an exception, this will be thrown up the interceptor chain instead of the return value.
        System.out.println("----------" + m.getName() + "--afterReturning-------------" + returnValue);
//            throw new RuntimeException("-------afterReturning-Exception----------");
    }
}

在这里插入图片描述

5. 引介增强 Introduction advice

与以上面的各种增强都不同的是,引介增强针对的不是方法而是类,另外引介增强只能使用在org.springframework.aop.IntroductionAdvisor类型切面当中,因为不需要针对方法匹配的切面。(对于引介增强不存在所谓的连接点)。

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

	ClassFilter getClassFilter();

	void validateInterfaces() throws IllegalArgumentException;

}

这个接口实现了org.springframework.aop.IntroductionInfo,通过方法getInterfaces定义增强类需要额外实现的接口。

public interface IntroductionInfo {

	Class<?>[] getInterfaces();

}

对应的引介增强类型必须为org.springframework.aop.IntroductionInterceptor

假设我们现在需要给所有的业务类(包含注解@Service)都实现一个接口(UsagTracker)作为标识。
接口定义如下所示

package com.example.aop.advice;

public interface UsagTracker {
}

切面类定义如下

package com.example.aop.advice;

import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Component
public class SimpleIntroductionAdvisor implements IntroductionAdvisor {
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return AnnotationUtils.findAnnotation(clazz, Service.class) != null;
            }
        };
    }

    @Override
    public void validateInterfaces() throws IllegalArgumentException {

    }

    @Override
    public Advice getAdvice() {
        return new IntroductionInterceptor() {
            @Override
            public boolean implementsInterface(Class<?> intf) {
                return true;
            }

            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                return invocation.proceed();
            }
        };
    }

    @Override
    public boolean isPerInstance() {
        return false;
    }

    @Override
    public Class<?>[] getInterfaces() {
        return new Class[]{UsagTracker.class};
    }
}

在这里插入图片描述
在Spring当中,可以根据这个增强接口查找标识的服务类
在这里插入图片描述
以上案例仅仅是作为标识接口使用,引介增强还可以作为一个公共工具类使用。比如说要对一些特定的类做特殊操作,比如上锁和解锁,在上锁期间,不能调用某类方法。
比如存在以下接口

package com.example.aop.advice;

public interface Lockable {
    void lock();

    void unlock();

    boolean locked();
}

我们现在要使目标方法实现这个接口,当想锁定目标类的add方法的时候,就调用lock方法,如果目标类的add方法被调用,那么就抛出LockedException异常。实现如下

package com.example.aop.advice;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    @Override
    public void lock() {
        this.locked = true;
    }

    @Override
    public void unlock() {
        this.locked = false;
    }

    @Override
    public boolean locked() {
        return this.locked;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("add") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}
package com.example.aop.advice;

import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.stereotype.Component;

@Component
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

执行结果如下:如果锁定之后,调用就会抛出异常
在这里插入图片描述
另外在这里,我们使用了org.springframework.aop.support.DelegatingIntroductionInterceptor,这时一个IntroductionInterceptor接口的实现类,针对invoke方法做了如下包装(首先针对增强的方法做了处理),让编程更方便。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
	if (isMethodOnIntroducedInterface(mi)) {
		// Using the following method rather than direct reflection, we
		// get correct handling of InvocationTargetException
		// if the introduced method throws an exception.
		Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());

		// Massage return value if possible: if the delegate returned itself,
		// we really want to return the proxy.
		if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
			Object proxy = ((ProxyMethodInvocation) mi).getProxy();
			if (mi.getMethod().getReturnType().isInstance(proxy)) {
				retVal = proxy;
			}
		}
		return retVal;
	}

	return doProceed(mi);
}

总结

本章针对Spring中常见的增强类接口类进行了说明。使用起来还是比较easy的。需要注意在增强方法中不能抛出异常。这会导致情况变得复杂。如果前置增强抛出了异常会导致目标方法不会执行,返回结果增强抛出了异常,目标方法是正常执行了,但是最后正常返回结果被异常代替了,而异常增强方法抛出了异常,也会覆盖原有异常,这两种情况都掩盖了目标方法的执行结果,需要格外注意。拦截器环绕接口功能最强大,拦截结果、拦截异常,甚至针对结果过和异常进行修改,除非必要,不建议过分使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值