Spring Boot Gradle 瘦身打包 thinJar,分离 lib jar 依赖

需求背景:

srping boot 2.0+ 打包生产的 fat jar 虽然部署起来很方便,但将所有依赖都打包到一个 jar 包中使得体积也不小(40M+),第一次部署还没问题,之后的更新就很痛苦了,每次发布更新时一般都是自己项目的业务代码发生变化,依赖一般不会变化除非升级或增加了项目依赖。

我们期望的场景是每次更新只上传我们自己的业务模块 jar 包,大小可能只有几十K,这就是今天要讲的 thin jar 如何打包。

微酷今天研究了一天,终于找到一个很理想的方案,当然没少被折腾,每次都想放弃 Java 回头拥抱 C#,但还是坚持了下来。

微酷研究了三个方案,推荐方案三。


方案一:

看起来非常的官方的方式,使用插件 spring-boot-thin-launcher ,使用起来也是相当的简单。

插件地址:

简单来说微酷对这个插件实现的理解:

  • 插件可生成 pom.xml 或 thin.properties 文件保存项目所依赖的包。
  • 打包时只将自己项目打包,依赖项目不打包。
  • 启动时使用单独的装载器从 pom.xml 或 thin.properties 中读取依赖,然后从 maven 仓库下载依赖到本地。
  • 运行自己项目的 Main 函数。

安装:

配置 build.gradle 如下:

