因為Spring在執行期把切面中的程式碼邏輯動態「織入」到了容器物件方法內,讓開發者能無感知地在容器物件方法前後任意新增程式碼片段,所以AOP其實就是個代理模式。凡是代理,由於程式碼不可直接閱讀,是 bug 的重災區。
某遊戲系統,含負責點券充值的類CouponService,它含有一個充值方法deposit():
deposit()會使用微信支付充值。因此在這個方法中,加入pay()。
由於微信支付是第三方介面,需記錄介面呼叫時間。
引入 @Around 增強 ,分別記錄在pay()方法執行前後的時間,並計算pay()執行耗時。
Controller:
存取介面,會發現這段計算時間的切面並沒有執行到,輸出紀錄檔如下:
切面類明明定義了切面對應方法,但卻沒執行到。說明在類的內部,通過this呼叫的方法,不會被AOP增強。
為什麼this參照的物件只是一個普通物件?
要從Spring AOP增強物件的過程來看。
AOP的底層是動態代理,建立代理的方式有兩種:
針對非Spring Boot程式,除了新增相關AOP依賴項外,還會使用 @EnableAspectJAutoProxy 開啟AOP功能。
這個註解類引入AspectJAutoProxyRegistrar,它通過實現ImportBeanDefinitionRegistrar介面完成AOP相關Bean準備工作。
現在來看下建立代理物件的過程。先來看下呼叫棧:
建立的的關鍵工作由AnnotationAwareAspectJAutoProxyCreator完成
一種BeanPostProcessor。所以它的執行是在完成原始Bean構建後的初始化Bean(initializeBean)過程中
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
關鍵方法wrapIfNecessary:在需要使用AOP時,它會把建立的原始Bean物件wrap成代理物件,作為Bean返回。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 省略非關鍵程式碼
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
// 省略非關鍵程式碼
}
建立代理物件的關鍵:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
// ...
// 1. 建立一個代理工廠
ProxyFactory proxyFactory = new ProxyFactory();
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
// 2. 將通知器(advisors)、被代理物件等資訊加入到代理工廠
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
// ...
// 3. 通過代理工廠獲取代理物件
return proxyFactory.getProxy(getProxyClassLoader());
}
經過這樣一個過程,一個代理物件就被建立出來了。我們從Spring中獲取到的物件都是這個代理物件,所以具有AOP功能。而之前直接使用this參照到的只是一個普通物件,自然也就沒辦法實現AOP的功能了。
只有參照的是被動態代理建立出來的物件,才會被Spring增強,具備AOP該有的功能。
什麼樣的物件具備這樣條件?
通過 @Autowired,在類的內部,自己參照自己:
AopContext,就是通過一個ThreadLocal來將Proxy和執行緒繫結起來,這樣就可以隨時拿出當前執行緒繫結的Proxy。
使用該方案有個前提,需要在 @EnableAspectJAutoProxy 加設定項 exposeProxy = true ,表示將代理物件放入到ThreadLocal,這才可以直接通過
AopContext.currentProxy()
獲取到,否則報錯:
於是修改程式碼:
勿忘修改EnableAspectJAutoProxy 的 exposeProxy屬性: