先声明本人并不是标题党,如果看了本篇文章并且认为没有得到任何收获,请您随便留言骂我,本人绝不还口,已经对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,你信吗?的更多相关文章

  1. 一个excel(20M)就能干趴你的poi,你信吗?

    自从上一篇:一个普通类就能干趴你的springboot,你信吗?后,很巧的是这次又发现一个问题,所以有了这篇文章,还是想沿用上篇的”流水帐“的方式查找问题和解决问题.这篇文章主要是因为使用POI导入一 ...

  2. PHP用单例模式实现一个数据库类

    使用单例模式的出发点: 1.php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 使用单例模式, 则可以避免大量的new 操作消耗的资源. 2.如果系统中需要有一个类来全局控制某些 ...

  3. 使用代码向一个普通的类注入Spring的实例

    转载请在页首注明作者与原文地址 一:应用场景 什么是普通的类,就是没有@Controller,@Service,@Repository,@Component等注解修饰的类,同时xml文件中,也没有相应 ...

  4. 一个Java文件至多包含一个公共类

    编写一个java源文件时,该源文件又称为编译单元.一个java文件可以包含多个类,但至多包含一个公共类,作为编译时该java文件的公用接口,公共类的名字和源文件的名字要相同,源文件名字的格式为[公共类 ...

  5. 一个java源文件中为什么只能有一个public类。

    我们都遇到过一个源文件中有多个java类,但当第一个类使用public修饰时,如果下面还有类使用public修饰,会报错.也就是是说一个java源文件最多只能有一个public类. 当有一个publi ...

  6. 很久以前写的一个 ShareRestrictedSD 类

    代码中一开始的 几个 USES 单元,可能是多余的. unit ShareRestrictedSD; interface uses Windows, Messages, SysUtils, Class ...

  7. 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。

    22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...

  8. 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 ...

  9. Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。

    #29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类T ...

随机推荐

  1. 手把手教你Pytest+Allure2.X定制报告详细教程,给自己的项目量身打造一套测试报告-02(非常详细,非常实用)

    简介 前边一篇文章是分享如何搭建pytest+Allure的环境,从而生成一份精美的.让人耳目一新的测试报告,但是有的小伙伴或者童鞋们可能会问,我能不能按照自己的想法为我的项目测试结果量身打造一份属于 ...

  2. springboot集成Spring Data JPA数据查询

    1.JPA介绍 JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.它的出现主要是为 ...

  3. Flask基础(01)-->Flask框架介绍

    什么是Flask? 说白了,Flask就是一种web框架 在python中常用的框架有 flask django tornado 什么又是web框架呢?  为什么要使用web框架呢? 增强扩展性和稳定 ...

  4. SpringMvc问题记录-Controller对于静态变量的访问分析

    问题描述 在于朋友的讨论中分析到一种场景,即:Controller对于一个类中的静态变量进行访问时,如果第一个接口修改该静态变量的数据,另外一个接口获取该静态变量的数据,那么返回的结果是什么? 操作步 ...

  5. Android 手机端自动化测试框架

    前言: 大概有4个月没有更新了,因项目和工作原因,忙的手忙脚乱,趁十一假期好好休息一下,年龄大了身体还是扛不住啊,哈哈.这次更新Android端自动化测试框架,也想开源到github,这样有人使用才能 ...

  6. Java后台开发方向面试题集合

    内容会不断更新. 初衷是每次看面经肯定都会有一些一时反应不过来的问题,希望集中记录一下便于自己查看. 而答案部分谷歌就很好,当然有些问题可能需要多次谷歌. 对于一些记不住的答案,我也会持续写上一些. ...

  7. Curl的移植编译以及注意事项

    最近需要用curl来发送http请求,遇到了不少问题,查了不少资料,都是零零散散的,现在总结下.   1.移植编译 ./configure --prefix=$(PWD)/build --host=a ...

  8. TouchListener PK OnTouchEvent + 多点触碰

    1.基于监听的TouchListener 代码示例: 实现效果图: 实现代码: main.xml <RelativeLayout xmlns:android="http://schem ...

  9. 即学即用的 30 段 Python 实用代码

    [☞ 分享:最全最新的Python学习大礼包 ☜ 点击查看](https://mp.weixin.qq.com/s?__biz=MzU2MzgyODA4OA==&mid=100000592&a ...

  10. Python flask 构建可扩展的restful apl☝☝☝

    Python flask 构建可扩展的restful apl☝☝☝ Flask-RESTful是flask的扩展,增加了对快速构建REST API的支持.Flask-RESTful通过最少的设置鼓励最 ...