务实优化:将本地单元测试Spring启动速度从1分半优化到16秒,全流程详解

引子

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

我默不做声,由于主项目尚未彻底服务化,主项目的总体war包太大,加上历来没有讲究过,开发同窗跑一个测试用例,每每启动Spring就要花一分半钟,哪里有心情按规范跑单测呢?同问了几个开发同窗也都有一样的痛点,感受解决单测环境刻不容缓,古人云:工欲善其事,必先利其器,对吧?spring

来了,老弟

将日志级别调至Debug级别

没错,这是咱们须要作的第一步最重要的步骤,开启单测,把日志打到一个文件里,从头撸到尾,究竟你这加载的一分半钟究竟干吗了~数据库

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

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

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

简单的网上冲浪了一下,咱们须要将缓存

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

但是设置成功以后,彻底不生效,仍是1500个,尝试许久,仍是没生效,感受很懵逼,甚至都有点开始怀疑Spring了。bash

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

也就是说,从这个初始化配置以后,继续扫其余的配置文件,仍是会继续加载,致使以前的配置失效。。工具

举例:
我在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,能不慢吗?ui

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

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

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

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

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

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

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

classLoader默认会读同名最近的那个类
复制代码

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

总结一把

自从单测优化以后,后面制定规范让你们交付测试的时候本身先跑遍单测,这样就能有效的避免由于一些小错误返工 四、5次的尴尬,并且单测测的更全,更不容易出错,利已利民。

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

多点时间陪陪家人

我:爽吗?我问对面的开发。
他:太爽了!
我笑了笑,深藏功与名(:
复制代码
相关文章
相关标签/搜索