一个普通类就能干趴你的springboot,你信吗?
先声明本人并不是标题党,如果看了本篇文章并且认为没有得到任何收获,请您随便留言骂我,本人绝不还口,已经对springboot了如指掌大大神,求放过!
不BB了,直接上代码,请各位在自己的springboot项目随便一个包下复制进去如下类(不要修改什么东西),如果你的springboot还能站起来算我输!
@Component
public class Environment {
}
运行springboot的启动类会报如下错误,然后你删除这个类,你的springboot又能健步如飞了,你可能就会怀疑人生了,这代码有毒。先说明我的springboot是2.1.7.RELEASE,我也试了最新的2.2,报错基本一致!
-- ::46.181 INFO --- [ main] com.rdpaas.platform.demo.RunApplication : Starting RunApplication on DESKTOP-9KL4U5L with PID (E:\project2018\platform\demo\target\classes started by in E:\project2018\platform)
-- ::46.183 INFO --- [ main] com.rdpaas.platform.demo.RunApplication : No active profile set, falling back to default profiles: default
-- ::48.490 WARN --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'methodValidationPostProcessor' defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]: Unsatisfied dependency expressed through method 'methodValidationPostProcessor' parameter ; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least bean which qualifies as autowire candidate. Dependency annotations: {}
-- ::48.499 INFO --- [ main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
-- ::48.615 ERROR --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : ***************************
APPLICATION FAILED TO START
*************************** Description: Parameter of method methodValidationPostProcessor in org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration required a bean of type 'org.springframework.core.env.Environment' that could not be found. Action: Consider defining a bean of type 'org.springframework.core.env.Environment' in your configuration.
如上很普通,谁都可能加上的类,就这样一个简单的类,居然可以直接导致springboot站不起来了,如果你认命了,其实也很好解决,你可能会换个名字试试,或者你压根就不会用到这个类,或者是你给@Component("env"),加个别名,可能就碰巧解决了这个问题,那么这时候你可能会当成springboot已经规定了你不能使用关键字environment作为bean的名称,那么这个问题就变得一文不值了,因为你已经认命了,不让我用我不用就行了,以后一辈子都不用这个类名就好了。眼不见心不烦,我用简称Env还来的省事点。不过我个人认为我们遇到难题应该迎难而上,不能随便认命,我们都是骄傲的程序员。应该抱着希望遇到难题的心态,积极去面对难题,多解决一些疑难杂症,用知识和经验武装自己,努力成长,走上人生巅峰!如果看到这里觉得不认命的请跟着我一起看看这个问题到底为啥会出现吧!
接下来我们一步步来找到问题的根源,为啥用了这个类,springboot就不举了?首先我们从启动的错误提示中找到唯一的关键信息:method methodValidationPostProcessor in org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration required a bean of type 'org.springframework.core.env.Environment' that could not be found。从这句话可以看出来在:org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration这个类中的这个方法:methodValidationPostProcessor 中需要一个:org.springframework.core.env.Environment类的对象作为参数,但是他找不到,看这个名字和我们自己定义的一样,先在idea中找到如上类的methodValidationPostProcessor方法的源码所在:

为了验证图中的猜想,由于我这不是源码编译的,所以只能自己模仿这个类同样使用@Bean修饰一个方法看看是不是里面的参数都是完全按照参数名称注入的(可以先注释掉之前的Environment类排除那个类的影响),如下
package com.rdpaas.platform.demo.env;
import org.springframework.stereotype.Component;
/**
* 用作测试的bean
* @author: rongdi
* @date: 2019-11-2 0:12
*/
@Component
public class TestBean {
}
package com.rdpaas.platform.demo.env; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* 模仿org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
* 使用@Configuration注解,并且提供一个static的@Bean修饰的方法
* @author: rongdi
* @date: 2019-11-2 0:11
*/
@Configuration
public class Config { @Bean
public static TestBean create(TestBean tb) {
System.out.println(tb);
return tb;
} }
如上我们使用默认的beanName为testBean的bean,然后Config类中注入的名称是tb运行springboot发现可以正常打印出tb对象,说明名称不一致同样可以注入成功,所i有我们大概可以排除之前的猜想,想想顶顶大名的springboot也不可能这么low逼吧!
之前的猜测被推翻,我们只能老老实实的使用debug一步步从springboot的入口一步步跟踪进去看看到底啥时候开始报错的,这一步如果不熟悉spring代码的一定要耐心一步步找找,如下

之前对spring底层源码有一定了解的应该知道spring是先把那些注解和xml声明的类加载到一个map里然后再进行初始化的,这个map就是beanDefinitionMap,一步步断点到spring最核心的方法refresh中

当执行到上图蓝色位置时,也就是执行完invokeBeanFactoryPostProcessors(beanFactory)方法后,当前beanFactory里的beanDefinitionMap对象中找到了我们声明的environment对象的身影,如下


断点的过程中发现代码太多要是一步步找过去,很容易就放弃,所以我们再从上面的错误日志找找有用信息,然后通过全局搜索看看到底时哪里报出的错误,如通过报错里的警告信息:expected at least 1 bean which qualifies as autowire candidate. Dependency annotations直接使用全局搜索(前提时先断点跑一边,然后根据idea提示下载好spring的源代码)


点击上述方法后如下一如既往的打个断点重新跑一边看看

这时候可以把断点打在if那行再进去看看是啥情况导致进入了这里,然后我们可以确定只要是if返回了true,就必然会导致报错,然后我们注释掉自己的Environment类看看还能否进入到这里,通过注释和不注释的对比我们发现两种情况断点之后有如下差别

所以问题的关键就是加入了自己的Environment类导致matchingBeans的map为空而产生了本例中的报错信息。


接下来我们为了调试的效率,在每个出现beanName参数的方法打断点都使用这个条件断点,现在问题就回归到为啥加上了自己的Environment类后给matchingBeans提供数据的方法findAutowireCandidates为空了。一如既往的条件断点打到里面

使用同样的方式在如下方法也加上条件断点,再次重复执行断点直到进去如下

耐心的再用如上同样方式进入这个方法,这里由于有多个类请使用F5(断点进入方法,可能快捷键不一样)


从上看出,刚进去循环的数组中明显有environment,但是结果为啥就成了空数组,进一步断点发现





对比以上两个结果,很明显当我们自己添加了Environment类后,singletonObjects肯定有一个移除操作,然后我们找到所有singletonObjects.remove()的地方打一个条件断点:beanName.equals("environment"),很明显从逻辑上看,只要springboot不是全部清空,必然会有一个 remove("environment")才能解释以上两者的差别。
然后我们再在singletonObjects.put()相关的方法都打上同样的条件断点,放心大胆的继续重新断点执行一遍,第一次进入断点如下
上图如果执行过addSingleton方法后this.singletonObjects中确实会放入以environment为key,以spring的StandardServetEnvironment为value的键值对进去,这里就不截图了,免得又要重新跑一次断点,直接点击左边调用栈那个679行后如下:



这里可以先记录下左边环境对象到底是在spring最重要的refresh方法的那一步

根据上面得出的结论,之所以报错最根本的原因就是这个singletonObjects找不到这个environment了,而这里有,所以肯定有地方删除了这个key,因为这个map看起来如此重要,spring不会无缘无故直接clear吧,所以只要找到唯一的删除key的方式singletonObjects.remove(),并打上上面说的条件断点,这一点上面其实说过了,那我们继续跑断点,直到找到在哪删除了这个key






其实复盘一下整个调试过程,发现其实源头如下

其实我也不知道这算不算是springboot的bug,还是其实只是一个关键字的限定,因为最终解释权不在于我,就像mybatis中的xml里大于符号要用>不然别人根本解析不了,从这一点来说mybatis使用xml存放sql实际上限制了我们使用大于小于等等这些符号的权力,只能用转义字符类似别名的东西替代。其实这里也是类似,也可以理解成人家系统需要,你要用这个请改个名字或者取个别名,比如@Component("env)。不过我还是希望springboot能还我们使用单词的自由,希望英文好的朋友可以发发邮件让springboot团队考虑下,哈哈!
最后来个篇中总结:
1) 从文笔上来说,一如既往的没有文笔,请各位大大海涵,真的尽力了,奈何胸无点墨!
2) 从排版来说,一如既往的没有排版,我是个纯技术人,这些花里胡哨的东西,真的一点不会,同样请各位包涵
3) 从知识点来说,其实这篇博客主要是给小白们分享一下看源码的技巧和基本的调试能力,还有遇到问题的处理态度。首先从这篇文章中应该能清晰的get到逆向思考一步步找的问题的方法,其次应该能获取到一些断点调试源码的技巧,最后也应该能学会方法调用栈的作用。其实懂这三点基本就够了,spring这些源码是否看过也不会影响你最终能找到这个问题的根源这一结果,最多会影响你找到根源的时间。
4) 从用心程度来说,这篇博客自认为是足够用心,周五晚上从下班回家一边一步步断点一遍写这篇博客,直到凌晨三点多才冲忙洗洗睡。文章里基本上把我知道的关于这个知识点的所有东西通过清晰的图文方式一步步展现,本人热爱技术,也喜欢分享技术,希望与广大程序猿们相濡以沫,共同进步!
5) 从文章质量来说,对大牛一文不值,对小白有一定帮助,希望大牛们多多包涵,不要喷我,有错误之处请多多指正。
一个普通类就能干趴你的springboot,你信吗?的更多相关文章
- 一个excel(20M)就能干趴你的poi,你信吗?
自从上一篇:一个普通类就能干趴你的springboot,你信吗?后,很巧的是这次又发现一个问题,所以有了这篇文章,还是想沿用上篇的”流水帐“的方式查找问题和解决问题.这篇文章主要是因为使用POI导入一 ...
- PHP用单例模式实现一个数据库类
使用单例模式的出发点: 1.php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 使用单例模式, 则可以避免大量的new 操作消耗的资源. 2.如果系统中需要有一个类来全局控制某些 ...
- 使用代码向一个普通的类注入Spring的实例
转载请在页首注明作者与原文地址 一:应用场景 什么是普通的类,就是没有@Controller,@Service,@Repository,@Component等注解修饰的类,同时xml文件中,也没有相应 ...
- 一个Java文件至多包含一个公共类
编写一个java源文件时,该源文件又称为编译单元.一个java文件可以包含多个类,但至多包含一个公共类,作为编译时该java文件的公用接口,公共类的名字和源文件的名字要相同,源文件名字的格式为[公共类 ...
- 一个java源文件中为什么只能有一个public类。
我们都遇到过一个源文件中有多个java类,但当第一个类使用public修饰时,如果下面还有类使用public修饰,会报错.也就是是说一个java源文件最多只能有一个public类. 当有一个publi ...
- 很久以前写的一个 ShareRestrictedSD 类
代码中一开始的 几个 USES 单元,可能是多余的. unit ShareRestrictedSD; interface uses Windows, Messages, SysUtils, Class ...
- 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。
22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...
- Java集合-5. (List)已知有一个Worker 类如下: 完成下面的要求 1) 创建一个List,在List 中增加三个工人,基本信息如下: 姓名 年龄 工资 zhang3 18 3000 li4 25 3500 wang5 22 3200 2) 在li4 之前插入一个工人,信息为:姓名:zhao6,年龄:24,工资3300 3) 删除wang5 的信息 4) 利用for 循
第六题 5. (List)已知有一个Worker 类如下: public class Worker { private int age; private String name; private do ...
- Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。
#29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类T ...
随机推荐
- 百度富文本编辑器ueditor添加到pom
<!-- 百度富文本编辑start --> <dependency> <groupId>com.baidu</groupId> <artifact ...
- JDK 自带的性能监控工具
JDK安装完后,其内置了许多的监控工具,常用的有jvisualvm,jconsole,jps,jmap,jinfo,jstat,jstack,这些命令都在jdk安装的bin目录下: 1.jvisual ...
- springboot 项目打包部署后设置上传文件访问的绝对路径
1.设置绝对路径 application.properties的配置 #静态资源对外暴露的访问路径 file.staticAccessPath=/upload/** #文件上传目录(注意Linux和W ...
- phaser学习总结之Text对象详解
前言 在phaser学习总结之phaser入门教程中,我们已经入门了phaser,对phaser也有所了解但是我们并没有对phaser中的每个对象的属性和方法进行详解,本章将对phaser中的Text ...
- 虚拟机ubuntu 网速慢的解决方法
其实虚拟机网速慢,我觉得就两个限制因素.一个是虚拟机的内存,内存小了,上传和下载的速率就慢了,就像内存小的虚拟机跑得慢是一个道理:还有一个就是网络连接方式,这里我使用的是桥接,之前我使用的NAT模式, ...
- python 写入txt的新方法
最新发现有新方法可以对txt等进行操作,比较有意思,之前没见过,故记录下 传统方法 with open(ur'D:\Desktop\a123.txt', 'a') as f: #以写的方式打开 f.w ...
- android 6.0导航栏 NavigationBar影响视图解决办法
在开发app的时候会遇到有些测试手机没有物理按钮,比如最近在做的一个app在小米手机上运行显示效果很好,但是在华为P7手机上显示就乱了,底部的NavigationBar直接覆盖在主视图上,导致按钮无法 ...
- Butter Knife
Butter Knife,专门为Android View设计的绑定注解,专业解决各种findViewById. 简介 对一个成员变量使用@BindView注解,并传入一个View ID, Butter ...
- 基于Influxdb对InfluxDBResultMapper的一点扩展
理想很饱满,现实很骨感. 由于业务需要"灵活可配置"的功能需求,在使用java开发Influxdb查询功能的时候,遇到了一个问题,Measurement注解的名称有可能需要动态变化 ...
- Java容器总结
容器总结 Java容器工具包框架图 List,Set,Map三者的区别 List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象 Set(注重独一无二的性 ...