踩坑表现
在一个类中,如果某个 A 方法使用了注解,然后同一个类下的 B 方法直接调用 A 方法时,会导致 A 方法的注解失效。
一般会表现为:
-
- 同一个类中,方法内部调用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(){
}
}
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()
获取代理对象。 ↩- 事务不生效的原因: 事务是通过
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于