Spring AOP調用本類方法為什么沒有生效


首先請思考一下以下代碼執行的結果:

  • LogAop.java

//聲明一個AOP攔截service包下的所有方法
@Aspect
public class LogAop {

@Around("execution(* com.demo.service.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
try {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object ret = joinPoint.proceed();
//執行完目標方法之后打印
System.out.println("after execute method:"+method.getName());
return ret;
} catch (Throwable throwable) {
throw throwable;
}
}
}
  • UserService.java

@Service
public class UserService{

public User save(User user){
//省略代碼
}

public void sendEmail(User user){
//省略代碼
}

//注冊
public void register(User user){
//保存用戶
this.save(user);
//發送郵件給用戶
this.sendEmail(user);
}
}
  • UserServiceTest.java

@SpringBootTest
public class UserServiceTest{

@Autowired
private UserService userService;

@Test
public void save(){
userService.save(new User());
}

@Test
public void sendEmail(){
userService.sendEmail(new User());
}

@Test
public void register(){
UserService.register(new User());
}
}

在執行save方法后,控制台輸出為:

after execute method:save

在執行sendEmail方法后,控制台輸出為:

after execute method:sendEmail

請問在執行register()方法后會打印出什么內容?

反直覺

 

這個時候可能很多人都會和我之前想的一樣,在register方法里調用了save和sendEmail,那 AOP 會處理save和sendEmail,輸出:

after execute method:save
after execute method:sendEmail
after execute method:register

然而事實並不是這樣的,而是輸出:

after execute method:register

在這種認知的情況下,很可能就會寫出有bug的代碼,例如:

@Service
public class UserService{
//用戶下單一個商品
public void order(User user,String orderId){
Order order = findOrder(orderId);
pay(user,order);
}

@Transactional
public void pay(User user,Order order){
//扣款
user.setMoney(user.getMoney()-order.getPrice());
save(user);
//...其它處理
}
}

當用戶下單時調用的order方法,在該方法里面調用了@Transactional注解修飾的pay方法,這個時候pay方法的事務管理已經不生效了,在發生異常時就會出現問題。

理解 AOP

 

我們知道 Spring AOP默認是基於動態代理來實現的,那么先化繁為簡,只要搞懂最基本的動態代理自然就明白之前的原因了,這里直接以 JDK 動態代理為例來演示一下上面的情況。

關注公眾號程序員小樂回復關鍵字“Java”獲取Java面試題和答案。

由於 JDK 動態代理一定需要接口類,所以首先聲明一個IUserService接口

IUserService.java
public interface IUserService{
User save(User user);
void sendEmail(User user);
User register(User user);
}

編寫實現類

  • UserService.java

public class UserService implements IUserService{

@Override
public User save(User user){
//省略代碼
}

@Override
public void sendEmail(User user){
//省略代碼
}

//注冊
@Override
public void register(User user){
//保存用戶
this.save(user);
//發送郵件給用戶
this.sendEmail(user);
}
}

編寫日志處理動態代理實現

  • ServiceLogProxy.java

public static class ServiceLogProxy {
public static Object getProxy(Class<?> clazz, Object target) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = method.invoke(target, args);
System.out.println("after execute method:" + method.getName());
return ret;
}
});
}
}

運行代碼

  • Main.java

public class Main{
public static void main(String[] args) {
//獲取代理類
IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService());
userService.save(new User());
userService.sendEmail(new User());
userService.register(new User());
}
}

結果如下:

after execute method:save
after execute method:sendEmail
after execute method:register

可以發現和之前 Spring AOP 的情況一樣,register方法中調用的save和sendEmail方法同樣的沒有被動態代理攔截到,這是為什么呢,接下來就看看下動態代理的底層實現。

關注公眾號程序員小樂回復關鍵字“offer”獲取算法面試題和答案。

動態代理原理

 

其實動態代理就是在運行期間動態的生成了一個class在 jvm 中,然后通過這個class的實例調用真正的實現類的方法,偽代碼如下:

public class $Proxy0 implements IUserService{

//這個類就是之前動態代理里的new InvocationHandler(){}對象
private InvocationHandler h;
//從接口中拿到的register Method
private Method registerMethod;

@Override
public void register(User user){
//執行前面ServiceLogProxy編寫好的invoke方法,實現代理功能
h.invoke(this,registerMethod,new Object[]{user})
}
}

回到剛剛的main方法,那個userService變量的實例類型其實就是動態生成的類,可以把它的 class 打印出來看看:

IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService());
System.out.println(userService.getClass());

輸出結果為:

xxx.xxx.$Proxy0

在了解這個原理之后,再接着解答之前的疑問,可以看到通過代理類的實例執行的方法才會進入到攔截處理中,而在InvocationHandler#invoke()方法中,是這樣執行目標方法的:

//注意這個target是new UserService()實例對象
Object ret = method.invoke(target, args);
System.out.println("after execute method:" + method.getName());

在register方法中調用this.save和this.sendEmail方法時,this是指向本身new UserService()實例,所以本質上就是:

User user = new User();
UserService userService = new UserService();
userService.save(user);
userService.sendEmail(user);

不是動態代理生成的類去執行目標方法,那必然不會進行動態代理的攔截處理中,明白這個之后原理之后,就可以改造下之前的方法,讓方法內調用本類方法也能使動態代理生效,就是用動態代理生成的類去調用方法就好了,改造如下:

  • UserService.java

public class UserService implements IUserService{

//注冊
@Override
public void register(User user){
//獲取到代理類
IUserService self = (IUserService) ServiceLogProxy.getProxy(IUserService.class, this);
//通過代理類保存用戶
self.save(user);
//通過代理類發送郵件給用戶
self.sendEmail(user);
}
}

運行main方法,結果如下:

after execute method:save
after execute method:sendEmail
after execute method:save
after execute method:sendEmail
after execute method:register

可以看到已經達到預期效果了。

Spring AOP 中方法調用本類方法的解決方案

 

同樣的,只要使用代理類來執行目標方法就行,而不是用this引用,修改后如下:

@Service
public class UserService{

//拿到代理類
@Autowired
private UserService self;

//注冊
public void register(User user){
//通過代理類保存用戶
self.save(user);
//通過代理類發送郵件給用戶
self.sendEmail(user);
}
}

好了,問題到此就解決了,但是需要注意的是Spring官方是不提倡這樣的做法的,官方提倡的是使用一個新的類來解決此類問題,例如創建一個UserRegisterService類:

@Service
public class UserRegisterService{
@Autowired
private UserService userService;

//注冊
public void register(User user){
//通過代理類保存用戶
userService.save(user);
//通過代理類發送郵件給用戶
userService.sendEmail(user);
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM