点击上方“Java知音”,选择“置顶公众号”

技术文章第一时间送达!

作者:最后Q泪滴

cnblogs.com/rongdi/p/11780204.html

先声明本人并不是标题党,如果看了本篇文章并且认为没有得到任何收获,请您随便留言骂我,本人绝不还口,已经对springboot了如指掌大大神,求放过!

不BB了,直接上代码,请各位在自己的springboot项目随便一个包下复制进去如下类(不要修改什么东西),如果你的springboot还能站起来算我输!

@Component
public class Environment {
}

运行springboot的启动类会报如下错误,然后你删除这个类,你的springboot又能健步如飞了,你可能就会怀疑人生了,这代码有毒。先说明我的springboot是2.1.7.RELEASE,我也试了最新的2.2,报错基本一致!

2019-11-02 00:42:46.181  INFO 13568 --- [           main] com.rdpaas.platform.demo.RunApplication  : Starting RunApplication on DESKTOP-9KL4U5L with PID 13568 (E:\project2018\platform\demo\target\classes started by 49519 in E:\project2018\platform)
2019-11-02 00:42:46.183  INFO 13568 --- [           main] com.rdpaas.platform.demo.RunApplication  : No active profile set, falling back to default profiles: default
2019-11-02 00:42:48.490  WARN 13568 --- [           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 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2019-11-02 00:42:48.499  INFO 13568 --- [           main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-11-02 00:42:48.615 ERROR 13568 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : ***************************
APPLICATION FAILED TO START
***************************Description:Parameter 0 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 {@Beanpublic static TestBean create(TestBean tb) {System.out.println(tb);return tb;}}

如上我们使用默认的beanName为testBean的bean,然后Config类中注入的名称是tb运行springboot发现可以正常打印出tb对象,说明名称不一致同样可以注入成功,所i有我们大概可以排除之前的猜想,想想顶顶大名的springboot也不可能这么low逼吧!

扩展:11张流程图帮你搞定 Spring Bean 生命周期

之前的猜测被推翻,我们只能老老实实的使用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")才能解释以上两者的差别。扩展:SpringBoot内容聚合

然后我们再在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) 从文章质量来说,对大牛一文不值,对小白有一定帮助,希望大牛们多多包涵,不要喷我,有错误之处请多多指正。

END

Java面试题专栏

【40期】说一下线程池内部工作原理

【39期】Mybatis面试18问,你想知道的都在这里了!

【38期】一份tcp、http面试指南,常考点都给你了

【37期】请你详细说说类加载流程,类加载机制及自定义类加载器

【36期】说说 如何停止一个正在运行的线程?

【35期】谈谈你对Java线程之间通信方式的理解

【34期】谈谈为什么要拆分数据库?有哪些方法?

【33期】分别谈谈联合索引生效和失效的条件

【32期】你知道Redis的字符串是怎么实现的吗?

【31期】了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?应对措施是什么

我知道你 “在看”

一个普通类就能干趴你的springboot,你信吗?相关推荐

  1. apache poi 修改docx表格_一个excel(20M)就能干趴你的poi,你信吗(附源码)?

    点击上方"阿拉奇学Java",选择"置顶或者星标" 优质文章第一时间送达! 链接: www.cnblogs.com/rongdi/p/11872810.html ...

  2. 一个excel(20M)就能干趴你的poi,你信吗(附源码)?

    点击上方"阿拉奇学Java",选择"置顶或者星标" 优质文章第一时间送达! 链接: www.cnblogs.com/rongdi/p/11872810.html ...

  3. 设计一个矩形类rectangle_万字长文带你捋清六种设计模式的设计原则(建议收藏)...

    对于设计模式,自己很早之前就看了好多本设计模式书籍,其中一些还看了好几遍,也一直希望自己能在编码的时候把这些设计模式用上去.可是,在日常的打码中,用的最多的就是单例,其次是观察者和建造者模式 ( bu ...

  4. 写一个ArrayList类的动态代理类

    动态代理可以提供对另一个对象的访问,同时隐藏实际对象的具体事实,代理对象对客户隐藏了实际对象.动态代理可以对请求进行其他的一些处理,在不允许直接访问某些类,或需要对访问做一些特殊处理等,这时候可以考虑 ...

  5. vue2 构建一个旅游类WebApp

    前言 从看了慕课上面黄易老师的饿了么课程后,总感觉学了VUE之后要做点什么,趁着热情兴趣还在,自己也用VUE全家桶写了一个旅游类的APP,为什么选择做旅游类的呢??呵呵,我不会告诉你虽然很多地方都没去 ...

  6. python 多线程 类_Python中如何自定义一个多线程类呢?

    摘要: 下文讲述Python中自定义一个多线程类的方法分享,如下所示: 实现思路: 1.定义一个类继承threading.Thread 2.在自定义类中构造函数重写run方法 例: Python3中自 ...

  7. 《JAVA练习题目9》 创建一个Person类和一个Family类。(这次的OJ实在写的没有状态,被样例搞崩了,这次代码还有好多代码复制等一系列不良现象,就是为了过OJ写的。。大家凑活看一下吧)

    题目内容: 创建一个Person类和一个Family类.其中, Person类包含编号id(整型).名字name(字符串).性别gender(字符串)三个属性,和一个初始化对象所有属性的有参构造方法, ...

  8. 第一个OC类、解析第一个OC程序

    01第一个OC 类 本文目录 • 一.语法简介 • 二.用Xcode创建第一个OC的类 • 三.第一个类的代码解析 • 四.添加成员变量 • 五.添加方法 • 六.跟Java的比较 • 七.创建对象 ...

  9. 一个有趣的算法问题:如何定义一个分数类

    一个来自于C++程序设计的经典问题.如何定义一个分数类,实现分数的约分化简,分数之间的加法.减法.乘法.除法四则运算? 1.初见 刚看到这道题的时候,第一感觉是挺简单的啊,就是基本的面向对象,定义对应 ...

最新文章

  1. 机器学习第8天:IPyhon与Jupyter notebook
  2. Winform中实现文件批量更名器(附代码下载)
  3. 从 CVE-2020-1048 到 CVE-2020-17001:Windows打印机模块中多个提权漏洞分析
  4. JDK1.7 API -- Scanner
  5. FTP初始化文件.netrc使用技巧[转发]
  6. mfc 二进制转换成图像_图像相似度---感知哈希算法(phash)---用matlab实现
  7. 一个关于全局变量的问题
  8. Android Multimedia框架总结(十八)Camera2框架从Java层到C++层类关系
  9. 敏感数据加密存储方案
  10. 看看同一种字体是如何对应不同的字体文件的
  11. 基于Java毕业设计爱心公益网站设计与制作源码+系统+mysql+lw文档+部署软件
  12. 服务器显示拥挤进不去怎么办,《拥挤城市》游戏进不去怎么办 玩不了解决方法...
  13. PC浏览器添加背景音乐 VS 手机浏览器添加背景音乐
  14. 姓莫的女孩子叫什么名字好听
  15. 昆仑mcp文件是什么版本_高性价比重疾险昆仑健康保,12月31日截止投保,想要要趁早!...
  16. HTML基于Vue实现Cron生成器
  17. 一家之言—通信专业IT男的弊端
  18. 关系数据库和非关系数据库
  19. John the Ripper 安装
  20. Freeline使用攻略

热门文章

  1. 血雨腥风43载,苹果帝国背后的5个男人
  2. 拼多多:扶贫项目正连续遭受网络舆情涉黑团伙攻击
  3. 魅族16s封胶事件结果:属极个别封胶漏点 双方协商执行一赔二
  4. 旅程落幕!网易相册将停止运营 这里有你的回忆吗?
  5. 韩国造智能手机时代走向终结:昔日巨头纷纷关闭生产线或削减产量
  6. 你大爷还是你大爷!三星震撼首发折叠屏智能手机Galaxy Fold
  7. 被江苏网警点名后 咪蒙发道歉信:公众号停更2个月 微博永久关停
  8. 程序员被公司开除,隔阵子领导命令回前公司讲解代码,网友直呼:关我嘛事?
  9. mysql查询每个用户第一条数据_MySQL数据库订单表按用户邮箱字段分组查询每个用户的第一条记录...
  10. vim可以用来编译python吗_Linux下编译Vim以支持python2.x