在项目有读取特殊配置文件的地方(不是 Spring 的 application 配置),项目打包为 jar 后,无法从外部替换默认的配置文件。

我自己尝试了 java -cp 的方式,发现没法启动(Spring Boot 打的包很特殊)。

通过谷歌搜索查到: Spring Boot Executable Jar with Classpath

其中 Peter Tarlos 的答案是完整的,本文的内容也是以这里为起点,通过查找官方文档来说明如何实现。

1. 关键的 PropertiesLauncher

Executable Jars Spring Boot’s executable jars, their launchers, and their format.

在 Spring Boot 中,存在 3 种类型的启动器:

  • JarLauncher
  • WarLauncher
  • PropertiesLauncher

当打包为 jar 或 war 时选择的前两个, JarLauncher BOOT-INF/lib/ 目录加载 jars, WarLauncher WEB-INF/lib/ WEB-INF/lib-provided/ 加载 jars,如果想添加额外的 jars 就需要往这些目录添加。

第三个 PropertiesLauncher 默认 BOOT-INF/lib/ 目录加载 jars,你还可以通过 LOADER_PATH 或者 loader.properties 中的 loader.path 配置额外的位置(多个位置逗号隔开),所以这个是我们需要的启动类。

启动类最终是在打包文件中的 MANIFEST.MF 中配置的,例如 jar 方式:

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication

想要使用 PropertiesLauncher,可以通过官方的配置来启用。

2. 如何配置使用 PropertiesLauncher

Build Tool Plugins Maven Plugin, Gradle Plugin, Antlib, and more.

在官方打包工具中,有 Maven 和 Gradle 的两种方式。

Maven 配置

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>${start.class}</mainClass>
                <layout>ZIP</layout>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

这里的 <layout>ZIP</layout> 配置可以选择使用哪个启动器,默认根据 <packing> 打包类型( jar 或 war)确定,可以配置下面可选值:

  • JAR
  • WAR
  • ZIP:使用 PropertiesLauncher
  • NONE: 不捆绑引导加载程序

通过选择 ZIP 即可使用 PropertiesLauncher

Gradle 配置

配置起来更直接,配置如下:

