spring在处理循环依赖的问题上引入了“提前曝光对象”,但是这避免不了在出现循环依赖时所带来的NPE可能性,这种场景博主在另一篇文章中有描述https://blog.csdn.net/u010597819/article/details/86646162。
那么没有循环依赖是不是我们就安全了?哎,果然还是与我们NPE的再次邂逅
问题描述
测试已经测完了大半的功能,突然在一天下午出现怎么都起不来服务,测试问我们有没有代码改动,而我们并没有提交任何代码,代码没有改动,之前测试好好的,突然怎么就起不来了呢?查看日志里面正是我们的老朋友NPE,然后根据日志查看具体代码
Caused by: java.lang.NullPointerException
at .....spring.boot.autoconfigure.api.AbstractClientCache.queryFromClientCache(AbstractClientCache.java:79) ~[client-cache-support-1.0.0.jar:1.0.0]
at .......DispatchFilterTypeConfigFullyCache.lambda$buildCache$1(DispatchFilterTypeConfigFullyCache.java:47) ~[classes/:?]
at java.util.ArrayList.forEach(ArrayList.java:1249) ~[?:1.8.0_131]
at .......DispatchFilterTypeConfigFullyCache.buildCache(DispatchFilterTypeConfigFullyCache.java:45) ~[classes/:?]
at .....spring.boot.autoconfigure.api.AbstractClientCache.initCache(AbstractClientCache.java:34) ~[client-cache-support-1.0.0.jar:1.0.0]
at .....spring.boot.autoconfigure.ClientCacheHandler.afterPropertiesSet(ClientCacheHandler.java:36) ~[client-cache-support-1.0.0.jar:1.0.0]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
... 14 more
问题原因分析
- 是不是我们之前碰到的循环依赖问题呢?于是赶紧去看了下依赖的bean里面是否存在循环依赖的问题,发现并木有。。。
@Component
public class DispatchFilterInfoFullyCache extends
AbstractFullyClientCache<String, List<DispatchFilterInfoDTO>, DispatchFilterInfo> {
@Resource
private DispatchFilterInfoManager dispatchFilterInfoManager;
- 了解spring加载bean流程的同学应该知道,bean在加载时,对于依赖的引用会默认使用提前曝光对象注入,所以不应该再该处出现空指针问题;那么问题出在了哪呢?
- 我们再来跟着日志一步步看下异常原因,很明显是dispatchFilterInfoFullyCache.cache为空导致的
- 也就是说依赖已经注入,但是注入的缓存还没有创建导致
1.type缓存调用另一个缓存,DispatchFilterTypeConfigFullyCache.lambda$buildCache$1(DispatchFilterTypeConfigFullyCache.java:47)
List<DispatchFilterInfoDTO> dispatchFilterInfoDTOS = dispatchFilterInfoFullyCache.queryFromClientCache(dispatchFilterTypeConfigResultDTO.getId()+"");
2.DispatchFilterInfoFullyCache缓存查询key对应的值
public V queryFromClientCache(K key) {
return this.cache.getIfPresent(key);
}
- 问题基本确认了,如果说ClientCacheHandler Bean在其他本地缓存前进行初始化的话,list属性中注入本地缓存的“提前曝光对象”,如果此时list中的缓存顺序是DispatchFilterTypeConfigFullyCache、DispatchFilterInfoFullyCache;那么在调用DispatchFilterTypeConfigFullyCache的buildCache方法时,由于该缓存使用了DispatchFilterInfoFullyCache缓存,如果DispatchFilterInfoFullyCache缓存还没有创建本地缓存,那么必然会出现空指针问题
@Bean
public ClientCacheHandler clientCacheHandler() {
return new ClientCacheHandler();
}
public class ClientCacheHandler implements InitializingBean {
@Autowired
private List<ClientCache> list;
public void afterPropertiesSet() {
Iterator var1 = this.list.iterator();
while(var1.hasNext()) {
ClientCache clientCache = (ClientCache)var1.next();
...
//init缓存中调用了本地缓存的buildCache方法
Cache cache = clientCache.initCache();
...
问题复现
首先按照分析结果尝试复现问题。我们使用@Order注解DispatchFilterInfoFullyCache,也就是强制指定顺序为最后。并且让DispatchFilterTypeConfigFullyCache实现PriorityOrdered接口提供更高优先级的加载或者也使用@Order注解指明最高优先级,果然问题本地复现成功。证明了我们的分析
问题解决
确认了问题之后,解决方案也就很明朗了,没错,只要保证DispatchFilterTypeConfigFullyCache依赖的DispatchFilterInfoFullyCache缓存先一步建立并刷新缓存即可,使用@Order(HIGHEST_PRECEDENCE)注解指定注入的list表中DispatchFilterInfoFullyCache排在最前面,因为缓存是按照顺序初始化加载,则问题自然就解决了
于是马上修改代码通知测试小伙伴重新打包发布,问题修复
期间的思考
疑惑:第一次复现时仅指定了DispatchFilterInfoFullyCache为@Order(默认为最低_LOWEST_PRECEDENCE_),已经可以百分百复现了,但是写本文时竟然不能复现了,需要同时配置DispatchFilterTypeConfigFullyCache为最高优先级才可以复现。这个是为什么???
那我们来看看在注入列表属性时的排序规则吧
- populateBean注入属性
- 根据类型注入autowireByType
- resolveDependency解析依赖对象
- 放入属性列表中待注入
解析依赖对象
- 工厂类解析依赖DefaultListableBeanFactory.resolveDependency
- 如果是Optional、ObjectFactory、ObjectProvider、javax.inject.Provider,则按照相应的提供者解析
- 如果均不是则判断是否懒惰方式处理,不是则执行解析doResolveDependency
- 如果是多bean则执行多bean解析resolveMultipleBeans并返回
- 我们直接看与我们需求对应的列表类型,如果是Collection集合类型,并且是接口类型,获取队列的bean列表
- 如果存在依赖比较器并且要注入的属性是List类型,则排序后返回
if (getDependencyComparator() != null && result instanceof List) {
Collections.sort((List<?>) result, adaptDependencyComparator(matchingBeans));
}
return result;
依赖比较器实现
- 依赖比较器在创建AnnotatedBeanDefinitionReader对象时注册,AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
- 实现类即:AnnotationAwareOrderComparator
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
- AnnotationAwareOrderComparator执行比较
private int doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
}
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider);
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
- 如果p1是PriorityOrdered类型p2不是,则p1优先级高,相反,p2优先级高
- 如果存在sourceProvider(默认为空)则通过sourceProvider获取order,如果读取的order为空,则getOrder
- getOrder从对象的注解中获取order,如果不存在则使用_LOWEST_PRECEDENCE_
protected int getOrder(Object obj) {
Integer order = findOrder(obj);
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
}