Spring Boot:是不是可以在带有胖 jar 的任意目录中使用外部 application.properties 文件?

Posted

技术标签:

【中文标题】Spring Boot:是不是可以在带有胖 jar 的任意目录中使用外部 application.properties 文件?【英文标题】:Spring Boot: Is it possible to use external application.properties files in arbitrary directories with a fat jar?Spring Boot:是否可以在带有胖 jar 的任意目录中使用外部 application.properties 文件? 【发布时间】:2014-11-26 06:13:26 【问题描述】:

是否可以有多个 application.properties 文件? (编辑:请注意,此问题已演变为标题中的问题。)

我尝试了 2 个文件。

第一个位于应用程序 Jar 的根文件夹中。 第二个在类路径中指定的目录中。

2 个文件都被命名为“application.properties”。

是否可以“合并”两个文件的内容? (并且第二个的属性值覆盖第一个)或者,如果我有一个文件,那么另一个文件会被忽略?

更新 1:可以“合并”内容。昨天好像第一个被忽略了,但似乎是因为当时有什么东西坏了。现在效果很好。

更新 2:又回来了!同样,仅应用了两个文件中的一个。这很奇怪......它是在我使用 Spring Tool Suite 构建应用程序 jar 文件之后开始的。而且似乎 Jar 版本总是忽略第二个(在类路径中),而在 STS 上运行的扩展版本的行为各不相同。我可以从哪里开始调查?

更新 3

Jar 版本的行为实际上是正确的。这是java.exe的规范。 当指定 -jar 选项时,java.exe 将忽略 -classpath 选项和 CLASSPATH 环境变量,并且类路径将仅包含 jar 文件。因此,类路径中的第二个 application.properties 文件将被忽略。

现在,如何让类路径上的第二个 application.properties 被加载?

更新 4

我在使用 -jar 选项时设法在外部路径中加载了一个 application.properties 文件。

关键是 PropertiesLauncher。

要使用 PropertiesLauncher,必须像这样更改 pom.xml 文件:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>  <!-- added -->
                <layout>ZIP</layout> <!-- to use PropertiesLaunchar -->
            </configuration>
        </plugin>
    </plugins>
</build>

