背景
在最近的一个项目中,同事为了使用@EnableCaching
导致工程启动不了,其中报错的是我们公司内部自定义的注入解析器(类似于AutoWired注解)。
分析原因
首先根据堆栈可以得知是空指针了.
Caused by: java.lang.NullPointerException: null
at com.xxx.rpc.spi.BeanUtils.getAllField(BeanUtils.java:40)
at com.xxx.rpc.spi.spring.InitRpcAnnotation.initCustomer(InitRpcAnnotation.java:81)
at com.xxx.rpc.spi.spring.InitRpcAnnotation.postProcessAfterInitialization(InitRpcAnnotation.java:73)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:430)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1798)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
点击进去查看代码,如下所示:
public static List<Field> getAllField(Class<?> clazz) {
Map<String, Field> map = (Map)FIELD_MAP.get(clazz);
if (null == map) {
synchronized(FIELD_MAP) {
map = (Map)FIELD_MAP.get(clazz);
if (null == map) {
map = new HashMap();
Class c = clazz;
while(true) {
Field[] fields = c.getDeclaredFields();
Field[] var5 = fields;
int var6 = fields.length;
for(int var7 = 0; var7 < var6; ++var7) {
Field f = var5[var7];
((Map)map).put(f.getName(), f);
}
c = c.getSuperclass();
if (Object.class.equals(c)) {
FIELD_MAP.put(clazz, map);
break;
}
}
}
}
}
return new ArrayList(((Map)map).values());
}
首先理解一下代码的逻辑是,对class去不断向上取superClass,当superClass是Object.class时,就将获取到的字段填入进去。但是在运行时候发现当参数clazz
是java.lang.Object
时,获取到superClass就是null,然后下次循环时就报了空指针。
java.lang.Object这个bean是正常的吗?
使用新的demo(Spring-boot:2.5)工程进行启动,通过actuator/beans
观察的确注册了java.lang.Object
的bean,代表的确是正常会产生的。那么这个类究竟是如何注册或者依赖的呢?
- 首先进入入口
org.springframework.context.support.AbstractApplicationContext#refresh
查看哪一步扫描到了java.lang.Object
- 通过
BeanFactory
定位到java.lang.Object
是被CacheAuotConfiguration
依赖之后导入的。 - 通过代码一步一步定位到是
org.springframework.context.annotation.ConfigurationClassParser
解析出来的。 - 因为上一步解析的步骤很复杂,所以转变思路,通过条件断点去定位。通过在
org.springframework.context.annotation.ConfigurationClass#ConfigurationClass(org.springframework.core.type.classreading.MetadataReader, org.springframework.context.annotation.ConfigurationClass)
打上断点,条件是importedBy.getResource().toString().contains("CacheAutoConfiguration")
- 经过上面的定位,发现CacheAutoConfiguration解析出来的依赖
org.springframework.boot.autoconfigure.cache.CacheType
,会经过org.springframework.context.annotation.ConfigurationClassParser#asSourceClass(java.lang.Class<?>, java.util.function.Predicate<java.lang.String>)的转化,像这种的会经过两个判断,类名路径和条件是否满足的判断,如果满足任何一个就会返回
java.lang.Object`
额外扩展 - Spring如何做的自动注入呢?
可以简单看一下时序图,有省略,发现最后使用了RelectionUtils.
总结
- Spring注入器应该兼容异常,之前写解析器的时候也会发现有各种异常的类。一般来说不应该阻断启动。
- 定位importedBy的时候可以在
ConfigurationClass
使用条件断点。 - 有关Autowired注解的debug可以在
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
中进行断点。
最后,新年快乐~
祝大家在新的一年里都学有所成,事业进步,心想事成!