场景
假设使用 Spring Boot 开发了一个可使用不同数据库的应用,每个数据库的 jdbc 驱动包都不同,不想在 Fat Jar 中打入所有的数据库驱动 jar,又不想为每一个确定了具体数据库的场景都打一个对应的 Fat Jar 包,有没有优雅的方式来实现这个需求呢?
我们先来看下 Spring Boot 的 Fat Jar(Executable Jar)是如何运行的。
Launcher
通常情况下,要启动一个 Spring Boot 应用,可通过如下方式:
$ java -jar example.jar
Spring Boot Jar 包的文件结构如下:
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
在 Launching Executable Jars 中,介绍了 Spring Boot 的 Jar 包可以直接运行的原因:org.springframework.boot.loader.Launcher
类是 Spring Boot Jar 包实际的主类,负责调用应用的 main 方法。Launcher
类有三个子类:JarLauncher
,WarLauncher
和 PropertiesLauncher
,由它们负责从 jar 包或者 war 包中读取内嵌的资源(.class 文件等)。
JarLauncher
从 BOOT-INF/lib/
固定路径加载资源;WarLauncher
是从 WEB-INF/lib/
和 WEB-INF/lib-provided/
路径。
PropertiesLauncher
默认从 BOOT-INF/lib/
加载资源,并且支持通过环境变量 LOADER_PATH
或 loader.path
来指定额外的路径。
具体的 Launcher 和应用的 main 函数所在类,设定在 MANIFEST.MF
文件中,一般由 Maven 或 Gradle 打包插件帮我们设定好,例如:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication
所以通过调整默认 Launcher 及使用环境变量指定额外 classpath 的方式,即可实现添加 Fat Jar 外 jar 包至运行环境的需求。
PropertiesLauncher
那么如果不想修改打包配置,或者手动修改 MANIFEST.MF
文件内容,该如何操作呢?
在 PropertiesLauncher
的 文档 中,有一个 loader.path
属性,相关描述如下:
Comma-separated Classpath, such as lib,${HOME}/app/lib. Earlier entries take precedence, like a regular -classpath on the javac command line.
比如本文最初提到的场景,可以将数据库的 jdbc 驱动 jar 包放在同级 lib 路径下,然后利用此属性,通过如下方式启动 Fat Jar:
$ java -cp example.jar -Dloader.path=./lib org.springframework.boot.loader.PropertiesLauncher
存在多个路径或文件时,可通过
,
间隔,一起指定,如:-Dloader.path=../sentinel-1.8.2,./config
注意,此时不再是使用 java -jar
方式启动,而是使用传统的 Java 应用启动方式,先通过 -cp
参数将 Fat Jar 加入 classpath,然后指定运行的主类 PropertiesLauncher
,并且通过 -D
参数,将系统属性传入主类中。
通过这种方式,结合 Override same class 中相关内容,还可以实现无需重新打包,覆盖 Fat Jar 中类的某些行为。
参考资料
- How to configure additional classpath in SpringBoot?
- Hack – How 2 add jars 2 springboot classpath with JarLauncher ?