开发时,遇到这样一个问题。项目使用springboot框架,项目中的task基于quartz实现,其中有个BaseTask代码实现quartz的Job接口,关键代码如下:
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); }
项目中的所有task会继承这个BaseTask,并重写doTask方法,如下:
public class MyTask extends BaseTask { @Override public void doTask(TaskDTO taskDto) { // 在这里实现你的具体任务逻辑 System.out.println("执行MyTask的doTask方法"); } }
这时候因为做的业务会涉及很多类似的数据同步,有很多task,且都需要做如下操作:
1.实现并发锁控制
2.读取上次同步时间进行增量同步操作
3.插入taskRecord记录等
这些代码会极大的冗余和重复,故此想使用AOP的思想,使用注解的方式,将这些操作抽象整合,于是开干:
1.引入springboot的aop依赖
org.springframework.bootspring-boot-starter-aop
2.写注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TaskRecord { }
3.使用@Aspect写切面
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(); // 获取方法的参数 System.out.println("方法参数: " + args[0]); } }
4.在MyTask类的方法里加上注解
public class MyTask extends BaseTask { @Override @TaskRecord public void doTask(TaskDTO taskDto) { // 在这里实现你的具体任务逻辑 System.out.println("执行MyTask的doTask方法"); } }
一番操作下来行云流水,心里默想:老子cv代码天下第一,肯定没问题。打断点debug调试,发现问题出现,根本进不了@Before方法里啊
于是排查问题,百度chatgpt问了n²+1天,在试过如下方法如:
1.将MyTask,BaseTask加上@Component将bean加载到spring容器内
2.启动类增加@EnableAspectJAutoProxy注解(实际SpringBoot默认自动配置AOP的)
3.联想是否跟CGLIB或JDK动态代理有关
一系列操作,发现屡战屡败,于是搁置了数天,今天又心血来潮试了下,换了个思路去思考,查询AOP失效的几个场景,总结如下:
1.内部方法调用:AOP通常不会拦截同一个类内部的方法调用。如果一个被代理的方法调用了另一个被代理的方法,那么只有外部调用的方法会触发AOP。
2.AOP配置问题:AOP切入点配置不正确,切入点表达式可能没有正确应用到目标bean。
3.直接实例化对象:如直接使用new关键字创建了一个对象实例,AOP将无法拦截此对象的方法调用。需要通过Spring容器获取对象,让Spring负责bean的实例化,才能让AOP生效。
嗯?好像有点思路了,很符合1的情况,于是在某个controller写了个请求并加上注解,发现可以进入,但是如果写一个内部方法,在内部方法上加注解,请求的方法再去调用则会失效,如下:
这样是ok的
@RequestMapping("/test") @ResponseBody @TaskRecord public String test(@RequestBody TestRequest request) { test1(request); return "执行成功!"; }
这样是not ok的
@RequestMapping("/test") @ResponseBody public String test(@RequestBody TestRequest request) { test1(request); return "执行成功!"; } @TaskRecord public void test1(TestRequest request) { System.out.println(request.toString()); }
所以思路就是在执行内部的doTask方法时,不能直接调用,需要通过代理类去调用才能让AOP切面生效,改造如下:
1.新增接口
public interface JobCommonService { void doTaskNew(TaskDTO taskDTO) throws Exception; }
2.BaseTask改造,不直接调用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... // 这是自建的一个spring容器工具类,可以获取容器里的bean JobCommonService jobService = SpringContextUtil.getBean(JobCommonService.class); jobService.doTaskNew(taskDTO); // do something after... } public abstract void doTask(TaskDTO taskDto); }
3.具体task改造,实现JobCommon接口重写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; } }
至此打完收工,成功进入@Before方法,下面关掉所有查询的窗口,可以愉快的进行其他操作啦!
到此这篇关于SpringBoot AOP注解失效问题排查与解决(调用内部方法)的文章就介绍到这了,更多相关SpringBoot AOP注解失效内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!