首先請思考一下如下代碼執行的結果:html
//聲明一個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; } } }
@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); } }
@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
方法後,控制檯輸出爲:java
after execute method:save
在執行sendEmail
方法後,控制檯輸出爲:git
after execute method:sendEmail
請問在執行register()
方法後會打印出什麼內容?github
這個時候可能不少人都會和我以前想的同樣,在register
方法裏調用了save
和sendEmail
,那 AOP 會處理save
和sendEmail
,輸出:spring
after execute method:save after execute method:sendEmail after execute method:register
然而事實並非這樣的,而是輸出:jvm
after execute method:register
在這種認知的狀況下,極可能就會寫出有bug
的代碼,例如:ide
@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
方法的事務管理已經不生效了,在發生異常時就會出現問題。this
咱們知道 Spring AOP 默認是基於動態代理來實現的,那麼先化繁爲簡,只要搞懂最基本的動態代理天然就明白以前的緣由了,這裏直接以 JDK 動態代理爲例來演示一下上面的狀況。代理
因爲 JDK 動態代理必定須要接口類,因此首先聲明一個IUserService
接口日誌
public interface IUserService{ User save(User user); void sendEmail(User user); User register(User user); }
編寫實現類
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); } }
編寫日誌處理動態代理實現
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; } }); } }
運行代碼
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
方法一樣的沒有被動態代理攔截到,這是爲何呢,接下來就看看下動態代理的底層實現。
其實動態代理就是在運行期間動態的生成了一個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);
不是動態代理生成的類去執行目標方法,那必然不會進行動態代理的攔截處理中,明白這個以後原理以後,就能夠改造下以前的方法,讓方法內調用本類方法也能使動態代理生效,就是用動態代理生成的類去調用方法就行了,改造以下:
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
能夠看到已經達到預期效果了。
一樣的,只要使用代理類來執行目標方法就行,而不是用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); } }