SpringBoot AOP注解失效問(wèn)題排查與解決(調(diào)用內(nèi)部方法)
開(kāi)發(fā)時(shí),遇到這樣一個(gè)問(wèn)題。項(xiàng)目使用springboot框架,項(xiàng)目中的task基于quartz實(shí)現(xiàn),其中有個(gè)BaseTask代碼實(shí)現(xiàn)quartz的Job接口,關(guān)鍵代碼如下:
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public abstract class BaseTask implements Job { @Override public void execute(JobExecutionContext context) { // do something before... doTask(taskDto); // do something after... } public abstract void doTask(TaskDTO taskDto); }
項(xiàng)目中的所有task會(huì)繼承這個(gè)BaseTask,并重寫(xiě)doTask方法,如下:
public class MyTask extends BaseTask { @Override public void doTask(TaskDTO taskDto) { // 在這里實(shí)現(xiàn)你的具體任務(wù)邏輯 System.out.println("執(zhí)行MyTask的doTask方法"); } }
這時(shí)候因?yàn)樽龅臉I(yè)務(wù)會(huì)涉及很多類(lèi)似的數(shù)據(jù)同步,有很多task,且都需要做如下操作:
1.實(shí)現(xiàn)并發(fā)鎖控制
2.讀取上次同步時(shí)間進(jìn)行增量同步操作
3.插入taskRecord記錄等
這些代碼會(huì)極大的冗余和重復(fù),故此想使用AOP的思想,使用注解的方式,將這些操作抽象整合,于是開(kāi)干:
1.引入springboot的aop依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.寫(xiě)注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TaskRecord { }
3.使用@Aspect寫(xiě)切面
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class TaskAspect { @Pointcut("execution(* com.example.task.*.doTask(..)) && @annotation(TaskRecord)") public void pointcut() {} @Before("pointcut()") public void before(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); // 獲取方法的參數(shù) System.out.println("方法參數(shù): " + args[0]); } }
4.在MyTask類(lèi)的方法里加上注解
public class MyTask extends BaseTask { @Override @TaskRecord public void doTask(TaskDTO taskDto) { // 在這里實(shí)現(xiàn)你的具體任務(wù)邏輯 System.out.println("執(zhí)行MyTask的doTask方法"); } }
一番操作下來(lái)行云流水,心里默想:老子cv代碼天下第一,肯定沒(méi)問(wèn)題。打斷點(diǎn)debug調(diào)試,發(fā)現(xiàn)問(wèn)題出現(xiàn),根本進(jìn)不了@Before方法里啊
于是排查問(wèn)題,百度chatgpt問(wèn)了n²+1天,在試過(guò)如下方法如:
1.將MyTask,BaseTask加上@Component將bean加載到spring容器內(nèi)
2.啟動(dòng)類(lèi)增加@EnableAspectJAutoProxy注解(實(shí)際SpringBoot默認(rèn)自動(dòng)配置AOP的)
3.聯(lián)想是否跟CGLIB或JDK動(dòng)態(tài)代理有關(guān)
一系列操作,發(fā)現(xiàn)屢戰(zhàn)屢敗,于是擱置了數(shù)天,今天又心血來(lái)潮試了下,換了個(gè)思路去思考,查詢(xún)AOP失效的幾個(gè)場(chǎng)景,總結(jié)如下:
1.內(nèi)部方法調(diào)用:AOP通常不會(huì)攔截同一個(gè)類(lèi)內(nèi)部的方法調(diào)用。如果一個(gè)被代理的方法調(diào)用了另一個(gè)被代理的方法,那么只有外部調(diào)用的方法會(huì)觸發(fā)AOP。
2.AOP配置問(wèn)題:AOP切入點(diǎn)配置不正確,切入點(diǎn)表達(dá)式可能沒(méi)有正確應(yīng)用到目標(biāo)bean。
3.直接實(shí)例化對(duì)象:如直接使用new關(guān)鍵字創(chuàng)建了一個(gè)對(duì)象實(shí)例,AOP將無(wú)法攔截此對(duì)象的方法調(diào)用。需要通過(guò)Spring容器獲取對(duì)象,讓Spring負(fù)責(zé)bean的實(shí)例化,才能讓AOP生效。
嗯?好像有點(diǎn)思路了,很符合1的情況,于是在某個(gè)controller寫(xiě)了個(gè)請(qǐng)求并加上注解,發(fā)現(xiàn)可以進(jìn)入,但是如果寫(xiě)一個(gè)內(nèi)部方法,在內(nèi)部方法上加注解,請(qǐng)求的方法再去調(diào)用則會(huì)失效,如下:
這樣是ok的
@RequestMapping("/test") @ResponseBody @TaskRecord public String test(@RequestBody TestRequest request) { test1(request); return "執(zhí)行成功!"; }
這樣是not ok的
@RequestMapping("/test") @ResponseBody public String test(@RequestBody TestRequest request) { test1(request); return "執(zhí)行成功!"; } @TaskRecord public void test1(TestRequest request) { System.out.println(request.toString()); }
所以思路就是在執(zhí)行內(nèi)部的doTask方法時(shí),不能直接調(diào)用,需要通過(guò)代理類(lèi)去調(diào)用才能讓AOP切面生效,改造如下:
1.新增接口
public interface JobCommonService { void doTaskNew(TaskDTO taskDTO) throws Exception; }
2.BaseTask改造,不直接調(diào)用doTask
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public abstract class BaseTask implements Job { @Override public void execute(JobExecutionContext context) { // do something before... // 這是自建的一個(gè)spring容器工具類(lèi),可以獲取容器里的bean JobCommonService jobService = SpringContextUtil.getBean(JobCommonService.class); jobService.doTaskNew(taskDTO); // do something after... } public abstract void doTask(TaskDTO taskDto); }
3.具體task改造,實(shí)現(xiàn)JobCommon接口重寫(xiě)doTaskNew方法
@Component public class MyTask extends BaseTask implements JobCommonService{ @Override public void doTask(TaskDTO taskDto) { return; } @Override @TaskRecord public void doTaskNew(TaskDTO taskDTO) { // do something... return; } }
至此打完收工,成功進(jìn)入@Before方法,下面關(guān)掉所有查詢(xún)的窗口,可以愉快的進(jìn)行其他操作啦!
到此這篇關(guān)于SpringBoot AOP注解失效問(wèn)題排查與解決(調(diào)用內(nèi)部方法)的文章就介紹到這了,更多相關(guān)SpringBoot AOP注解失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析
這篇文章主要介紹了spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10IDEA中osgi的開(kāi)發(fā)應(yīng)用指南詳解
這篇文章主要介紹了IDEA中osgi的開(kāi)發(fā)應(yīng)用指南詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04springboot整合mybatis實(shí)現(xiàn)數(shù)據(jù)庫(kù)的更新批處理方式
這篇文章主要介紹了springboot整合mybatis實(shí)現(xiàn)數(shù)據(jù)庫(kù)的更新批處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Java ArrayList.add 的實(shí)現(xiàn)方法
這篇文章主要介紹了Java ArrayList.add 的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11學(xué)Java做項(xiàng)目需要學(xué)習(xí)的一些技能
這篇文章主要介紹了學(xué)Java做項(xiàng)目需要學(xué)習(xí)的一些技能,例如JavaSE、Servlet、JSP等,總結(jié)了他們中需要學(xué)習(xí)的東西,都是一些經(jīng)驗(yàn)總結(jié),需要的朋友可以參考下2014-07-07一小時(shí)迅速入門(mén)Mybatis之實(shí)體類(lèi)別名與多參數(shù) 動(dòng)態(tài)SQL
這篇文章主要介紹了一小時(shí)迅速入門(mén)Mybatis之實(shí)體類(lèi)別名與多參數(shù) 動(dòng)態(tài)SQL,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09spring導(dǎo)入framework報(bào)紅問(wèn)題
這篇文章主要介紹了spring導(dǎo)入framework報(bào)紅問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07聊聊Spring?Cloud?Gateway過(guò)濾器精確控制異常返回問(wèn)題
這篇文章主要介紹了Spring?Cloud?Gateway過(guò)濾器精確控制異常返回問(wèn)題,本篇任務(wù)就是分析上述現(xiàn)象的原因,通過(guò)閱讀源碼搞清楚返回碼和響應(yīng)body生成的具體邏輯,需要的朋友可以參考下2021-11-11