背后的故事

某个平凡熟悉的早上,传来测试同学的一阵哀嚎:那个谁!你提测的代码连运行都不能运行,苦涩。

我默不作声,因为主项目还没有完全服务化,主项目的整体 war 包太大,加上从来没有讲究过,开发同学跑一个测试用例,往往启动 Spring 就要花一分半钟,哪里有心情按规范跑单测呢?同问了几个开发同学也都有同样的痛点,感觉解决单测环境刻不容缓,古人云:工欲善其事,必先利其器,对吧?

来了,老弟

将日志级别调至Debug级别

没错,这是我们需要做的第一步最重要的步骤,开启单测,把日志打到一个文件里,从头撸到尾,究竟你这加载的一分半钟究竟干嘛了~

一些Spring配置初始化根据ComponentScan扫路径的Class类,加入待注入候选列表根据Mapper扫路径的Class类,加入待注入候选列表对扫出来的Mapper创建MapperFactoryBean创建注入@Configuration里@Bean注解的Bean的BeanDefinitions预加载一些Bean一些组件例如,PostProcessor、Advisor初始化一些中间件例如数据库、缓存、消息队列加载扫描的Bean的初始化,依赖注入。。Bean的PostConstruct开始跑....//可能不是很全,列举了其中一部分

默默的看了眼日志,20M,妈耶,引了一个 ApplicationContextAware 看了一下 BeanFactory ,好吧,加载了1500个 Bean Spring 默认的Ioc容器会把所有的Bean在启动时,都加载成功,首先想到的措施是让Bean懒加载,按需加载,不用的就不加载嘛,很简单!

坑来了: 如何对Bean进行懒加载?

简单的网上冲浪了一下,我们需要将

注解法:@ComponentScan(value = "com.evanyz",lazyInit = true) //将这个配置设为true//xml里是beans里有一个default-lazy-init标签

可是设置成功之后,完全不生效,还是1500个,尝试许久,还是没生效,感觉很懵逼,甚至都有点开始怀疑 Spring 了。

排查许久后,突然发现为什么 Debug 的时候,会报一些该Bean重复已存在忽略的错,突然灵光一现。MD,我们项目里写代码根本不讲究,每一个子项目里,例如 common biz 都含有 Spring 的初始化文件。

也就是说,从这个初始化配置之后,继续扫其他的配置文件,还是会继续加载,导致之前的配置失效。。

举例:我在test的初始化类里配置了 @ComponentScan(value = "com.evanyz",lazyInit = true) ,当他扫到biz子项目的时候,发现另外一个Spring的配置文件是@ComponentScan(value = "com.evanyz")这时候懒加载会被覆盖掉,就不生效了。。

这时候想到的办法就是 在test配置里加上一些操作,解释见注解

