因為Spring在運行期把切面中的代碼邏輯動態“織入”到了容器物件方法內,讓開發者能無感知地在容器物件方法前后任意添加代碼片段,所以AOP其實就是個代理模式,凡是代理,由于代碼不可直接閱讀,是 bug 的重災區,
案例
某游戲系統,含負責點券充值的類CouponService,它含有一個充值方法deposit():

deposit()會使用微信支付充值,因此在這個方法中,加入pay(),
由于微信支付是第三方介面,需記錄介面呼叫時間,
引入 @Around 增強 ,分別記錄在pay()方法執行前后的時間,并計算pay()執行耗時,

Controller:

訪問介面,會發現這段計算時間的切面并沒有執行到,輸出日志如下:

切面類明明定義了切面對應方法,但卻沒執行到,說明在類的內部,通過this呼叫的方法,不會被AOP增強,
決議
- this對應的物件就是一個普通CouponService物件:

- 而在Controller層中自動裝配的CouponService物件:

是個被Spring增強過的Bean,所以執行deposit()時,會執行記錄介面呼叫時間的增強操作,而this對應的物件只是一個普通的物件,并無任何額外增強,
為什么this參考的物件只是一個普通物件?
要從Spring AOP增強物件的程序來看,
實作
AOP的底層是動態代理,創建代理的方式有兩種:
- JDK方式
只能對實作了介面的類生成代理,不能針對普通類 - CGLIB方式
可以針對類實作代理,主要是對指定的類生成一個子類,覆寫其中的方法,來實作代理物件,

針對非Spring Boot程式,除了添加相關AOP依賴項外,還會使用 @EnableAspectJAutoProxy 開啟AOP功能,
這個注解類引入AspectJAutoProxyRegistrar,它通過實作ImportBeanDefinitionRegistrar介面完成AOP相關Bean準備作業,
現在來看下創建代理物件的程序,先來看下呼叫堆疊:

- 創建代理物件的時機
創建一個Bean時
創建的的關鍵作業由AnnotationAwareAspectJAutoProxyCreator完成
AnnotationAwareAspectJAutoProxyCreator
一種BeanPostProcessor,所以它的執行是在完成原始Bean構建后的初始化Bean(initializeBean)程序中
AbstractAutoProxyCreator#postProcessAfterInitialization
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回傳,
AbstractAutoProxyCreator#wrapIfNecessary
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;
}
// 省略非關鍵代碼
}
createProxy
創建代理物件的關鍵:
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注解
通過 @Autowired,在類的內部,自己參考自己:


直接從AopContext獲取當前Proxy
AopContext,就是通過一個ThreadLocal來將Proxy和執行緒系結起來,這樣就可以隨時拿出當前執行緒系結的Proxy,
使用該方案有個前提,需要在 @EnableAspectJAutoProxy 加配置項 exposeProxy = true ,表示將代理物件放入到ThreadLocal,這才可以直接通過
AopContext.currentProxy()
獲取到,否則報錯:

于是修改代碼:

勿忘修改EnableAspectJAutoProxy 的 exposeProxy屬性:

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/301955.html
標籤:java
上一篇:LeetCode 213. 打家劫舍 II【c++/java詳細題解】
下一篇:編程筆試(決議及代碼實作):猴子吃桃。猴子第一天吃了若干個桃子,當即吃了一半,還不解饞,又多吃了一個…的C++、Java、Python、C#等語言代碼實作