buildscript {
    ext {
        springBootVersion = '2.1.4.RELEASE'
        wrapperVersion = '1.0.21.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot.experimental:spring-boot-thin-gradle-plugin:${wrapperVersion}")
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id "io.spring.dependency-management" version "1.0.7.RELEASE"
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.springframework.boot.experimental.thin-launcher'

jar.dependsOn = [thinProperties]

// 其它配置略...

配置上插件后就会在 Gradle 的 Tasks 中多出好多任务,都是以 thin*** 开头的。

20190427163409481.png

打包:

运行 thinJar 任务即可。生成的 jar 包在 ./build/thin/root 下。

运行:

java -jar weiku-web-1.0.0.jar

注意:第一次运行会有点慢,他会自己下载所需要的依赖。

放弃:

下载依赖是微酷相当不喜欢的(虽然下载后可以缓存之后就不用再下载了),因为我本地有所依赖的所有 jar 包,为什么不让用的,并且一上午大把的时候都在研究这个问题最终也没找到答案,于是放弃这个方案了。

另外由于我的项目是多 module 于是引用的自己的依赖没有上传到 maven 仓库根本就找不到,所以我们项目最终也没有用这个方案运行成功。(可能有方法配置但我放弃继续研究了,英文不好的我的官方的 Readme 读了一遍已累爬。)

我的提问:https://github.com/spring-projects-experimental/spring-boot-thin-launcher


方案二:

又找到一个方法,看起来是超级的简单。

标题:Spring Boot 用gradle bootJar打包瘦身(外置依赖jar)
原文:https://my.oschina.net/formatkm/blog/1822900

方法二旧方法(原作者已经废弃):

网上 SpringBoot 打包瘦身的教程很多,但基本都是 Maven 的。一般公共库都外置,不需要打包进可执行的 jar 中,这样每次发布都不用传几十兆的文件。用 gradle 的 bootJar 实际很简单,方法就是把 compile 依赖替换为 compileClasspathcompileClasspath
的依赖不会被打包进可运行的jar中。

//打包进jar
//compile("org.springframework.boot:spring-boot-starter-freemarker")

//只打包自己的代码
compileClasspath("org.springframework.boot:spring-boot-starter-web")

另外可以用 complie 先配置好,导出依赖的jar到一个目录

task copyToLib(type: Copy) {
    into "$buildDir/libs/lib"
    from configurations.runtime
}

导出后又改为compileClasspath

方法二的新方法:

修改 bootJar 如下,仅此而已:

bootJar {
    classifier = 'boot'
    excludes = ["*.jar"]
}

运行的时候用 -Djava.ext.dirs=指定依赖库的路径

java -Djava.ext.dirs=./lib -jar bootrun.jar

微酷实践:

微酷的项目用方案二的新方法能启动起来,大多数页面正常,但有的页面出错,如访问 MSSQL 数据库时出错:

服务器异常:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“java.lang.RuntimeException: Could not generate DH keypair”。 ClientConnectionId:eb5aa5f7-8ed4-4bb0-99b8-a70a936640c5

针对这个问题搜索找到一个文章说:

-Djava.ext.dirs 会覆盖 Java 本身的 ext 设置,java.ext.dirs 指定的目录由 ExtClassLoader 加载器加载,如果您的程序没有指定该系统属性,那么该加载器默认加载 $JAVA_HOME/jre/lib/ext 目录下的所有 jar 文件。但如果你手动指定系统属性且忘了把$JAVA_HOME/jre/lib/ext 路径给加上,那么 ExtClassLoader 不会去加载 $JAVA_HOME/lib/ext 下面的jar文件,这意味着你将失去一些功能,例如java自带的加解密算法实现。原文:https://blog.csdn.net/cyony/article/details/74375251

于是微酷将电脑C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext下的 13 个 jar 文件拷贝到项目发布后的 libs 目录中,竟然神奇的运行正常了。

其实应该继续研究如何合并两个位置的 jar 包来解决这个问题,那么就完美了。

但微酷又找到了完美的方案三,于是这个问题就不再继续研究了。


方案三(推荐):

又找到这种方法,看起来不错,运行时也不需要加 java.ext.dirloader.path 参数。

原标题:spring boot + gradle打包bootJar分离lib
原文:https://blog.csdn.net/georgeye/article/details/85318802

以前项目打包一直是用的 maven,最近新开一个项目,使用的是spring boot 2.11 + gradle 4.10.3,在打包的时候分离lib折腾了好几天,网上找了很多方法都不成功,老是卡在 configurations.compile 这里,总是获取不到正确的jar包路径。最后上google终于找到解决办法,总结整理后简单又好用,特此记录如下:

// 清除现有的lib目录
task clearJar(type: Delete) {
    delete "$buildDir\\libs\\lib"
}
 
// 将依赖包复制到lib目录
task copyJar(type: Copy, dependsOn: 'clearJar') {
    from configurations.compileClasspath
    into "$buildDir\\libs\\lib"
}
 
bootJar {
    // 例外所有的jar
    excludes = ["*.jar"]
    // lib目录的清除和复制任务 
    dependsOn clearJar
    dependsOn copyJar
 
    // 指定依赖包的路径
    manifest {
        attributes "Manifest-Version": 1.0,
            'Class-Path': configurations.compileClasspath.files.collect { "lib/$it.name" }.join(' ')
    }
}

运行的时候也不需要指定 java.ext.dirloader.path 了,直接-jar运行就可以了。

java -jar test-0.0.1-SNAPSHOT.jar

微酷实践:

但微酷加到项目中,运行出错:

A problem occurred evaluating project ':weiku-web'.
Cannot change dependencies of configuration ':weiku-web:compile' after it has been included in dependency resolution.

解决:

找到这个文章:

2019042717055.png

于是我尝试把我的 dependencies 放到了 bootJar{} 之前,竟然真的好了。

最终微酷整理的 build.gradle 文件:

buildscript {
    ext {
        springBootVersion = '2.1.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id "io.spring.dependency-management" version "1.0.7.RELEASE"
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group 'co.weiku'
version '1.0.0'

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '7.2.2.jre8'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.1.4.RELEASE'
    compile group: 'org.apache.commons', name: 'commons-pool2', version: '2.6.2'
    compile group: 'com.alibaba', name: 'druid', version: '1.1.16'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc', version: '2.1.4.RELEASE'
    compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.0.1'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.1.4.RELEASE'
    compile group: 'org.apache.shiro', name: 'shiro-spring', version: '1.4.0'
    compile group: 'org.springframework.boot', name: 'spring-boot-devtools', version: '2.1.4.RELEASE'
    compile project(':weiku-framework')
    compile project(':weiku-common')
}

// 清除lib
task myClearLib(type: Delete) {
    delete "$buildDir/libs/lib"
}

// 拷贝lib
task myCopyLib(type: Copy) {
    into "$buildDir/libs/lib"
    from configurations.runtime
}

bootJar {
    //mainClassName = 'co.weiku.WeikuApplication'
    baseName = 'weiku-web'
    version = '1.0.0'

    classifier = 'boot'
    excludes = ["*.jar"]

    // lib目录的清除和复制任务
    dependsOn myClearLib
    dependsOn myCopyLib

    // 指定依赖包的路径,运行时不再需要指定 java.ext.dir 或 loader.path 参数。
    manifest {
        attributes "Manifest-Version": 1.0,
                'Class-Path': configurations.runtime.files.collect { "lib/$it.name" }.join(' ')
    }
}

这样运行 bootJar 任务后就会打包 thinJar 完成,并将依赖的 jar 包放在 lib 子目录下,运行测试成功。

20190427160206586.png

完美收工。

最后修改:2019 年 09 月 06 日 09 : 01 AM
如果觉得我的文章对你有用,请随意赞赏

1 条评论

  1. 薇拉

    终于搞定了,不错!

发表评论