Java 同类调用导致注解失效

本贴最后更新于 638 天前,其中的信息可能已经东海扬尘

踩坑表现

在一个类中,如果某个 A 方法使用了注解,然后同一个类下的 B 方法直接调用 A 方法时,会导致 A 方法的注解失效。

一般会表现为:

    1. 同一个类中,方法内部调用1 ,@Transactional 注解失效
  • 同理,@RedisLock 这类自定义注解也会失效

原理

一般来说,注解的实现是通过 Spring AOP​代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用

即 B 调用 A 的方法,调用目标方法不是通过代理类进行的,因此导致注解不生效。

解决方法

1. 拆分方法

将两个方法拆分到不同的类中,这样就可以让 B 方法调用 A 方法时,使用到的是代理类来执行

2. AopContxt.currentProxy() 获得当前代理对象

B 方法通过 AopContxt.currentProxy() 获得当前代理对象,然后通过这个代理对象来执行 A 方法,这样就可以触发 A 方法中的注解了

@Slf4j @Service public class FriendServiceImpl implements FriendService { @Override public void apply(Long uid, FriendApplyReq request) { ...... ((FriendService) AopContext.currentProxy()).applyApprove(uid, new FriendApproveReq(friendApproving.getId())); ..... } @Override @Transactional(rollbackFor = Exception.class) @RedissonLock(key = "#uid") public void applyApprove(Long uid, FriendApproveReq request) { UserApply userApply = userApplyDao.getById(request.getApplyId()); ....... } }
  • 注意通过 AopContext.currentProxy() 获取的代理对象要“强转”为对应的类,才能调用指定的方法!

3. 通过 Spring 应用上下文获得 bean 后进行调用(ApplicationContext.getBean())

对于 SpringBoot 的应用可以这样获得应用上下文。

Spring 应用上下文工具类,这里只写了两种 getBean()。

public class SpringContextUtil { private static ApplicationContext applicationContext; public static ApplicationContext getApplicationContext() { return applicationContext; } public static void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.applicationContext = applicationContext; } @SuppressWarnings("unchecked") public static <T> T getBean(String name) { return (T) applicationContext.getBean(name); } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } }

SpringBoot 启动时设置 applicationContext 的值。

@SpringBootApplication @ServletComponentScan public class SpringBootApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringBootApplication.class, args); SpringContextUtil.setApplicationContext(context); } }

可以在 test1 中通过 SpringContextUtil.getBean(“TestService”).test2() 进行调用。

@Service public class TestService { public void test1(){ SpringContextUtil.getBean("TestService").test2(); } @Transactional public void test2(){ } }

  1. 4. 同一个类中,方法内部调用

    @Service public class TianLuoServiceImpl implements TianLuoService {     @Autowired     private TianLuoMapper tianLuoMapper;          @Autowired     private TianLuoFlowMapper tianLuoFlowMapper;          public void addTianLuo(TianLuo tianluo){      // 调用内部的事务方法      this.executeAddTianLuo(tianluo);    }     @Transactional     public void executeAddTianLuo(TianLuo tianluo) {         tianLuoMapper.save(tianluo);         tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));     } }
    • 事务不生效的原因: 事务是通过 Spring AOP​代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用。即以上代码,调用目标 executeAddTianLuo​方法不是通过代理类进行的,因此事务不生效。
    • 解决方案:可以新建多一个类,让这两个方法分开,分别在不同的类中。如下:
    @Service public class TianLuoExecuteServiceImpl implements TianLuoExecuteService {     @Autowired     private TianLuoMapper tianLuoMapper;     @Autowired     private TianLuoFlowMapper tianLuoFlowMapper;          @Transactional     public void executeAddTianLuo(TianLuo tianluo) {         tianLuoMapper.save(tianluo);         tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));     } } @Service public class TianLuoAddServiceImpl implements TianLuoAddService {     @Autowired     private TianLuoExecuteService tianLuoExecuteService;          public void addTianLuo(User user){      tianLuoExecuteService.executeAddTianLuo(user);    } }

    当然,有时候你也可以在该 ​ Service​ 类中注入自己,或者通过 AopContext.currentProxy()​获取代理对象。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3203 引用 • 8217 回帖 • 2 关注
  • 一些有用的避坑指南。

    69 引用 • 93 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...