前言
在springAOP的运用中,@Around, @Before, @After, @AfterReturning, @AfterThrowing的使用频率非常高,因此弄清他们的原理非常重要。
在学习其原理之前,我们先弄清这2个类JoinPoint和Advice
JoinPoint
JoinPoint作用:描述目标方法
public interface JoinPoint {
//连接点的缩写字符串表示法
String toString();
String toShortString();
String toLongString();
//获取代理对象
Object getThis();
//获取目标方法的对象
Object getTarget();
//目标方法的入参
Object[] getArgs();
//忽略部分代码.....
}
ProceedingJoinPoint作用:描述和调用目标对象
public interface ProceedingJoinPoint extends JoinPoint {
//忽略代码....
//调用没有入参的目标方法
public Object proceed() throws Throwable;
//调用有入参的目标方法
public Object proceed(Object[] args) throws Throwable;
}
Advice
Advice作用:建议忠告, 劝告, 通知。表示的是在 Pointcut 点上应该执行的方法。而这些方法可以在目标方法之前、之后、包裹、抛出异常等等任何地方执行。 其主要分成两类:普通advice 与Interceptor/MethodInterceptor
@Before
AspectJMethodBeforeAdvice
是解析 AspectJ 中的@Before
属性来生成的Advice- 其拦截器是
MethodBeforeAdviceInterceptor
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice, Serializable {
//忽略代码....
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
//忽略代码...
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//这是执行Before增强方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
//这是执行目标方法
return mi.proceed();
}
}
结论:Before增强方法,是把方法放在目标方法前。如果Before增强方法抛出异常,目标方法就不会执行
@Before案例
public class HelloService {
@Override
public void hello() {
System.out.println("调用hello方法...");
}
}
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() {}
@Before("pointcut()")
public void before1(JoinPoint joinPoint) throws Throwable {
System.out.println("执行Before方法....");
}
}
public class App {
public static void main( String[] args ){
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new HelloService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
HelloService proxy = proxyFactory.getProxy();
proxy.hello();
}
}
运行程序输出:
执行Before方法....
调用hello方法...
@After
- AspectJAfterAdvice即是一个After通知器,也是一个After拦截器
public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {
//忽略代码...
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//执行目标方法
return mi.proceed();
}
finally {
//执行After增强方法
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
}
结论:After增强方法是在目标方法执行之后调用。不管目标方法是否抛出异常。
After案例
public class HelloService {
@Override
public void hello() {
System.out.println("调用hello方法...");
int i = 0/0;
}
}
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() {}
@After("pointcut()")
public void before1(JoinPoint joinPoint) throws Throwable {
System.out.println("执行After方法....");
}
}
public class App {
public static void main( String[] args ){
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new HelloService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
HelloService proxy = proxyFactory.getProxy();
proxy.hello();
}
}
注意:我在hello方法执行了这段代码:0/0
运行程序输出
调用hello方法...
执行Before方法....
Exception in thread "main" java.lang.ArithmeticException: / by zero
....
@AfterReturning
- 其重要属性如下
@Target(ElementType.METHOD)
public @interface AfterReturning {
//绑定建议的切入点表达式
String value() default "";
//通知签名中要将返回值绑定到的参数的名称
String returning() default "";
}
AspectJAfterReturningAdvice是解析 AspectJ 中的 @AfterReturning 注解来生成的通知器,其拦截器是AfterReturningAdviceInterceptor
public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice implements AfterReturningAdvice, AfterAdvice,{
//忽略代码...
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
if (shouldInvokeOnReturnValueOf(method, returnValue)) {
invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
}
}
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
private final AfterReturningAdvice advice;
//忽略代码...
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//目标方法
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
}
结论:AfterReturning增强方法是在目标方法之后执行,如果目标方法执行错误,那么AfterReturning增强方法就不会执行
AfterReturning案例
public class HelloService {
public int hello() {
System.out.println("调用hello方法...");
return 1;
}
}
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() {}
@AfterReturning(value="pointcut()",returning="result")
public void before1(JoinPoint joinPoint,Object result) throws Throwable {
System.out.println("执行AfterReturning方法....");
System.out.println("目标方法返回值是"+result);
}
}
public class App {
public static void main( String[] args ){
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new HelloService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
HelloService proxy = proxyFactory.getProxy();
proxy.hello();
}
}
运行程序输出
调用hello方法...
执行AfterReturning方法....
目标方法返回值是1
@AfterThrowing
- 其重要属性如下
@Target(ElementType.METHOD)
public @interface AfterReturning {
//绑定建议的切入点表达式
String value() default "";
//通知签名中要将引发的异常绑定到的参数的名称
String throwing() default "";
}
- AspectJAfterThrowingAdvice即是解析 AspectJ 中的 @AfterThrowing 注解来生成的通知器,也是其拦截器
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice{
//忽略代码....
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//这里是调用目标方法
return mi.proceed();
}
catch (Throwable ex) {
if (shouldInvokeOnThrowing(ex)) {
//这里是AfterThrowing增强方法
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
//这里又把异常重新抛出去了
throw ex;
}
}
}
结论:AfterThrowing增强方法是目标方法抛出异常后调用的方法,其执行位置在catch块中。
AfterThrowing案例
public class HelloService {
public void hello() {
System.out.println("调用hello方法...");
int i = 0/0;
}
}
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() { }
//注意:joinPoint参数必须写在第一位
@AfterThrowing(value="pointcut()",throwing="exception")
public void before(JoinPoint joinPoint,Exception exception) throws Throwable {
System.out.println("执行AfterThrowing方法....");
System.out.println(exception.getMessage());
}
}
public class App {
public static void main( String[] args ) {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new HelloService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
HelloService proxy = proxyFactory.getProxy();
proxy.hello();
}
}
运行程序输出
Connected to the target VM, address: '127.0.0.1:49250', transport: 'socket'
调用hello方法...
执行AfterThrowing方法....
/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
@Around
- AspectJAroundAdvice即是解析 AspectJ 中的 @Around 注解来生成的通知器,也是其拦截器
个人理解:Around增加方法有些特殊,它是把目标方法变成增加方法,和上面的不同,上面的几个是添加增强方法
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
@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 pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
//调用Around增加方法
return invokeAdviceMethod(pjp, jpm, null, null);
}
}
案例
public class HelloService {
public int hello() {
System.out.println("调用hello方法...");
return 1;
}
}
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() { }
@Around("pointcut()")
public Object before(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前(@Around)");
Object result = joinPoint.proceed();
System.out.println("环绕后(@Around),结果是:"+result);
return result;
}
}
public class App {
public static void main( String[] args ) {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new HelloService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
HelloService proxy = proxyFactory.getProxy();
proxy.hello();
}
}
测试输出:
环绕前(@Around)
调用hello方法…
环绕后(@Around),结果是:1
如果把
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() { }
@Around("pointcut()")
public void before(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前(@Around)");
Object result = joinPoint.proceed();
System.out.println("环绕后(@Around),结果是:"+result);
}
}
测试输出:
环绕前(@Around)
调用hello方法...
环绕后(@Around),结果是:1
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match
1、为什么会报错:增强方法没有返回值与的目标方法返回类型不匹配
?`
因为目标方法有返回值,但是增强方法没有,可以理解这是一个消弱,不是增强,所以报错
但是注意:如果目标方法没有返回值,但是增强方法有,那就不会报错。把无返回值的方法变成有返回值,这也是一种增强吧
2、在上面几个增强方法的案例中,你可能有这样疑惑,入参ProceedingJoinPoint或者JoinPoint是怎么注入的
?
在这里我们以AspectJMethodBeforeAdvice代码为例子。
这里是调用增强方法的入空
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice, Serializable {
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
//getJoinPointMatch是获取匹配目标方法的对象
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
protected Object invokeAdviceMethod(
@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
throws Throwable {
//getJoinPoint获取切入点
//argBinding是绑定增强方法的参数
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}
protected JoinPoint getJoinPoint() {
return currentJoinPoint();
}
public static JoinPoint currentJoinPoint() {
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
//在缓存map中能获取以joinpoint为键的值
JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
//如果不能获取,那么就增加
if (jp == null) {
jp = new MethodInvocationProceedingJoinPoint(pmi);
pmi.setUserAttribute(JOIN_POINT_KEY, jp);
}
//返回切入值
return jp;
}
protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
@Nullable Object returnValue, @Nullable Throwable ex) {
//验证JoinPoint类型,下面有讲解
calculateArgumentBindings();
// AMC启动
Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
int numBound = 0;
if (this.joinPointArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
numBound++;
}
else if (this.joinPointStaticPartArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
numBound++;
}
if (!CollectionUtils.isEmpty(this.argumentBindings)) {
// 从切入点匹配绑定
if (jpMatch != null) {
PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();
for (PointcutParameter parameter : parameterBindings) {
String name = parameter.getName();
Integer index = this.argumentBindings.get(name);
adviceInvocationArgs[index] = parameter.getBinding();
numBound++;
}
}
// 绑定返回值
if (this.returningName != null) {
Integer index = this.argumentBindings.get(this.returningName);
adviceInvocationArgs[index] = returnValue;
numBound++;
}
// 绑定异常
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;
}
综上可述:增强方法的入参是AbstractAspectJAdvice
对象帮我们注入的。
3、在上面几个增强方法的案例中,你可能有些疑惑,为什么@Around增强方法使用ProceedingJoinPoint作为入参,而其他的增强方法不用?
一:从几个增强方法代码调用理解来看,只有@Around增强方法需要我们主动调用目标方法,其他几个不需要,所以没必要使用
ProceedingJoinPoint。二,spring定死的,如下代码:
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
public final synchronized void calculateArgumentBindings() {
// 简单的例子。。。没什么好绑的。
if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
return;
}
int numUnboundArgs = this.parameterTypes.length;
//获取参数类型的class对象
Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
//进行参数类型的class对象验证
//maybeBindJoinPoint是判断是否是JoinPoint类型
//maybeBindProceedingJoinPoint是判断是否是支持ProceedingJoinPoint类型
//AbstractAspectJAdvice的maybeBindProceedingJoinPoint默认返回false
//目前只有AspectJAroundAdvice重写这个方法,返回值是true
if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||
maybeBindJoinPointStaticPart(parameterTypes[0])) {
numUnboundArgs--;
}
if (numUnboundArgs > 0) {
// 需要按从切入点匹配返回的名称绑定参数
bindArgumentsByName(numUnboundArgs);
}
this.argumentsIntrospected = true;
}
增强方法顺序
@Aspect
public class MyAspect {
@Pointcut("execution(* hello(..))")
private void pointcut() { }
@After(value="pointcut()")
public void after(JoinPoint joinPoint) throws Throwable {
System.out.println("执行after方法....");
}
@Before(value="pointcut()")
public void before(JoinPoint joinPoint) throws Throwable {
System.out.println("执行before方法....");
}
@AfterReturning(value="pointcut()",returning="result")
public void afterReturning(JoinPoint joinPoint,Object result) throws Throwable {
System.out.println("执行afterReturning方法....");
}
@AfterThrowing(value="pointcut()",throwing="exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception) throws Throwable {
System.out.println("执行afterThrowing方法....");
}
@Around(value="pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行around前置方法方法....");
joinPoint.proceed();
System.out.println("执行around后置方法方法....");
}
}
目标方法正常输出`
执行around前置方法方法....
执行before方法....
调用hello方法...
执行afterReturning方法....
执行after方法....
执行around后置方法方法....
目标方法错误输出`
执行around前置方法方法....
执行before方法....
调用hello方法...
执行afterThrowing方法....
执行after方法....
影响其调用顺序的源码是
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {
private static final Comparator<Method> METHOD_COMPARATOR;
static {
//从这可以看出Around增强方法优先级最高
Comparator<Method> adviceKindComparator = new ConvertingComparator<>(
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
(Converter<Method, Annotation>) method -> {
AspectJAnnotation<?> annotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
return (annotation != null ? annotation.getAnnotation() : null);
});
Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
}
延伸: 不同aspect,advice的执行顺序
Spring AOP通过Order控制aspect的优先级,来控制不同aspect,advice的执行顺序,有两种方式:
1、Aspect 类添加注解:org.springframework.core.annotation.Order
,使用注解value属性指定优先级。
2、 Aspect 类实现接口:org.springframework.core.Ordered
,实现 Ordered 接口的 getOrder() 方法。