优化超大单体Spring Boot项目开发环境启动速度

之前维护的一个项目,项目启动需要12分钟,开发环境需要频繁启停,极大影响开发效率

分析

使用IDEA profiler收集启动阶段的数据,并用idea打开火焰图分析

发现启动时间大部分都消耗在注入Autowired资源以及创建Aop切面

同时整理了项目的模块发现开发环境下有一部分是不常用的

优化方案

首先是修改pom使开发环境不加载不常用的模块,启动速度快了60秒左右

然后编写了脚本静态分析源码,收集了所有没有使用的@Autowired和@Resource,并手动处理(项目代码规范不是很好, 不确定是否有特殊的引用, 虽然最后没发现这样的引用),启动速度快了30秒左右

import java.io.File

val dir = File("/path/to/a/project")
val unused = mutableListOf<Pair<String, String>>()
val autowiredPattern = Regex("(private|public|protected)\\s+\\w+\\s+(?<varName>\\w+)\\s*;")

fun collect(file: File) {
    if (file.isDirectory) {
        // 忽略编译产物
        file.name == "target" && file.parentFile.resolve("pom.xml").exists() && return
        file.listFiles()?.forEach { collect(it) }
        return
    }
    if (!file.name.endsWith(".java")) {
        return
    }
    // println("scan ${file.name}")
    // 找到所有autowired
    val autowiredList = mutableListOf<String>()
    var nextIsAutowired = false
    val lines = file.readLines().filter { it.startsWith("//") }
    for (line in lines) {
        if (line.trim().run{ startsWith("@Autowired") || startsWith("@Resource") }) {
            nextIsAutowired = true
        }
        if (nextIsAutowired) {
            val result = autowiredPattern.find(line)
            if (result != null) autowiredList.add(result.groups["varName"]!!.value)
            nextIsAutowired = false
            continue
        }
    }
    if (autowiredList.isEmpty()) return
    // 查询没有使用的autowired
    val filePath = file.absolutePath
        .replace("\\", "/")
        .substringAfter("src/main/java/")
        .replace("/", ".")
        .removeSuffix(".java")
    autowiredList.filter { varName ->
        lines.all { line -> varName !in line || line.trim().matches(autowiredPattern) }
    }.let {
        unused += it.map { v -> filePath to v }
    }
}

collect(dir)
File("unused-autowired-${dir.name}.txt").writeText(unused.joinToString("\n") { (k, v) -> "$k: $v" })

此时启动速度依然很慢,于是使用 lazy-initialization 的方案,环境变量中添加 spring.main.lazy-initialization=true

但是所有bean都懒加载会导致一部分模块出错最终导致启动失败,于是添加filter

@Bean
public LazyInitializationExcludeFilter filter() {
    return (beanName, beanDefinition, beanType) -> {
        String className = beanType.getName();
        return className.startsWith("com.example.module.plugin.important")
                || className.startsWith("com.example.module.common")
                ;
    };
}

最终启动速度从12分钟优化到最快4分钟(不启用可选的模块)

总结

  • 模块拆分解耦是好文明,在这种场景下直接禁用不用的模块也不影响项目启动
  • 在bean初始化里写逻辑是坏文明,遇到一个上游依赖的组件,自己封装了一层XxlJobSpringExecutor,还把class设置成package-private,导致不加载该模块就无法正常初始化xxljob
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