为此,我引用了以下 *** 问题:spring boot properties launcher unable to use。顺便说一句,在 Spring Boot Maven 插件文档(http://docs.spring.io/spring-boot/docs/1.1.7.RELEASE/maven-plugin/repackage-mojo.html)中,没有提到指定使用 PropertiesLauncher 的 ZIP 触发器。 (也许在另一个文件中?)

在构建 jar 文件后,我可以通过检查 jar 中 META-INF/MENIFEST.MF 中的 Main-Class 属性看到使用了 PropertiesLauncher。

现在,我可以按如下方式运行 jar(在 Windows 中):

java -Dloader.path=file:///C:/My/External/Dir,MyApp-0.0.1-SNAPSHOT.jar -jar MyApp-0.0.1-SNAPSHOT.jar

请注意,应用程序 jar 文件包含在 loader.path 中。

现在 C:\My\External\Dir\config 中的 application.properties 文件已加载。

作为奖励,该目录中的任何文件(例如,静态 html 文件)也可以被 jar 访问,因为它位于加载程序路径中。

至于UPDATE 2中提到的非jar(扩展)版本,可能是classpath顺序问题。

(顺便说一句,我将问题的标题更改为更具体地针对此问题。)

【问题讨论】:

您能解释一下您是如何构建和运行 jar 的吗?谢谢 @geoand jar 是使用 Spring Tool Suite 构建的。它实际上执行'mvn package'。它构建了一个胖罐子。在 Jar 的 MANIFEST.MF 文件中,Main-Class 为 org.springframework.boot.loader.JarLauncher。 MANIFEST.MF 由 spring boot maven 插件创建。 @geoand 我曾经使用'java -cp D:\My\External\Dir -jar MyApp-0.0.1-SNAPSHOT.jar' 运行 Jar,但现在我知道 -cp 是被java忽略。 @geo 我设法加载了一个外部 application.properties 文件。请参阅我的更新 4。 非常感谢。鉴于其他一切都很简单,我是唯一一个觉得这比应该做的更难的人吗? 【参考方案1】:

如果您没有更改 Spring Boot 的默认值(意味着您正在使用 @EnableAutoConfiguration@SpringBootApplication 并且没有更改任何 Property Source 处理),那么它将按照以下顺序查找属性(最高覆盖最低) :

    当前目录的/config 子目录 当前目录 一个类路径/config包 类路径根

以上列表在文档的this 部分提到

这意味着,如果在src/resources 下找到例如application.properties 的属性,将被/config 目录“下一个”的application.properties 中的同名属性覆盖打包好的jar。

Spring Boot 使用的这种默认顺序允许非常简单的配置外部化,从而使应用程序易于在多个环境(开发、登台、生产、云等)中进行配置

要查看 Spring Boot 提供的用于属性读取的整套功能(提示:比从application.properties 读取更多可用功能)请查看文档的this 部分。

从我上面的简短描述或完整文档中可以看出,Spring Boot 应用程序对 DevOps 非常友好!

【讨论】:

Spring Boot 肯定会使用上面提到的优先级合并每个属性文件的内容。很高兴它对你有用! 又回来了!同样,仅应用了两个文件中的一个。有关该问题,请参阅我的 UPDATE 2。而且由于似乎根本问题没有解决,所以我暂时取消了接受答案。对不起。 @zeodtr 不要担心不接受!可能是构建的问题。您是否尝试过进行 Maven 清理并从 Maven 构建引导 jar? 是的,其实STS项目是基于maven的。我做了 maven clean and package(由 STS 运行)。也许我应该直接在命令行上尝试。顺便说一句,我机器的操作系统是 Windows 7。 我不使用 STS,所以我真的没有更多的信息。但是我无法重现您的问题【参考方案2】:

这一切都在文档中进行了解释:

http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

这说明这是优先顺序:

当前目录的 /config 子目录。 当前目录 类路径 /config 包 类路径根

它还指出,您可以为覆盖定义额外的属性文件,如下所示:

java -jar myproject.jar 
    --spring.config.location=classpath:/overrides.properties

如果您使用spring.config.location,则还包括application.properties 的所有默认位置。这意味着您可以在 application.properties 中设置默认值并根据特定环境的需要进行覆盖。

【讨论】:

谢谢。但我读过文档。目前尚不清楚覆盖是基于值(以便您可以合并多个文件中的值)还是基于文件(在这种情况下您只能选择一个文件 - 其他文件被忽略)。我的经验表明是后一种情况。也许我错过了什么。 现在效果很好。可以“合并”内容。昨天好像第一个文件被忽略了,但似乎是因为当时有什么东西坏了。谢谢。【参考方案3】:

我在使用 -jar 选项时设法在外部路径中加载了一个 application.properties 文件。

关键是 PropertiesLauncher。

要使用 PropertiesLauncher,必须像这样更改 pom.xml 文件:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>  <!-- added -->
                <layout>ZIP</layout> <!-- to use PropertiesLaunchar -->
            </configuration>
        </plugin>
    </plugins>
</build>

为此,我引用了以下 *** 问题:spring boot properties launcher unable to use。顺便说一句,在 Spring Boot Maven 插件文档(http://docs.spring.io/spring-boot/docs/1.1.7.RELEASE/maven-plugin/repackage-mojo.html)中,没有提到指定使用 PropertiesLauncher 的 ZIP 触发器。 (也许在另一个文件中?)

在构建 jar 文件后,我可以通过检查 jar 中 META-INF/MENIFEST.MF 中的 Main-Class 属性看到使用了 PropertiesLauncher。

现在,我可以按如下方式运行 jar(在 Windows 中):

java -Dloader.path=file:///C:/My/External/Dir,MyApp-0.0.1-SNAPSHOT.jar -jar MyApp-0.0.1-SNAPSHOT.jar

请注意,应用程序 jar 文件包含在 loader.path 中。

现在 C:\My\External\Dir\config 中的 application.properties 文件已加载。

作为奖励,该目录中的任何文件(例如,静态 html 文件)也可以被 jar 访问,因为它位于加载程序路径中。

至于UPDATE 2中提到的非jar(扩展)版本,可能是classpath顺序问题。

【讨论】:

由于某种原因,这对我不起作用。我正在使用弹簧靴 2.1.1。我尝试设置布局,但这对我没有任何改变。只是背景:我的一个依赖项中有 ClassLoader.getSystemResource(path),因此我想使用 -Dloader.path 使配置可用,但即使在执行上述步骤后它也没有选择。 @zeodtr 你能指出这里可能出了什么问题吗? @tom 抱歉,我不知道。在 Spring Boot 1.x 中它起作用了。它在 2.x 升级后工作。但是现在我不维护相关的源代码,几乎忘记了 Spring Boot 的细节。再次抱歉。 别担心!我会尝试发布一个新问题。谢谢!【参考方案4】:

您将能够使用外部属性文件路径启动您的 Spring Boot 应用程序,如下所示:

java -jar jar-file-name.jar 
--spring.config.location=file:///C:/file-path/file-name.properties

【讨论】:

谢谢,如果不需要我提到的 UPDATE 4 的“奖金”,这个解决方案更简单。 我已尝试使用您的 UPDATE 4(如下所述),但没有成功。因此,使用上述命令,我能够从外部文件路径中获取属性。 java -Dloader.path=file:///C:/My/External/Dir,MyApp-0.0.1-SNAPSHOT.jar -jar MyApp-0.0.1-SNAPSHOT.jar 这个解决方案对我有用,因此我发布了它并不要赚取任何形式的“奖金”【参考方案5】:
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layout>ZIP</layout> 
            </configuration>
        </plugin>
    </plugins>
</build>

java -Dloader.path=file:///absolute_path/external.jar -jar example.jar

【讨论】:

【参考方案6】:

我知道这是一个尖锐的问题,并且 op 想要加载不同的属性文件。

我的回答是,做这样的自定义黑客是一个糟糕的主意。

如果您使用 spring-boot 与云服务提供商(如 cloud Foundry)一起使用,请帮自己一个忙并使用云配置服务

https://spring.io/projects/spring-cloud-config

它加载和合并 default/dev/project-default/project-dev 特定属性,如魔术

再次, Spring Boot 已经为您提供了足够的方法来做到这一点 https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

请不要重新发明***。

【讨论】:

【参考方案7】:
java -jar server-0.0.1-SNAPSHOT.jar --spring.config.location=application-prod.properties

【讨论】:

【参考方案8】:

这可能会迟到,但我想我找到了一种更好的方法来加载外部配置,尤其是当您使用 java jar myapp.war 而不是 @PropertySource("classpath:some.properties" 运行 spring-boot 应用程序时)

配置将从项目的根目录或从运行 war/jar 文件的位置加载

public class Application implements EnvironmentAware 

    public static void main(String[] args) throws Exception 
        SpringApplication.run(Application.class, args);
    

    @Override
    public void setEnvironment(Environment environment) 
        //Set up Relative path of Configuration directory/folder, should be at the root of the project or the same folder where the jar/war is placed or being run from
        String configFolder = "config";
        //All static property file names here
        List<String> propertyFiles = Arrays.asList("application.properties","server.properties");
        //This is also useful for appending the profile names
        Arrays.asList(environment.getActiveProfiles()).stream().forEach(environmentName -> propertyFiles.add(String.format("application-%s.properties", environmentName))); 
        for (String configFileName : propertyFiles) 
            File configFile = new File(configFolder, configFileName);
            LOGGER.info("\n\n\n\n");
            LOGGER.info(String.format("looking for configuration %s from %s", configFileName, configFolder));
            FileSystemResource springResource = new FileSystemResource(configFile);
            LOGGER.log(Level.INFO, "Config file : 0", (configFile.exists() ? "FOund" : "Not Found"));
            if (configFile.exists()) 
                try 
                    LOGGER.info(String.format("Loading configuration file %s", configFileName));
                    PropertiesFactoryBean pfb = new PropertiesFactoryBean();
                    pfb.setFileEncoding("UTF-8");
                    pfb.setLocation(springResource);
                    pfb.afterPropertiesSet();
                    Properties properties = pfb.getObject();
                    PropertiesPropertySource externalConfig = new PropertiesPropertySource("externalConfig", properties);
                    ((ConfigurableEnvironment) environment).getPropertySources().addFirst(externalConfig);
                 catch (IOException ex) 
                    LOGGER.log(Level.SEVERE, null, ex);
                
             else 
                LOGGER.info(String.format("Cannot find Configuration file %s... \n\n\n\n", configFileName));

            

        
    


希望对你有帮助。

【讨论】:

【参考方案9】:

另一种灵活的方式使用包含 fat jar (-cp fat.jar) 或所有 jars (-cp "$JARS_DIR/*") 的类路径和另一个自定义配置类路径或包含配置文件的文件夹,通常在其他地方和 jar 之外。所以不要使用有限的 java -jar,而是使用更灵活的类路径方式,如下所示:

java \
   -cp fat_app.jar \ 
   -Dloader.path=<path_to_your_additional_jars or config folder> \
   org.springframework.boot.loader.PropertiesLauncher

见Spring-boot executable jar doc和this link

如果您确实有多个常见的 MainApp,您可以使用 How do I tell Spring Boot which main class to use for the executable jar?

您可以通过在 loader.properties 中设置环境变量 LOADER_PATH 或 loader.path(以逗号分隔的目录、存档或存档中的目录的列表)来添加其他位置。基本上 loader.path 适用于 java -jar 或 java -cp 方式。

和往常一样,您可以覆盖并准确指定它应该为调试目的而拾取的 application.yml

--spring.config.location=/some-location/application.yml --debug

【讨论】:

我正在使用 Tomcat9 并部署我的 webapp WAR 文件。我将 application.yml 放入 tomcat 顶部文件夹,但它根本没有被拾取。我应该如何指定位置?谢谢。【参考方案10】:

yml文件的解决方案:

1.将yml复制到jar应用的同一目录

2.运行命令,例如xxx.yml:

java -jar app.jar --spring.config.location=xxx.yml

它工作正常,但在启动记录器中是 INFO:

No active profile set .........

【讨论】:

【参考方案11】:

当您使用 maven install 创建 spring boot jar 并且您希望在 jar 之外创建所有资源(如属性文件和 lib 文件夹)...然后在我定义输出文件夹的 pom.xml 中添加以下代码我想在哪里提取和存储所需的 jar 资源。

<build>
<finalName>project_name_Build/project_name</finalName>
   <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>$project.build.directory/project_name_Build/lib</outputDirectory>
                        <overWriteReleases>false</overWriteReleases>
                        <overWriteSnapshots>false</overWriteSnapshots>
                        <overWriteIfNewer>true</overWriteIfNewer>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                        <mainClass>write here the qualified or complete path of main class of application</mainClass>
                    </manifest>
                    <manifestEntries>
                        <Class-Path>. resources/</Class-Path>
                    </manifestEntries>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </plugin>
    </plugins>

    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>application.properties</include>
                <include>log4j.properties</include>
            </includes>
            <targetPath>$project.build.directory/ConsentGatewayOfflineBuild/resources</targetPath>
        </resource>

        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>application.properties</include>
                <include>log4j.properties</include>
            </includes>
        </resource>

    </resources>

    <pluginManagement>
        <plugins>
            <!-- Ignore/Execute plugin execution -->
            <plugin>
                <groupId>org.eclipse.m2e</groupId>
                <artifactId>lifecycle-mapping</artifactId>
                <version>1.0.0</version>
                <configuration>
                    <lifecycleMappingMetadata>
                        <pluginExecutions>
                            <!-- copy-dependency plugin -->
                            <pluginExecution>
                                <pluginExecutionFilter>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-dependency-plugin</artifactId>
                                    <versionRange>[1.0.0,)</versionRange>
                                    <goals>
                                        <goal>copy-dependencies</goal>
                                    </goals>
                                </pluginExecutionFilter>
                                <action>
                                    <ignore />
                                </action>
                            </pluginExecution>
                        </pluginExecutions>
                    </lifecycleMappingMetadata>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

【讨论】:

【参考方案12】:

将您想要外部化的属性放在 jar 外部的 file.properties 或 yml 中。

@Bean
    //Reads database properties from the config.yml
    public static PropertySourcesPlaceholderConfigurer properties() 
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        yaml.setResources(new FileSystemResource(filePath + File.separator +"config.yml"));
        propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
        return propertySourcesPlaceholderConfigurer;
    

【讨论】:

以上是关于Spring Boot:是不是可以在带有胖 jar 的任意目录中使用外部 application.properties 文件?的主要内容,如果未能解决你的问题,请参考以下文章

使用胖 Jar 插件和 Spring Boot 应用程序构建的 Gradle 给出“应用程序启动失败”

从 jar 运行时 CSS 未在 Spring Boot 中加载

嵌入式 Jetty 环境中的 Spring Boot 和 Spring Security 集成

如何使用 Gradle 创建具有实现依赖项的可执行胖 jar

带有 JSP 的 Spring Boot 应用程序不能作为独立的 jar 工作,但可以在 IntelliJ 中工作

带有静态内容的 Spring Boot 项目在运行 jar 时生成 404