tasks.named("bootWar") {
	manifest {
		attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher'

3. 指定其他类加载路径

详细的配置可以参考官方文档: PropertiesLauncher Features,这里就简单举例用用:

java -Dloader.path=file:/config -jar spring-boot-app.jar

通过 -Dloader.path=file:/config 指定路径后,就能通过这种方式覆盖 jar 包中的文件了。

使用 -Dloader.dubug=true 会通过 System.out.println 输出日志信息。

4. 类路径的加载顺序(优先级)

为了确保替换配置文件的方式有效,最后还要确认一下类路径的加载顺序,只有当我提供的配置先加载时,才能确保替换默认的配置文件,官方没有明确说明加载顺序,因此只能通过代码来确认。

PropertiesLauncher 中存在 main 方法:

public static void main(String[] args) throws Exception {
	PropertiesLauncher launcher = new PropertiesLauncher();
	args = launcher.getArgs(args);
	launcher.launch(args);

这里调用了父类 Launcherlaunch 方法:

protected void launch(String[] args) throws Exception {
	if (!isExploded()) {
		JarFile.registerUrlProtocolHandler();
	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
	String jarMode = System.getProperty("jarmode");
	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
	launch(args, launchClass, classLoader);

在创建 ClassLoader 时,调用了 getClassPathArchivesIterator() 方法,这个方法会获取所有类路径下面的资源文件和jar包,这个方法就是我们要重点关注的方法:

@Override
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
	ClassPathArchives classPathArchives = this.classPathArchives;
	if (classPathArchives == null) {
		classPathArchives = new ClassPathArchives();
		this.classPathArchives = classPathArchives;
	return classPathArchives.iterator();

这里创建了 ClassPathArchives 的单例,在构造方法中:

ClassPathArchives() throws Exception {
	this.classPathArchives = new ArrayList<>();
	for (String path : PropertiesLauncher.this.paths) {
		for (Archive archive : getClassPathArchives(path)) {
			debug("paths: " + archive.getUrl());
			addClassPathArchive(archive);
	addNestedEntries();

这里的 PropertiesLauncher.this.paths 就是通过 loader.path 配置的所有路径,这部分内容首先添加进去了,从这儿已经可以看出 loader.path 的优先级更高,因此通过这种方式设置的外部配置文件会优先使用。

上面代码后面的 addNestedEntries 在 jar 包启动时,就是加载 jar 包中的内容:

private void addNestedEntries() {
	// The parent archive might have "BOOT-INF/lib/" and "BOOT-INF/classes/"
	// directories, meaning we are running from an executable JAR. We add nested
	// entries from there with low priority (i.e. at end).
	try {
		Iterator<Archive> archives = PropertiesLauncher.this.parent.getNestedArchives(null,
				JarLauncher.NESTED_ARCHIVE_ENTRY_FILTER);
		while (archives.hasNext()) {
			this.classPathArchives.add(archives.next());
	catch (IOException ex) {
		// Ignore

在这里的 PropertiesLauncher.this.parent 对应的就是启动的 jar,这里调用获取嵌套的包,使用 JarLauncher.NESTED_ARCHIVE_ENTRY_FILTER 作为过滤条件,过滤条件定义:

static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
	if (entry.isDirectory()) {
		return entry.getName().equals("BOOT-INF/classes/");
	return entry.getName().startsWith("BOOT-INF/lib/");

可以看到当遍历当前 jar 包时,只会匹配 BOOT-INF/classes/ 目录和 BOOT-INF/lib/ 下面的所有文件。

5. 不在深入一点吗?

到这里本文关注的内容就结束了,但是如果看源码只看到这种程度就够了吗?

看源码最好是有目的的看,看到感兴趣的地方时再深入看,看上面代码时你最有兴趣的地方在哪里?

我最感兴趣的是 ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); 这里,以及后续对该 classLoader 的使用,这部分的内容应该是 Spring Boot 能打成一个特殊 fat jar 启动的核心,Spring Boot 包和 Apache Maven Shade Plugin 插件的区别在于 “Spring Boot 中依赖的 jar 包仍然是独立的 jar,存在于 BOOT-INF/lib 中,Shade 插件打的是一个真正的大 jar 包,把所有依赖的 jar 都抽取到了大的 jar 中,这会存在同路径和名称文件的覆盖问题”。如果后续有时间再单独从这个角度再分析看看。

在项目有读取配置文件的地方,项目打包为 jar 后,无法从外部替换默认的配置文件,通过 Spring Boot 一系列的配置可以解决该问题。直接谷歌搜索查到:Spring Boot Executable Jar with Classpath其中一个 Peter Tarlos 的答案是完整的,本文的内容也是以这里为起点,通过查找官方文档来说明如何实现。关键的 PropertiesLauncherExecutable Jars Spring Boot’s executable jars, their
spring boot 动态加载模块(加载外部jar) ImportBeanDefinitionRegistrar) Spring Boot 如何热加载jar实现动态插件?
Spring Boot加载外部Jar 项目有一个需求,需要通过spring加载配置下的Jar,将Jar中的类注册成Spring的一个Bean以供后续使用。 1、构建自己的ClassLoader。 关于java自己的ClassLoader的加载方式,详情可移步此篇博客。 以下是我定义的JarClassLoader,将Jar转换为URL,通过URLClassLoader去加载。 import org.slf4j.Logger; import org.slf4j.LoggerFactory;
springboot启动时加载外部jar java -cp springboot-demo.jar:lib/* org.springframework.boot.loader.JarLauncher 需要指定多个jar或目录时,linux下使用冒号分隔,windows下使用分号分隔 web程序加载配置文件分为jar中的配置文件和jar外面的配置文件 修改jar中的配置文件需要使用压缩方式打开并复制修改后的配置文件进入jar中(需要外部配置和内部配置替换) springboot后只支持带jar外部的config中的application.properties的加载(通过外部文件控制启动行为),对于其他配置文件不能加载 由于一些外部原因,不能通过mavn来下载外部依赖,此时我们需要将自己的依赖手动打到我们的项目中去,比如一些自定义的加解密等sdk,通过maven的方式将jar加入项目。 首先,我们需要单独处理下我们的私有,将所有依赖准备好,在maven中加入相关打插件 <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> 属性文件是一种常见的方法,可用于存储特定于项目的信息。 理想情况下,应该将其保留在jar外部,以便能够根据需要更改配置。 接下来通过各种方式从Spring Boot应用程序中jar外部的位置加载属性文件。 2.使用默认位置 按照惯例,Spring Boot按照以下优先级顺序在4个预定的位置中寻找一个外部化的配置文件– application.properties或application.yml: 当前目录的/config子目录 类路径/config 加载冲突时,优先级高
1、在项目 根目录下创建 libs 2、将jar放入 libs 目录里。 3、在maven pom.xml 中配置 jar文件。1、在项目 根目录下创建 libs 2、将jar放入 libs 目录里。 3、在maven pom.xml 中配置 jar文件。 <dependencies>。。。其他 配置 。。。 <dependency>
1.1 springboot启动会扫描一下位置的application.properties或者application.yml作为默认的配置文件 工程根目录:./config/ 工程根目录:./ classpath:/config/ classpath:/ 加载的优先级顺序是从上向下加载,并且所有的文件都会被加载,高优先级的内容会覆盖底优先级的内容,形成互补配置 也可以通过指定配置spring.config.location来改变默认配置,一般在项目已经打后,我们可以通过指令   java -jar x
当我们把代码打jar时,properties文件是写死在jar中的,不可修改。一旦我们要修改properties文件,就要再重新打jar,再发布。非常麻烦!可不可以在外部配置properties,启动jar加载这个配置文件呢?有! 解决方案: 1.以Windows为例,准备一个你自己的jar 2.在同目录下,创建一个myApplication.properties 内容如下: # 这里书写规则跟正常的application.properties一样 server.po
在项目中,有时候需要引入外部jar,启动运行。有两种方式,一种是直接在项目中添加jar,另一种是在本地maven仓库中添加jar,然后在pom.xml文件中引入依赖。 第一种方式:在项目中添加jar step1:在src/main/resources下创建lib目录,然后将jar放在该目录下 step2:然后在project structure中引入该lib 第二种方式:在pom.xml文件中引入外部jar step1:通过命令行方式,在本地maven仓库中添加jar 打开cmd,
要扫描Spring Boot JAR中的JAR的类,可以使用Spring Boot的自动类扫描功能。在Spring Boot应用程序中,可以使用@SpringBootApplication注释启用自动扫描,它将扫描主应用程序所在中的所有类和其子,因此可以启用反射技术来查找和加载其他JAR中的类。如果要扫描特定的类,可以使用@ComponentScan注释指定要扫描的名。 除了自动扫描之外,还可以使用ClassLoader类的方法来扫描JAR中的类,例如getResources()方法可以获取JAR中所有的资源,然后使用反射技术来查找并加载类。 需要注意的是,在扫描JAR中的类时,可能会遇到JAR冲突的情况,即不同版本的JAR中存在相同的类,这会导致类加载错误和应用程序异常。为了解决这个问题,可以使用Maven或Gradle等构建工具来管理依赖关系,确保使用的JAR版本一致。同时,可以使用Spring Boot的exclude属性排除某些JAR,或使用@ConditionalOnMissingBean注释来避免应用程序中同名的多个bean。 总之,扫描Spring Boot JAR中的JAR的类需要使用反射技术和自动类扫描功能,同时需要注意依赖关系和JAR冲突等问题。