搬砖小抄

在@PostConstruct方法中调用带拦截器的方法时,拦截器未生效

字数统计: 624阅读时长: 2 min
2018/04/11 Share

故事是有两个Bean,分别是A和B

  • B注入了A里面
  • A有一个@PostConstruct方法methodA
  • B有一个mehtodB方法,也是配置了拦截器的

症状就是在methodA调用mehtodB时,mehtodB的拦截器没有生效.

真实的代码片段

UserService(对应BeanA)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class UserService{
@Autowired
private RoleCache roleCache;
private final AtomicReference<Role> defaultRole=new AtomicReference<>(null); // role for new created user

@PostConstruct
private void afterConstruct(){
loadDefaultRole();
}

private loadDefaultRole(){
Role role = roleCache.loadAndCache("default-role");
defaultRole.set(role);
}
}

RoleCache(对应BeanB)

1
2
3
4
5
6
7
8
9
10
@Component
public class RoleCache{
@Autowired
private RoleRepository roleRepository;

@CachePut(cacheNames = {"role",key = "'role:name:' + #name")
public Role loadAndCache(String name){
return roleRepository.findRoleByName(name);
}
}

代码的本意是:

  • 在初始化时就加载一个Role对象(因为这个角色是所有新注册用户都必须要有的默认角色)
  • 同时也希望把这个Role对象刷到缓存中去

问题的原因就是我忽略一个问题,上面两个类和拦截器都是Bean,他们都是在refreshContext阶段初始化,并且没有顺序保证,所以在UserService里面,虽然RoleCache已经注入,但是RoleCache的拦截器(负责处理缓存的拦截器,由@CachePut触发)却还没有初始化,在这个时候调用的loadAndCache,实际上出于裸奔状态.

这段代码来自CacheAspectSupport.java,负责处理缓存.

1
2
3
4
5
6
7
8
9
10
11
12
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}

return invoker.invoke();
}

在调试的时候,验证了上面的说法,loadDefaultRole调用loadAndCache方法时this.initialized为false,于是相关缓存处理代码没有执行.

CommonAnnotationBeanPostProcessorInitDestroyAnnotationBeanPostProcessor这两个类在也值得关注一下,他们就是负责处理@PostConstruct的.

解决办法

由于没有办法确保Bean初始化顺序,只好推迟调用有拦截器的方法了,理想的时机是收到ApplicationReadyEvent事件后执行(其实这里才是执行初始化动作的正确地方,上面代码本来就是一个错误).
修改后的UserService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserService{
@Autowired
private RoleCache roleCache;
private final AtomicReference<Role> defaultRole=new AtomicReference<>(null); // role for new created user

public loadDefaultRole(){
Role role = roleCache.loadAndCache("default-role");
defaultRole.set(role);
}

@Component
static class Initializer implements ApplicationListener<ApplicationReadyEvent> {
@Autowired
private UserServiceImpl userService;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
userService.loadDefaultRole();
}
}
}

StackOverflow上也[相同的问题](https://stackoverflow.com/questions/28350082/spring-cache-using-cacheable-during-postconstruct-does-not-work
StackOverflow)

CATALOG
  1. 1. 真实的代码片段
  2. 2. 解决办法