【问题标题】:Spring Boot embedded Tomcat not loading external properties file in ApplicationListenerSpring Boot 嵌入式 Tomcat 未在 ApplicationListener 中加载外部属性文件
【发布时间】:2018-01-08 12:27:21
【问题描述】:

我有一个使用嵌入式 Tomcat 运行的 SpringBoot 应用程序。此侦听器负责从 MySQL 数据库加载应用程序属性并将它们插入到环境中。它看起来像这样:

@Component
public class DbMigrationAndPropertyLoaderApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    private static final Logger LOGGER = LoggerFactory.getLogger(DbMigrationAndPropertyLoaderApplicationListener.class);

    private static final String PROPERTY_SOURCE_NAME = "applicationProperties";

    private final int order = Ordered.HIGHEST_PRECEDENCE + 4;

    private final PropertySourceProcessor propertySourceProcessor = new PropertySourceProcessor();

    @Override
    public int getOrder() {
        return this.order;
    }

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        Properties databaseProperties;
        try {
            databaseProperties = PropertiesLoaderUtils.loadAllProperties("application-datasource.properties");
        } catch (IOException e) {
            throw new RuntimeException("Unable to load properties from application-datasource.properties. Please ensure that this file is on your classpath", e);
        }
    ConfigurableEnvironment environment = event.getEnvironment();
    Map<String, Object> propertySource = new HashMap<>();
    try {
        DataSource ds = DataSourceBuilder
                .create()
                .username(databaseProperties.getProperty("flyway.user"))
                .password(EncryptionUtil.decrypt(databaseProperties.getProperty("flyway.password")))
                .url(databaseProperties.getProperty("spring.datasource.url"))
                .driverClassName(databaseProperties.getProperty("spring.datasource.driver-class-name"))
                .build();

        LOGGER.debug("Running Flyway Migrations");
        //Run Flyway migrations. If this is the first time, it will create and populate the APPLICATION_PROPERTY table.
        Flyway flyway = new Flyway();
        flyway.setDataSource(ds);
        flyway.migrate();

        LOGGER.debug("Initializing properties from APPLICATION_PROPERTY table");
        //Fetch all properties

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = ds.getConnection();
            preparedStatement = connection.prepareStatement("SELECT prop_key, prop_value FROM APPLICATION_PROPERTY");

            resultSet = preparedStatement.executeQuery();

            //Populate all properties into the property source
            while (resultSet.next()) {
                String propName = resultSet.getString("prop_key");
                propertySource.put(propName, propertySourceProcessor.decrypt(resultSet.getString("prop_value")));
            }

            //Create a custom property source with the highest precedence and add it to the Environment
            environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource));

我这样调用应用程序:

