故事是有两个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 ); @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) { 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,于是相关缓存处理代码没有执行.
CommonAnnotationBeanPostProcessor
和InitDestroyAnnotationBeanPostProcessor
这两个类在也值得关注一下,他们就是负责处理@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 ); 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)