Java 同类调用导致注解失效

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

踩坑表现

在一个类中,如果某个 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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3168 引用 • 8207 回帖
  • 一些有用的避坑指南。

    69 引用 • 93 回帖 • 2 关注

相关帖子

欢迎来到这里!

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

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