之前在开发过程中使用 @AfterReturning ,想要在切面方法中改变目标方法的返回值,但是却发现这样做不行。当时没有想到原因,后来猜测 @AfterReturing 标记的切面方法可能在 finnally 块中被调用所以不能改变返回值。今天经过测试发现这个猜测是错误的,在 finally 块中是可以改变返回值的,因为此时方法还没有返回。
public class FinallyAlterReturnValDemo {
public static String saySomething(){
return "say something ...";
}
public static Object invoke(){
String val;
try {
val = saySomething();
return val;
}catch (Exception e){
}finally {
val = "changed return value";
return val;
}
}
public static void main(String[] args) {
Object invoke = invoke();
System.out.println(invoke);
// 执行结果是 changed return value
}
}
对Spring AOP 的 @AfterReturning 进行测试
public interface Say {
String saySomething();
}
@Component
public class Target implements Say{
@Override
public String saySomething(){
return "say something ... ";
}
}
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* Created by j4love on 2016/12/22.
* Description :
*/
@Component
@org.aspectj.lang.annotation.Aspect
@EnableAspectJAutoProxy()
public class Aspect {
@Pointcut(value = "execution(* kernel.spring.AOP.bean.*.*(..))")
public void pointcut(){}
@AfterReturning(pointcut = "pointcut()",returning = "retVal")
public Object afterReturning(JoinPoint jp,Object retVal){
System.out.println("afterReturning ---- " + retVal);
return "change ... " + retVal;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="kernel.spring.AOP" />
</beans>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Created by j4love on 2016/12/22.
* Description :
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:conf/spring/applicationContext-aop.xml")
public class TargetTest {
@Autowired
private Say target;
@Test
public void saySomething() throws Exception {
// Object o = AopContext.currentProxy();
System.out.println("isJdkProxy = " + AopUtils.isJdkDynamicProxy(this.target));
System.out.println("isCglibProxy = " + AopUtils.isCglibProxy(this.target));
String s = this.target.saySomething();
System.out.println(s);
}
}
AfterReturning 确实是无法改变返回值的,查看源码发现原因,下面贴出关键源码。
package org.springframework.aop.framework.adapter;
import java.io.Serializable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.util.Assert;
/**
* Interceptor to wrap am {@link org.springframework.aop.AfterReturningAdvice}.
* Used internally by the AOP framework; application developers should not need
* to use this class directly.
*
* @author Rod Johnson
*/
@SuppressWarnings("serial")
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
private final AfterReturningAdvice advice;
/**
* Create a new AfterReturningAdviceInterceptor for the given advice.
* @param advice the AfterReturningAdvice to wrap
*/
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();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
}
程序运行,目标方法被调用后,会调用 invoke(MethodInvocation mi) throws Throwable 方法,这里就会去调用目标方法,也就是说目标方法在这里就执行完了。retVal 就是目标方法的返回值。而 afterReturing 是在目标方法执行返回之后才去调用的,并且 aftreReturing 方法返回 void 所以,尽管我在@AfterReturing 标记的切面方法中有返回值也没什么作用。整个 invoke 方法最终返回的是目标方法的返回值 retVal 。这下把 @afterReturing 为什么没法改变返回值搞清楚了。