@ComponentScan(//扫包    value = {com.evanyz"},   //排除一些Bean    excludeFilters = {        //做点小优化,让他把一些在跑单测时的扩展点不要注入        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {            SmsServicePostProcessor.class, CatUrlPostProcessor.class,        }),        //按政策排除,把一些其他项目里的Spring配置去掉        @ComponentScan.Filter(type = FilterType.REGEX, pattern = {            "com\\.evanyz\\.test\\..*",            "com\\.evanyz\\.biz\\.springconfig..*",            "com\\.evanyz\\.common\\.springconfig..*",            "com\\.evanyz\\.common\\.cat..*"        }),        //最后清掉了发现,还是有一些配置被加载了        //一不做二不休,我全干掉,搞白名单还不行吗?哭哭        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {            ComponentScan.class, Configuration.class, ImportResource.class        }),    }, lazyInit = true)

经过以上的一顿操作,Spring终于懒加载了,直观的看一下之后的 Debug ,瘦身了3倍,爽!

继续优化,Mock一些毒瘤依赖

虽然 Spring 已懒加载,但是依赖很乱,往往依赖一个服务,又要注入很多的类,而且很多的业务的类,都写了 @PostConstruct ,如果里面包含业务代码,例如查库啊之类的,你就呵呵吧。一次Spring启动能给你跑100条Sql,能不慢吗?

来,壮士断腕,把这些毒瘤,会预先加载的类,选一些不重要的在单测不需要用的都做个Mock,不要让这个拖垮我们的环境!

精益求精,搜寻日志,发现异常类加载超过10s,来魔改

这时候,其实一次单测已经在30s就可以搞定了,但是本着有点追求的想法,还是想再优化一下。

突然发现有一个可疑的日志

我们用的 Cat 做监控,我们项目里有很多 Cat 打点的工具类,只要跑到一个打点上,Cat就会开始加载(明明连不上),但是这一步骤估计是 IO 之类的东西,加载一下居然花了10几秒,我的天,肯定要干掉!

怎么干呢?因为这些打点是耦合在代码里的,不好动,这时候想到的解决方案就是看看 Cat 能不能关掉,后来冲浪了一番,发现我们这个旧版本没办法关。怎么办呢?被逼出来的

在代码目录里,搞个同名类,覆盖原有类,让他默认打点的时候不要初始化。魔改一把,生效!美滋滋!

classLoader默认会读同名最近的那个类

最后跑了一下单测, 16s ,好爽!

自从单测优化之后,后面制定规范让大家交付测试的时候自己先跑遍单测,这样就能有效的避免因为一些小错误返工 4、5次的尴尬,而且单测测的更全,更不容易出错,利已利民。

这里贴一下单测的核心类的注释:

多点时间陪陪家人

我:爽吗?我问对面的开发。

他:太爽了!

我笑了笑,深藏功与名(:

作者:小之Evan@票牛网

掘金:https://juejin.im/user/5a7b9c106fb9a063417b182d

加入星球特权

1、从前端到后端玩转Spring Cloud

2、实战分库分表中间件Sharding-JDBC

3、实战分布式任务调度框架Elastic Job

4、配置中心Apollo实战

5、高并发解决方案之缓存

6、更多课程等你来解锁,20+课程

Spring Boot 中测试用例简单演示 在测试用例中通过指明扫描范围,加快 spring 容器的 启动 速度 通过使用h2内存数据库加快测试 速度 和隔离测试环境和他环境数据的相互的问题 Spring 扫描范围和 启动 速度 随着业务的发展,项目复杂度度增加引用的jar和业务代码越来越多, Spring 应用在 启动 时需要扫描和实例化装载的Bean越来越多,以及环境上下文的处理,这势必会导致 启动 时间边长,特别是有中间的依赖的时候(例如连接数据库、消息队列、NoSql等)。 但是在测试的时候,我们可能只是测试单个方
如图在 spring 容器 启动 打印日志,读取xml配置文件,日志停留到读取某个文件不继续执行打印日志。 原因:xsi:schemalocation 中配置的 xsd 版本与项目中的版本不一致。 1.在 spring 启动 是读取XMl时,会加载XSD对XML进行校验。 2.默认情况下是加载本地的XSD文件(如下图),XSD文件一般是保存在对应的jar包里。 3.版本不一致,上图中jms的XSD版本只有2.5 3.0 3.1,但是我在配置文件中使用4.0,这种情况一般发生在复制其他地方的配置文件导致的。.
背景:在项目提测前,自己需要对代码逻辑进行验证,所以 单元测试 必不可少。但是现在的java项目几乎都是基于 Spring Boot系列开发的, 所以在进行 单元测试 时,执行一个测试类就要 启动 spring boot项目,加载上下文数据,每次执行一次测试都要再重新加载上下文环境,这样 就会很麻烦,浪费时间;在一次项目中,我们使用自己的技术框架进行开发,每次 单元测试 时都要初始化很多数据(例如根据数据模型建立表, 加载依赖其它模块的类),这样导致每一次 单元测试 时都会花3-5分钟时间(MacOs 四核I
如何将Spock模拟注入到 Spring 集成测试中 该项目旨在用作示例指南,以说明如何将Spock与 Spring (和 Spring Boot)结合使用,并结合使用 Spring 配置和Spock模拟。 有时,您想使用整个 Spring 上下文进行更完整的集成测试。 这通常会导致您宁愿嘲笑的少数问题点。 在 Spring Boot 1.4中,他们引入了很多很酷的测试玩具,包括@MockBean 。 遗憾的是,仅在使用JUnit进行测试时才支持。 值得庆幸的是,Spock 1.1引入了DetachedMockFactory 。 结合新的 Spring Boot @ Test Configuration您可以提高测试的幸福感。 这个例子的核心存在于我们的PersonControllerInt Test 。 PersonControllerInt Test 启动 Spring 上下文,因此我们可以对REST端点进行M
项目最近做了大的改动,添加了大量配置, Spring Boot项目 启动 时间从原来的100 变成了400 以上。从大量无效日志中一时难以看出瓶颈所在。首先需要一款性能分析软件,测试了几款Java性能分析工具,发现性能分析工具反而成了性能瓶颈(或许是跟项目水土不服),不过最终发现Intel® VTune™ Profiler对项目性能影响甚小。 VTune是 Intel 提供的一个强大的应...
别人都写从0开始实现xxx,我先从-1开始就显得更牛逼一些。今天,先开个头,来教大家怎么实现一个中间件。首先,我们新建一个多 module 的项目用于测试。项目包含两个模块,用户中间件模块的开发,用于测试。 开发中间件 项目创建 OK 了,接着开始开发一个最最最简单的中间件。在目录下创建文件,用于自动装配,别问我啥是自动装配,然后配置一个自动装配类。 实现 ,最简单的方式,直接使用注解声明一个 Bean 交给 Spring 管理。 然后实现真正的中间件逻辑的处理部分。 这样的话,一个最简单的中间件就开发好了
1. 加载配置文件: Spring 启动 时会读取配置文件,包括 XML 配置文件、Java 配置类等。 2. 创建 IOC 容器: Spring 会根据配置文件中的信息创建 IOC 容器,也就是 ApplicationContext。 3. 实例化 Bean: Spring 会根据配置文件中的信息,实例化 Bean,并将其放入 IOC 容器中。 4. 注入依赖: Spring 会根据配置文件中的信息,将 Bean 之间的依赖关系注入到对应的 Bean 中。 5. 调用初始化方法: Spring 会调用 Bean 中的初始化方法,例如 init-method。 6. 应用程序使用 Bean: Spring 启动 完成后,应用程序可以通过 IOC 容器获取 Bean 并使用。 以上就是 Spring 启动 流程 的主要步骤。