缘起
最近公司项目要求JDK从8升到17,SpringBoot版本从2.x升级到3.x,期间遇到了一个诡异的FileNotFoundException
异常,日志如下(敏感信息使用xxx脱敏)
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [***.xxx.xxx.OperationAnalysisApplication]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:179)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:397)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:283)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:745)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:565)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291)
at ***.xxx.xxx.OperationAnalysisApplication.main(OperationAnalysisApplication.java:34)
Caused by: java.io.FileNotFoundException: class path resource [org/springframework/web/servlet/config/annotation/WebMv***onfigurerAdapter.class] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:211)
at org.springframework.core.type.classreading.SimpleMetadataReader.getClassReader(SimpleMetadataReader.java:54)
at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:48)
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103)
at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.createMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:86)
at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.getMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:73)
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:81)
at org.springframework.context.annotation.ConfigurationClassParser.asSourceClass(ConfigurationClassParser.java:611)
at org.springframework.context.annotation.ConfigurationClassParser$SourceClass.getSuperClass(ConfigurationClassParser.java:924)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:335)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:244)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:189)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:298)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:244)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:197)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:165)
... 13 ***mon frames omitted
SpringBoot3.x已经删除了类WebMv***onfigurerAdapter
,建议实现接口WebMv***onfigurer
过程
1. 查找当前项目中使用WebMv***onfigurerAdapter的地方
这个报错日志明显没有提示出哪个类有问题,先排除当前项目自己写的代码有没有这个类,于是在IDEA中使用全局查找来查找使用WebMv***onfigurerAdapter
的地方,发现都被WebMv***onfigurer
接口替代了,看来并不是自己写的代码里面的,二是某个Jar中的,这大概就是没有显示因为哪个类继承了WebMv***onfigurerAdapter
导致异常的原因吧
2. 查找哪个Jar中引用了这个类
下个断点看看
这TM和大海捞针没区别啊,这项目光依赖自己的包就几十个,大部分都是公司内部包,我都没有源码,这可怎么找啊,只能去报异常的地方碰碰运气了。我找到了报异常的地方:
日志显示在这
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:211)
源码如下:
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
} else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.absolutePath);
} else {
is = ClassLoader.getSystemResourceAsStream(this.absolutePath);
}
if (is == null) {
throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
} else {
return is;
}
}
报异常的地方很明显了,就是这
if (is == null) {
throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
}
在这里下断点,启动项目,发现这个类里this.clazz
是null,this.classLoader
不是null,感觉这个classloader
应该就是加载某个类的时候发现它的父类是WebMv***onfigurerAdapter
,去加载WebMv***onfigurerAdapter
找不到才报错的。就去这个classloader
里面翻找了一下,感觉最有价值的就是里面的classes
属性了,如下:
当前实例中的classes中找找
里面居然已经加载了5285个类,一个一个的看显然不行,于是在classes
上右键,然后选择评估表达式,想着这个classes
是个ArrayList
,使用classes.stream().filter()
筛选一下加载的类里面是否有父类是WebMv***onfigurerAdapter
的,找到了不就知道是哪个有问题了么。写好筛选条件后执行,报错如下:
这…大概是JDK里面很多类还没加载导致的吧,看来这条行不通了。于是考虑这个classes
是ArrayList
类型,有序列表啊,其中最后一个元素有没有可能是当前继承了WebMv***onfigurerAdapter
的类么?于是使用评估表达式获取了最后一个元素,很遗憾,是个idea的类:
梦想再一次破灭了。
是不是当前类加载器能提供点什么信息
想着是不是当前类加载器能看到当前在加载哪个类或者哪个类的加载引起了当前类的加载啊?很遗憾,classloader根本没有这信息,只有当前类加载器正在加载的这个类WebMv***onfigurerAdapter
。
看看谁触发了当前类加载
很遗憾,上面的方法都没找到很有用的信息,于是想着,看看谁调用了当前方法,也就是说谁触发了加载WebMv***onfigurerAdapter
这个类的动作,于是顺着调用栈:
一直往回找,终于,在下面这个地方找到了:
这里点开resource
字段,直接定位到类:
import ***.xxx.data.analytics.interceptor.RequestDataInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMv***onfigurerAdapter;
@Configuration
public class InterceptorConfig extends WebMv***onfigurerAdapter {
public InterceptorConfig() {
}
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestDataInterceptor()).addPathPatterns(new String[]{"/**"});
super.addInterceptors(registry);
}
}
就是这了。Jar里面没法排掉某个特定的类,只能在当前项目中创建一个和这个类一模一样的类,代码改成自己想要的。因为Java中,某个类(同一个classloader+全限定名)已经被加载了,就不会再次被加载,项目中的类加载优先于Jar包中的,又都是同一个类加载器加载,所以会优先加载到项目中类,Jar中的则不会被加载了,曲线救国 我试了下似乎不行,待考证,暂时先加删除线,后续会回来看看怎么解决这个问题。
-------------------------------------------------------->分割线20230729<------------------------------------------------------------------------------
最后在项目里创建了一个package :org.springframework.web.servlet.config.annotation
然后把WebMv***onfigurerAdapter
拷贝进来,把InterceptorConfig
在SpringBoot启动类上设置为不扫描,把问题解决了。不创建包org.springframework.web.servlet.config.annotation
并拷贝WebMv***onfigurerAdapter
直接在SpringBoot启动类上设置为不扫描是不行的,因为你设置InterceptorConfig
不扫描时JVM会首先加载这个类,这个类有父类就先尝试加载父类WebMv***onfigurerAdapter
,父类不存在,直接报错。