public static void main(String[] args) {
        ApplicationContext ctx = new SpringApplicationBuilder(PortalApplication.class)
                .listeners(new DbMigrationAndPropertyLoaderApplicationListener())
                .build(args)
                .run();

我想要做的是将 application-datasource.properties 文件外部化,以便它可以驻留在我的各种应用服务器(Dev、QA、Prod 等)上。但是,我无法让侦听器找到此属性文件,我不知道为什么。我尝试将 deployment.conf 文件中的 RUN_ARGS 属性设置为类似

RUN_ARGS=--spring.config.location=/path/to/application-datasource.properties

我还尝试将带有属性文件的目录添加到类路径中。我所做的一切似乎都不起作用,但我确定我只是在这里做一些愚蠢的事情。请注意,加载文件时我没有收到异常,生成的属性只是空的。

【问题讨论】:

    标签: spring tomcat spring-boot


    【解决方案1】:

    Spring Boot 使属性文件加载变得非常轻松无忧。加载属性时您无需费心,Spring Boot 就在那里。 使用 Spring Boot 加载属性文件的方法有很多,其中包括: 1) 只需使用application-{Profile}.properties 在类路径上提供应用程序属性,然后将活动配置文件作为参数传递--spring.profiles.active= profileName

    2) 带有spring.config.location 的配置文件,其中加载的属性被定义为使用的环境属性。 (我们可以从类路径或外部文件路径加载它。 根据spring boot官方文档,默认配置的位置是classpath:/,classpath:/config/,file:./,file:./config/.,结果搜索顺序是:

    file:./config/
    
    file:./
    
    classpath:/config/
    
    classpath:/
    

    配置自定义配置位置时,除了默认位置外,还会使用它们。在默认位置之前搜索自定义位置。例如,如果配置了自定义位置classpath:/custom-config/,file:./custom-config/,则搜索顺序变为:

    file:./custom-config/
    
    classpath:custom-config/
    
    file:./config/
    
    file:./
    
    classpath:/config/
    
    classpath:/
    

    3) 您还可以在 @Configurationclasses 上使用 @PropertySource 注释

    请参考this了解更多详情(24.4 和 24.5)

    编辑的答案

    根据您的评论,您希望在创建任何 bean 之前加载属性,那么为什么要从类路径中获取它? 您可以通过将其保存在相对文件路径上来使其更安全。从相对文件路径读取属性文件有几个好处。

    1) 服务器上的相对文件路径上的属性文件,该文件是安全且不可直接访问的。 2)如果文件被修改,则不需要新的补丁,您只需重新启动该过程即可使用更新的属性。 3) 总而言之,修改所需的工作量更少。

    下面是完美符合您要求的示例:

    private static Properties loadConfigProps() {
            InputStream configStream = null;
            Properties _ConfigProps = null;
            try {
                String prjDir = System.getProperty("user.dir");
                String activeProfile = System.getProperty("activeProfile");
                int lastIndex = prjDir.lastIndexOf(File.separator);
    
                String configPath = prjDir.substring(0, lastIndex);
                configStream = new FileInputStream(new File(configPath
                        + File.separator + "_configurations" + File.separator + activeProfile + File.separator 
                        + "resources" + File.separator + "myDatabaseProperties.properties"));
                _ConfigProps = new Properties();
                _ConfigProps.load(configStream);
    
            } catch (Exception ex) {
                ex.printStackTrace();
                System.exit(1);
            } finally {
                if (null != configStream) {
                    try {
                        configStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return _ConfigProps;
        }
    

    现在你只需要做两件事, 1) 创建目录 2) 在运行时提供实际的活动配置文件

    1)创建目录:

    如果您的工作目录是./path/to/applications/active_project,则在./path/to/applications/ 目录中创建新文件夹:

    ./path/to/applications/
                 -active_project/
                 -_configurations/
                                -QA/myDatabaseProperties.properties
                                -Dev/myDatabaseProperties.properties
                                -Prod/myDatabaseProperties.properties
    

    2) 提供运行时的实际活动配置文件:

    java -DactiveProfile=Dev -jar jarFileName.jar
    

    【讨论】:

    • 嗯,我遇到的问题是我的应用程序侦听器在创建任何 bean 之前或在自动装配完成之前执行,因此它尚未加载应用程序属性。因此,我尝试从类路径手动加载属性文件,然后使用这些属性来初始化数据源并获取我的其余属性并将它们注入到环境中,以便它们在应用程序初始化时可用。
    • 好的,我明白你的意思了,我相信提供的路径有问题。
    • 好的,我明白了你的意思,我相信提供的路径存在问题,例如,你提供的路径类似于“/path/to/application-datasource.properties”,但春天没有考虑默认情况下,类路径 'src/main/resources/'。我会相应地更新我的答案。
    • 这就是诀窍!我稍微调整了您的代码,但这很完美。谢谢!
    • 在检查相对路径之前,我只是添加了一些东西来将路径作为系统属性加载。String configPath = System.getProperty("config.location"); if (StringUtils.isEmpty(configPath)) { String projectDir = System.getProperty("user.dir"); int lastIndex = projectDir.lastIndexOf(File.separator); configPath = projectDir.substring(0, lastIndex) + File.separator + "config"; }
    【解决方案2】:

    你可以从 main 方法本身试试这个。

      public static void main(String[] args){
        SpringApplication app = new SpringApplication(Application.class);
        Map<String, Object> properties =  new HashMap<>();
        properties.put("spring.profiles.default", "local");
        app.setDefaultProperties(properties);
        Environment env = app.run(args).getEnvironment();
        env.getProperty("spring.application.name")
        }
    

    您可以为您设置的环境创建相应的文件。

    【讨论】:

      猜你喜欢
      • 2015-06-26
      • 2018-06-25
      • 2021-08-31
      • 1970-01-01
      • 2016-07-22
      • 1970-01-01
      • 2019-03-04
      • 2015-05-14
      相关资源
      最近更新 更多