1

I have a SpringBoot app that is running with embedded Tomcat. This listener is responsible for loading the application properties from a MySQL database and inserting them into the Environment. It looks like this:

@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));

I Invoke the application like this:

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

What I'm trying to do is to externalize the application-datasource.properties file so it can reside on my various app servers (Dev, QA, Prod, etc.). However, I am unable to get the listener to find this properties file, and I'm not sure why. I tried setting the RUN_ARGS property in the deployment.conf file to something like

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

I've also tried adding the directory with the properties file to the classpath. Nothing I'm doing seems to be working, but I'm sure I'm just doing something stupid here. Note that I don't get an Exception when loading the file, the resulting Properties are simply empty.

cloudwalker
  • 2,346
  • 1
  • 31
  • 69

2 Answers2

1

Spring Boot made property file loading very easy and hassle free.You do not need to get bother while loading properties and spring boot is there. There are many ways to load properties file with spring boot some of there are: 1) simply provide application property on classpath with application-{Profile}.properties and just pass active profile as argument --spring.profiles.active= profileName

2) configuration file with spring.config.location in which loaded properties to be defined as an environment property used. (we can load it from classpath or external file path. As per spring boot official docs, By default, the configured locations are classpath:/,classpath:/config/,file:./,file:./config/. The resulting search order is:

file:./config/

file:./

classpath:/config/

classpath:/

When custom config locations are configured, they are used in addition to the default locations. Custom locations are searched before the default locations. For example, if custom locations classpath:/custom-config/,file:./custom-config/ are configured, the search order becomes:

file:./custom-config/

classpath:custom-config/

file:./config/

file:./

classpath:/config/

classpath:/

3) You can also use @PropertySource annotations on your @Configurationclasses

Please refer this for more details (24.4 and 24.5)

Edited Answer

As per your comment you want your properties loaded before any bean created then why you want it from class path??? You can make it more secure by keep it on relative file path. There are several benefits while reading property file from relative file path.

1) Property file on relative file path on server which is secure and no direct access. 2) If file is modified then new patch is not required, You just need to restart the process and updated properties will be utilized. 3) Over all less efforts required in modification.

Now below is the example which is perfect match with your requirements:

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;
    }

Now you just need to do two things, 1) Create Directory 2) Provide actual active profile on run time

1)Create Directory:

If your working directory is ./path/to/applications/active_project then create new folder in ./path/to/applications/ directory:

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

2) Provide actual active profile on run time:

java -DactiveProfile=Dev -jar jarFileName.jar
  • Well, the issue I'm having is that my application listener executes before any beans have been created or before the autowiring has completed, so it has not yet loaded the application properties. Therefore, I am trying to load the properties file manually from the classpath and then use the properties to initialize a datasource and fetch the rest of my properties and inject them into the Environment so they are available on app initialization. – cloudwalker Aug 01 '17 at 20:19
  • ok i got your point and i believe there is issue with provided path. – Irfan Bhindawala Aug 02 '17 at 09:44
  • ok i got your point and i believe there is issue with provided path, for example you are providing path like '/path/to/application-datasource.properties' but spring not considering sub-directories of class path 'src/main/resources/' by default. I will update my answer accordingly. – Irfan Bhindawala Aug 02 '17 at 10:08
  • That does the trick! I tweaked your code slightly, but that's perfect. Thank you! – cloudwalker Aug 02 '17 at 14:46
  • I just added some stuff to load the path as a System property before checking the relative path.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"; } – cloudwalker Aug 03 '17 at 14:59
  • Thanks for the post. What is the best practice storing SSL password In embedded tomcat spring boot applications?I see examples that all have those alias and password for a trustStore.jks defined in a properties file. What if we need to use Dockerfile to creat an image and deploy it to a kubernetes cluster? Any design patterns or suggestions? Thanks! – fongfong Feb 23 '20 at 17:59
0

You can try this from the main method itself.

  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")
    }

and you can create the corresponding file for environment you set.

shabinjo
  • 1,473
  • 1
  • 16
  • 22