Solve Spring Annotation not Working While Service Method is Called Internally
当Service方法被内部调用时,Spring注解会失效。就是Spring的Service类,如果public
方法加上注解,类内部的其它方法使用this
调用该方法,会导致注解失效。
例如Spring的Service实现类如下:
@Service("userService")
class UserServiceImpl implements UserService{
@Override
@Cacheable(CacheNames="USER_CACHE", key="#userId")
public User getUser(String userId){
// do something
}
@Override
public String getUserName(String userId) {
User u = this.getUser(userId); // getUser方法的@Cacheable注解失效了
return u == null ? "" : u.getName();
}
}
原因是this
引用的对象没有被Spring代理,调用该对象的public
方法时,Spring不能处理相关注解。
解决方法很简单,就是使用Spring代理过的对象,代替this
。然后只需解决如果获取该Service类被Spring代理过的对象。
1 循环依赖
就是自己注入自己。在Service类定义一个自身对象的属性,让Spring装配时把自己注入到自己。虽然Spring 5(具体版本待查证)声称解决了循环依赖的问题,但是Spring Boot 2.6.0开始默认设置不允许循环依赖。循环依赖是一个古老的问题,一样认为要避免。所以此方法不推荐。
1)先设置
# Spring Boot 2.6.0之后,允许循环依赖
spring.main.allow-circular-references = true
2)上面的例子改为
@Service("userService")
class UserServiceImpl implements UserService{
@Autowired
private UserService self; // 自己注入自己
@Override
@Cacheable(CacheNames="USER_CACHE", key="#userId")
public User getUser(String userId){
// do something
}
@Override
public String getUserName(String userId) {
User u = self.getUser(userId); // 用self代替this,注解生效
return u == null ? "" : u.getName();
}
}
2 获取装配后的自己
避免Bean的循环依赖,主要思路是,在Bean装配完成后,再获取被Spring代理的自己。至于怎样获取,实现方法是多种多样的。
方法1,从Bean容器中获取自己。即:
UserService self = applicationContext.getBean("userService");
至于怎么获取Bean容器applicationContext
,方法也是多样的。
方法2,开启AspectJ自动代理来获取自己。
要注意,AspectJ自动代理不只是解决本文档的问题,需考虑是否会带来未知的问题。
开启AspectJ自动代理的方法有多种,这里列出三种:
1)在启动类添加注解:
@EnableAspectJAutoProxy(proxyTargetClass=true, exposeProxy=true)
2)Spring增加配置:
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true" />
3)Spring Boot的配置文件增加配置:
# 开启AspectJ自动代理
spring.aop.auto=true
# 开启CGLIB代理
spring.aop.proxy-target-class=true
然后就可以在当前Service类的方法中,通过类似的代码调用自身的方法,且能保证该方法的注解正常执行:
User u = ((UserService) AopContext.currentProxy()).getUser(userId);
方法3,延迟执行自己注入自己。
很简单,就是使用@Lazy
注解,达到Bean初始化不执行自己注入自己,避免循环依赖的错误。我记得解决Spring 2.x循环依赖的问题时,也是采用延迟注入的配置。此方法写的代码最少,目前倾向采用这种方法。于是,上面的代码改为:
@Service("userService")
class UserServiceImpl implements UserService{
@Lazy
@Autowired
private UserService self; // 延迟自己注入自己
@Override
@Cacheable(CacheNames="USER_CACHE", key="#userId")
public User getUser(String userId){
// do something
}
@Override
public String getUserName(String userId) {
User u = self.getUser(userId); // 用self代替this,注解生效
return u == null ? "" : u.getName();
}
}