最近项目在切换springboot,一个项目开发过程中,发生了springboot项目启动失败,但是日志提醒又很少,今天在这里主要分享下问题排查的过程
问题,springboot项目启动时候报错
控制台异常-
因为设置了日志没有达到控制台,截图下具体的日志
具体日志
分析过程
- 看到这堆日志刚开始有点懵逼,因为都报了一堆框架的错,一个都没有自己写的类的问题,静下心来强迫自己仔细看了下(日志从下往上看)
SpringApplication类中容器启动的方法
SpringApplication中容器启动的方法at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:743)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:390)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1214)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1203)
- 先看这段吧,都是SpringApplication类里面,就是容器启动的方法报错
查看具体的报错方法
image.png@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
- 具体定位到了invokeBeanFactoryPostProcessors(beanFactory)方法上了,看注释吧,这个方法,大概是调用工程处理器注册bean对象到容器上下文中,那么我们在看一下这个方法中具体报错的地方
invokeBeanFactoryPostProcessors方法报错的地方
这一行PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
- 这一行,第一个入参beanFactory就是spring的bean工厂对象,第二个参数getBeanFactoryPostProcessors()就是就是获取bean工厂的处理类对象,是一个list对象,我们在往上看
报错日志的at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
-
在往上看就看了一个比较关键的地址:
map遍历 - 看日志报错,发现中间有一行map遍历时候报的错,看上下文,我们大致判断是ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法中,遍历map处理的时候报错的,我们来看下源码
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
- 这段代码如果没有深入理解过spring源码的话,应该看不懂,其实我第一眼看也比较懵逼,那么我们用我们熟悉的知识看,这样代码就是遍历了Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> 这个map对象,遍历的每一key和他对象的value,registrars.forEach((registrar, metadata) 这块里面的registrar, metadata就是key和value,然后这段registrar.registerBeanDefinitions(metadata, this.registry));,就是遍历出来的key value,用key对象registrar,调用了registrar对象的registerBeanDefinitions方法
那么我们大致判断出了问题的原因
-
就是某一次遍历时候出现了错误,虽然我们看不懂这段代码具体是干什么的,但是我们可以在这打个断点,看看这个map里面具体的值,看看能发现问题么
image.png
运行结果 - metadata对象都是这样子的,可以猜测下,就是springboot里面用的config注册类,
- 一个springboot项目会有很多个这样的类需要注册,这个方法一直在被循环调用
-
那么循环到一个上停止报错了,就是这个地方有问题,我一直在进行继续的操作
经过我反复尝试
报错点
那么我们继续跟进,就是说这个注册的时候报错了
EnableConfigurationPropertiesImportSelector类中
处理类- 这个类,看着代码,大致就是处理注册bean工作的,而且看最上层的报错
java.lang.ClassCastException: java.lang.NoClassDefFoundError cannot be cast to [Ljava.lang.Object; - 应该就是某一个属性的依赖jar包没有,导致没有创建成功
-
我们在这打断点
最终我们定位到了这里
image.png
最低层的错误就是转化成class的报错,因为这个jar包中没有这个类,说明我们jar包的版本错误了
image.png
最终我们定位到了问题是spring-boot-autoconfigure这个依赖版本错误了,少了这个类,所以在转化的时候报了java.lang.NoClassDefFoundError: org/springframework/boot/autoconfigure/security/SecurityPrerequisite,但是spring容器这个时候没有做处理,把这个异常直接放到属性map里面了,不知道算不算spring的一个bug。
-
我们来看下依赖
spring-boot-autoconfigure的版本 -
我们用的是2.1.7版本
-
在来看下实际打包出来的
2.0.3 -
实际打包出来是2.0.3,别的boot的版本都是2.1.7
image.png
到这里问题基本清楚了,也解决了
文章里面主要是分享了下排查问题的过程,至于spring源码解读,我也不是很精通,有时间有机会在为大家呈上,欢迎大家来交流,指出文中一些说错的地方,让我加深认识,愿大家没有bug,谢谢!
网友评论