新聞中心
SpringAOP是Spring中除了依賴注入以外最為核心的功能,其原理是利用CGlib和JDK動態(tài)代理等方式來實現(xiàn)運行期動態(tài)方法增強,從而降低系統(tǒng)耦合,提升代碼的復(fù)用性。

成都創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于做網(wǎng)站、網(wǎng)站設(shè)計、萬載網(wǎng)絡(luò)推廣、小程序制作、萬載網(wǎng)絡(luò)營銷、萬載企業(yè)策劃、萬載品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供萬載建站搭建服務(wù),24小時服務(wù)熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com
不過,在享受AOP強大功能便利的同時,我們也會經(jīng)常遇到一些看起來莫名其妙的bug。
今天,我們來聊一聊,為什么說在AOP方法中,不要輕易使用this調(diào)用方法?
使用了this會出現(xiàn)什么樣的情況?背后的原理是什么?又該如何解決??
廢話不多說,直接實戰(zhàn)上代碼。
場景復(fù)現(xiàn)
假設(shè)我們有一個核心支付類,其中有pay()支付功能,同時會通過record()方法記錄都有哪些用戶訪問過這個核心功能。
@Service
public class PayService {
public void pay(){
System.out.println("執(zhí)行一些核心支付業(yè)務(wù)操作");
//記錄用戶訪問日志
this.record();
}
public void record(){
try {
System.out.println("模擬將操作記錄投遞到日志系統(tǒng),耗時100ms");
TimeUnit.MICROSECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
隨著業(yè)務(wù)的不斷擴大,我們需要統(tǒng)計一下保存訪問日志這個動作的耗時情況,看看是否會對核心支付功能有較大的影響。
所以,我們使用SpringAOP進行了切面處理。
@Aspect
@Component
public class LogAspect {
@Around(value = "execution(* com.shishan.demo2023.service.PayService.record()) )")
public Object record(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object proceed = proceedingJoinPoint.proceed();
System.out.println("記錄日志耗時:" + (System.currentTimeMillis() - begin));
return proceed;
}
}
切面類很簡單,通過@Around方法對record方法進行切入切出,并記錄該方法的執(zhí)行時間。
?看起來很完美是不是?
老代碼不用改動,只需新增一個切面就可以實現(xiàn)新的需求。
我們新建一個controller,看看我們的切面類有沒有生效。
@RestController
@RequestMapping(value = "/demo")
public class DemoController {
@Resource
private PayService payService;
@RequestMapping(value = "/pay")
public ResponseEntity
啟動服務(wù),訪問
http://localhost:8080/demo/pay。
問題出現(xiàn)了。
按照上面的代碼,在打印完業(yè)務(wù)日志之后,應(yīng)該打印一行記錄日志耗時的日志。然而控制臺卻空空如也,說明我們的切面類并沒有生效。
?為什么定義的切面沒有執(zhí)行呢?
問題就出現(xiàn)在pay()方法中的this調(diào)用上。?
我們可以看到,圖中的this指向的是一個普通的PayService對象,而不是被Spring增強后的bean。
而SpringAOP起作用的原理是什么:Spring通過JDK動態(tài)代理和CGlib代理對目標(biāo)類生成一個代理類,在代理類中做功能增強。
我們看一下在controller中的PayService:
可以看到,在controller中的payService是一個被SpringCLlib增強后的代理類,而我們通過this引用到的,對于Spring來說只是一個普通的bean對象,自然無法實現(xiàn)AOP的功能。
那Spring在什么時候會對一個對象進行代理呢?
Spring會在一個bean創(chuàng)建的時候判斷是否要進行代理,核心類是
AnnotationAwareAspectJAutoProxyCreator,其本質(zhì)是一個BeanPostProcessor。當(dāng)需要使用到AOP時,它會把創(chuàng)建的原始的Bean對象wrap成代理對象作為Bean返回。
所以,最終結(jié)論是:只有被動態(tài)代理出來的對象,才可以被Spring增強,具備AOP的能力。
解決辦法
既然問題找到了,那么如何解決因為this調(diào)用帶來的AOP失效的問題呢?
有兩種辦法。
一,自己引用自己
直接在當(dāng)前類中注入自己,這樣Spring會對類中的屬性進行代理,生成一個payService代理類。
需要注意的是,這樣其實是人為的制造了循環(huán)依賴。在高版本的Springboot中,循環(huán)依賴是默認(rèn)關(guān)閉的。如果想開啟循環(huán)依賴,需要配置
spring.main.allow-circular-references=true。
二,通過AopContext
AopContext內(nèi)部維護了一個保存proxy的ThreadLocal,簡單說就是通過一個ThreadLocal將proxy和當(dāng)前線程綁定起來,這樣就可以隨時拿出當(dāng)前線程綁定的 Proxy。
如果使用這樣的方式,需要在@EnableAspectJAutoProxy 里加一個配置項 exposeProxy = true。
通過方式一修改下代碼,看看AOP是否生效。
可以看到,成功的打印出來日志耗時的log。
總結(jié)
SpringAOP實際上會自動為我們創(chuàng)建一個Proxy,使得調(diào)用者能無感知地調(diào)用指定方法,本質(zhì)上就是一個動態(tài)代理。我們只有訪問這些代理對象的方法,才能獲得AOP實現(xiàn)的功能,所以通過this引用是無法去正確使用 AOP 功能的。
網(wǎng)站題目:為什么說在SpringAOP中,不要使用This調(diào)用方法?
標(biāo)題網(wǎng)址:http://fisionsoft.com.cn/article/djedhji.html


咨詢
建站咨詢
