16

Problem Statement

I want to load properties from a properties file in a classpath or at an external location before the beans are initialized. These properties are also a part of Bean initialization. I cannot autowire the properties from Spring's standard application.properties or its customization because the same properties file must be accessible by multiple deployables.

What I Tried

I'm aware about Spring Application Events; in fact, I'm already hooking ContextRefreshedEvent to perform some tasks after the Spring Context is initialized (Beans are also initialized at this stage).

For my problem statement, from the description of Spring Docs ApplicationEnvironmentPreparedEvent looked promising, but the hook did not work.


@SpringBootApplication
public class App {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(App.class, args);
    }


    @EventListener
    public void onStartUp(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedEvent");    // WORKS
    }

    @EventListener
    public void onShutDown(ContextClosedEvent event) {
        System.out.println("ContextClosedEvent");   // WORKS
    }

    @EventListener
    public void onEvent6(ApplicationStartedEvent event) {
        System.out.println("ApplicationStartedEvent");  // WORKS BUT AFTER ContextRefreshedEvent
    }


    @EventListener
    public void onEvent3(ApplicationReadyEvent event) {
        System.out.println("ApplicationReadyEvent");    // WORKS WORKS BUT AFTER ContextRefreshedEvent
    }


    public void onEvent1(ApplicationEnvironmentPreparedEvent event) {
        System.out.println("ApplicationEnvironmentPreparedEvent");  // DOESN'T WORK
    }


    @EventListener
    public void onEvent2(ApplicationContextInitializedEvent event) {
        System.out.println("ApplicationContextInitializedEvent");   // DOESN'T WORK
    }


    @EventListener
    public void onEvent4(ApplicationContextInitializedEvent event) {
        System.out.println("ApplicationContextInitializedEvent");
    }

    @EventListener
    public void onEvent5(ContextStartedEvent event) {
        System.out.println("ContextStartedEvent");
    }

}

Update

As suggested by M.Deinum in the comments, I tried adding an application context initializer like below. It doesn't seem to be working either.

    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .sources(App.class)
                .initializers(applicationContext -> {
                    System.out.println("INSIDE CUSTOM APPLICATION INITIALIZER");
                })
                .run(args);

    }

Update #2

While my problem statement is regarding loading properties, my question/curiosity is really about how to run some code before the classes are initialized as beans and put into Spring IoC container. Now, these beans require some property values during initialization and I can't/don't want to Autowire them because of the following reason:

As stated in comments and answers, the same can be done using Spring Boot's externalized configuration and profiles. However, I need to maintain application properties and domain-related properties separately. A base domain properties should have at least 100 properties, and the number grows over time. Both application properties and domain-related properties have a property file for different environments (dev, SIT, UAT, Production). Property files override one or more of the base properties. That's 8 property files. Now, the same app needs to be deployed into multiple geographies. That makes it 8 * n property files where n is the number of geographies. I want all the property files stored in a common module so that they can be accessed by different deployables. Environment and geography would be known in run-time as system properties.

While these might be achieved by using Spring profiles and precedence order, I want to have a programmatic control over it (I also would maintain my own property repository). Eg. I would write a convenience utility called MyPropUtil and access them like:

public class MyPropUtil {
     private static Map<String, Properties> repository;

     public static initialize(..) {
         ....
     }

     public static String getDomainProperty(String key) {
        return repository.get("domain").getProperty(key);
     }

     public static String getAppProperty(String key) {
         return repository.get("app").getProperty(key);
     }

     public static String getAndAddBasePathToAppPropertyValue(String key) {
        ...
     }

}

@Configuration
public class MyComponent {

    @Bean
    public SomeClass getSomeClassBean() {
        SomeClass obj = new SomeClass();
        obj.someProp1(MyPropUtil.getDomainProperty('domainkey1'));
        obj.someProp2(MyPropUtil.getAppProperty('appkey1'));
        // For some properties
         obj.someProp2(MyPropUtil.getAndAddBasePathToAppPropertyValue('some.relative.path.value'));
        ....
        return obj;
    }

}

From the docs, it seems like ApplicationEvents and ApplicationInitializers fit my need, but I am not able to get them to work for my problem statement.

Dilip Raj Baral
  • 3,060
  • 6
  • 34
  • 62
  • 3
    And why wouldn't adding `spring.config.additional-locations` work? Or just providing all config files through `spring.config.location`? I don't see why would you need to load them yourself? Those locations can be as external as what you want to load (just use the `file:` prefix to load them from the file system). – M. Deinum Nov 06 '19 at 08:09
  • Did you try @PreConstruct? – FishingIsLife Nov 06 '19 at 15:17
  • @M.Deinum It's a little more complicated that. Just talking about the number of possible properties file - the product is supposed to multiple geography with each geography having their own development, UAT and production version of the properties and I need to be able to set the precedence; Beside I'd also have a wrapper (PropertyUtility) to get and process the property values based on different context. (I can again do this with a `@Configuration` class but there are too many properties.) – Dilip Raj Baral Nov 06 '19 at 17:56
  • @medTech That won't work as I need to load several property files to a property repository not just for a couple beans. – Dilip Raj Baral Nov 06 '19 at 17:57
  • 2
    That is what profiles are for (different Config per environment) and the precedence is the order in which you specify them. Regardless work with the framework and not around it. YOu want to use the Spring facilities but not use the framework. If you really want to do complex stuff which you think isn’t possible use an ApplicationContextInitializer implementation to do this and register additional PropertySources (not to be confused with @PropertySource!). – M. Deinum Nov 06 '19 at 18:31
  • @M.Deinum Thank you for pointing out to ApplicationContextInitializer. However, I couldn't get it to work. Also, why do you think some of the event initializer that I already tried to hook to didn't work? – Dilip Raj Baral Nov 07 '19 at 07:14
  • Because you are too late in the process. But again, I strongly suggest first to try the out-of-the-box features by specifing the configurations, config names etc. IMHO you don't really need all the added complexity and Spring Boot offers already all you need. – M. Deinum Nov 07 '19 at 07:21
  • @M.Deinum Let's agree for a moment that I don't not need the complexity for the current problem statement. But what if really need to run something else that's unavoidable? What do you mean by `too late in the process`? – Dilip Raj Baral Nov 08 '19 at 10:30
  • 1. Do you know/did you have a look at [spring-cloud-config](https://cloud.spring.io/spring-cloud-config/reference/html/)? it sounds like "your problem" is exactly the domain of this (spring-sub-)project. (put your properties on a git repo, and bootstrap all your applications from there) 2. There are many ways to "do something before spring" (static block, main method to name 2..(spring-independent)) ... – xerx593 Nov 08 '19 at 10:40
  • Spring provides `BeanFactoryPostProcessor` and `PropertyResourceConfigurer` interfaces that you can implement as a `@Component` which will be executed prior to bean creation - however, for the problem you're describing, I have to agree with the previous comment from xerx593 that you would be better off looking at the spring-cloud-config functionality. – Rob D Nov 08 '19 at 13:24
  • This task can be solved by creating a _parent_-project that contains general properties and configs. And those properties might be used by child projects. `spring-boot-starter-parent -> custom-configurable-parent -> child-project` – Rostyslav Barmakov Nov 08 '19 at 21:16
  • You could also just set specific environment variables on the target host when running the application and use placeholders for them in your different deployables application.properties. Have a look at https://stackoverflow.com/a/35535138/11133168 – FlorianDe Nov 09 '19 at 07:59
  • Have you considered implementing the [`EnvironmentPostProcessor`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/env/EnvironmentPostProcessor.html)? Sounds like it can solve your problem. See also [Customize the Environment or ApplicationContext Before It Starts](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-environment-or-application-context) – Denis Zavedeev Nov 10 '19 at 19:57
  • You are trying to add property files in a situation when they are already loaded and (partially) resolved. So adding them with a listener like that is too late in the process. For that use an `ApplicationContextInitializer`. But for just loading property files, just use the mechanisms provided by Spring Boot. For other things it depends on what you want to do and what to run when. – M. Deinum Nov 11 '19 at 06:43
  • @M.Deinum I'm trying to get `ApplicationContextInitializer` to work but noot able to. Please see the updates in the qeustion. – Dilip Raj Baral Nov 11 '19 at 07:40
  • If you don't want to autowire the values and don't need them to be available to spring boot, then load them yourself in your utility class. Then don't bother with Spring Boot, load them in the constructor or static initializer of your class. – M. Deinum Nov 11 '19 at 07:44
  • Take a look at this interface and hook method: `BeanPostProcessor#postProcessBeforeInitialization`. You can find a link in my answer. – Burak Akyıldız Nov 11 '19 at 07:47
  • @M.Deinum Yes. That's what I need but I need it to load the props using that utility class before any beans are initialized because these utility class would be used in more than one beans and I want to avoid `if (utilityNotAlreadyInitialized) initialize();` – Dilip Raj Baral Nov 12 '19 at 07:48
  • Again do that in a static initializer, which will load the properties as soon as the class is loaded in memory. If you don't require spring to do this, then don't try to shoehorn it into Spring. – M. Deinum Nov 12 '19 at 08:09
  • I don't remember 9 answers given to bounty question, it'll be great if you update on your progress – Ori Marko Nov 13 '19 at 08:28
  • https://stackoverflow.com/questions/32111968/read-spring-boot-property-inside-listener See if this can help you – Bhaumik Thakkar Nov 13 '19 at 08:35
  • didn't you try "--spring.config.location=" runtime argument ? – Keaz Nov 14 '19 at 06:11

12 Answers12

10

Bit late to the party but hopefully I can offer a solution to your updated problem statement.

This will focus on problem of how to run some code before the classes are initialized as beans and put into Spring IoC container

One issue I notice is that you're defining your application events via the @EventListener annotation.

These are only called once all beans are initiated since these annotations are processed by EventListenerMethodProcessor which is only triggered when the context is ready (see SmartInitializingSingleton#afterSingletonsInstantiated)

As such, some of the events that occur before the context is ready. e.g. ContextStartedEvent, ApplicationContextInitializedEvent won't make it to your listener.

Instead, what you can do is extend the interface for these events directly.

@Slf4j
public class AllEvent implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(final ApplicationEvent event) {
        log.info("I am a {}", event.getClass().getSimpleName());
    }

Note the missing @Component. Even bean instantiation can occur after some of these events. If you use @Component, then you'll get the following logs

I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent

Still better and more instant than the annotative listeners but will still not receive the initialization events. For that, what you need to do is follow the instructions found here

To summarize,

  • Create directory resources/META-INF
  • Create file spring.factories
  • org.springframework.context.ApplicationListener=full.path.to.my.class.AllEvent

The result:-

I am a ApplicationContextInitializedEvent
I am a ApplicationPreparedEvent
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent

In particular, ApplicationContextInitializedEvent should allow you to perform whatever per-instantiation tasks you need.

Matt R
  • 1,276
  • 16
  • 29
3

Create a bean that will be a properties repository and inject it in other beans requiring properties.

In your example, instead of having static methods in MyPropUtil, make the class a bean itself with instance methods. Initialize Map<String, Properties> repository in the initialize method annotated with @PostConstruct.

@Component
public class MyPropUtil {

  private static final String DOMAIN_KEY = "domain";
  private static final String APP_KEY = "app";

  private Map<String, Properties> repository;

  @PostConstruct
  public void init() {
    Properties domainProps = new Properties();
    //domainProps.load();
    repository.put(DOMAIN_KEY, domainProps);

    Properties appProps = new Properties();
    //appProps.load();
    repository.put(APP_KEY, appProps);
  }

  public String getDomainProperty(String key) {
    return repository.get(DOMAIN_KEY).getProperty(key);
  }

  public String getAppProperty(String key) {
    return repository.get(APP_KEY).getProperty(key);
  }

  public String getAndAddBasePathToAppPropertyValue(String key) {
    //...
  }
}

and

@Configuration
public class MyComponent {

  @Autowired
  private MyPropUtil myPropUtil;

  @Bean
  public SomeClass getSomeClassBean() {
    SomeClass obj = new SomeClass();
    obj.someProp1(myPropUtil.getDomainProperty("domainkey1"));
    obj.someProp2(myPropUtil.getAppProperty("appkey1"));
    // For some properties
    obj.someProp2(myPropUtil.getAndAddBasePathToAppPropertyValue("some.relative.path.value"));
      //...
      return obj;
  }
}

Or you can inject MyPropUtil directly to the SomeClass:

@Component
public class SomeClass {

  private final String someProp1;
  private final String someProp2;

  @Autowired
  public SomeClass(MyPropUtil myPropUtil) {
    this.someProp1 = myPropUtil.getDomainProperty("domainkey1");
    this.someProp2 = myPropUtil.getAppProperty("appkey1");
  }
  //...
}
Eugene Khyst
  • 9,236
  • 7
  • 38
  • 65
  • Properties are going to be read from almost every where (over 1000 classes) at the moment. Do you think it's a clean way (or a good idea) to inject PropertyUtil to every such classes and scenarios? – Dilip Raj Baral Nov 12 '19 at 13:51
  • Yes, it's a clean way. Consider a `User` entity (`@Entity`) and a DAO `UserRepository`. Will you inject it in all classes that require this DAO functionality? Yes, and number of classes to inject this DAO doesn't matter. – Eugene Khyst Nov 12 '19 at 14:16
  • Considered that the properties can be read from over 1000 classes, all the more reason to use this approach and avoid using static method to get those properties. Imagine if one day you need to change how `MyPropUtil` works, and need to substitute it with some of its variation/subclasses. Spring provides many options to do that through dependency injection, comparing to having to manually changing method from calling `MyPropUtil` to `MyPropUtil2`.. but of course, that depends on your business domain, e.g. how likely the `MyPropUtil` may changes in the future. So, static may be fine too. – Montri M Nov 15 '19 at 01:07
3

I think Spring Cloud Config is a perfect solution for your problem statement. Detailed documentation Here

Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system.

So you can easily manage the configurations outside of the app, as well as all the instances will use same configurations.

1

I feel like your main issue is that you need to maintain application properties and domain-related properties separately. From spring's perspective, it doesn't really matter since all properties files are kinda merged together after they have been loaded in memory. So for example, you have two files that contain some properties:

application.related=property1 # this is in application.properties
domain.related=property2 # this is in domain-specific.properties

After they have been loaded, you will get one big thing that contains all properties, if I am not mistaken, it is a org.springframework.core.env.ConfigurableEnvironment instance.

Then what you need to do is just inject the property you need using something like @Value.

For the main issue, to separate properties into different files, you just need to specify spring's spring.config.name property (via environment variable, command line or programmatically). Following the above example, it should be spring.config.name=application,domain-specific.

Furthermore, if you really want to have programmatic control, you can add a custom EnvironmentPostProcessor which exposes the ConfigurableEnvironment instance.

geliba187
  • 357
  • 2
  • 11
0

As explaned in this post you can add external property files like this;

public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
    properties.setLocation(new FileSystemResource("/Users/home/conf.properties"));
    properties.setIgnoreResourceNotFound(false);
    return properties;
}

If you don't want to use this, just read the property file with jackson and set the properties to System.setProperty("key","value") in the main method before spring starts.

If you don't want to use this too, take a look at the BeanPostProcessor#postProcessBeforeInitialization method. It runs before bean properties initialized by spring.

Burak Akyıldız
  • 1,550
  • 15
  • 25
  • This might and will lead to issues, as it registers an additional `PropertySourcesPlaceholderConfigurer` and these properties aren't available in the `Environment`. – M. Deinum Nov 11 '19 at 06:42
  • Spring creates its own bean when any been with type `PropertySourcesPlaceholderConfigurer` missing. There is no additional `PropertySourcesPlaceholderConfigurer`. You can look at `PropertyPlaceholderAutoConfiguration` classes code. – Burak Akyıldız Nov 11 '19 at 07:27
  • 1
    Added in 1.5, in earlier versions this was a cause for issues. But you don't need an additional `PropertySourcesPlaceholderConfigurer` just add an `@PropertySource` which is easier and less error prone, and the properties can be resolved through `Environment` which you cannot if you decide to load the properties the way you suggest. – M. Deinum Nov 11 '19 at 07:28
0

I might be missing what exactly do you mean by "Beans initialization", probably an example of such a bean in a question could be beneficial.

I think you should differentiate between properties reading part and bean initialization. By the time of bean initialization, properties are already read and available. Thats a part of spring magic, if you wish.

That's why the following code works for example:

@Component
public class MySampleBean {

    public MySampleBean(@Value("${some.prop}" String someProp) {...}
}

It doesn't matter from where do these property come (spring boot defines many different ways of these places with precedence between them), it will happen before the initialization of beans happens.

Now, lets get back to your original question:

I want to load properties from a properties file in a classpath or at external location (before the beans are initialized - irrelevant).

In spring / spring-boot there is a concept of profiles that basically allows to create a file application-foo.properties (or yaml) and when you load with --spring.profiles.active=foo it will automatically load properties defined in this application-foo.properties in addition to the regular application.properties

So you can place the stuff that you want to "load from classpath" into application-local.properties (the word local is for the sake of example only) and start the application with --spring.profiles.active=local (in the deployment script, docker file or whatever)

If you want to run the property from external location (outside the classpath) you can use: --spring.config.location=<Full-path-file>

Note that even if you put some properties into a regular application.properties and still use --spring.config.location with the same key-value pairs they will take precedence over the properties in the classpath.

Alternatively you can use only --sring.profiles.active=local or remote and do not use config locations at all.

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
0

You can configure external location directly in the command line:

java -jar app.jar --spring.config.location=file:///Users/home/config/external.properties
Selindek
  • 3,269
  • 1
  • 18
  • 25
0

You can use WebApplicationInitializer to execute code before classes are initialized as beans

public class MyWebInitializer implements WebApplicationInitializer {    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
       var ctx = new AnnotationConfigWebApplicationContext();
       ctx.register(WebConfig.class);
       ctx.setServletContext(servletContext);

We create an AnnotationConfigWebApplicationContext and register a web configuration file with register().

Ori Marko
  • 56,308
  • 23
  • 131
  • 233
0

You can check if PropertySource may help you.

Example:

@PropertySource({"classpath:persistence/persistence.properties"})

You can use this annotation on every @Configuration or @SpringBootApplication bean

ValerioMC
  • 2,926
  • 13
  • 24
0

It sounds like you want to take some ownership of a part of the bean initialization. Typically people think of Spring completing the bean configuration, but in your case it might be easier to consider Spring as starting it.

So, your bean has some properties you want to configure, and some that you want Spring to configure. Just annotate the ones you want Spring to configure (with @Autowire or @Inject, or whatever flavour you prefer), and then take over the control from there, using @PostConstruct or InitializingBean.

class MyMultiStageBoosterRocket {

  private Foo foo;
  private Bar bar;
  private Cat cat;

  @Autowire
  public MyMultiStageBoosterRocket(Foo foo, Bar bar) {
    this.foo = foo;
    this.bar = bar'
  }

  // called *after* Spring has done its injection, but *before* the bean
  // is registered in the context
  @PostConstruct
  public void postConstruct() {
    // your magic property injection from whatever source you happen to want
    ServiceLoader<CatProvider> loader = ServiceLoader.load(CatProvider.class);
    // etc...
  }
}

Of course your mechanism for property resolution would need to be available statically somehow, but that seems to fit with you MyPropUtil example.

Getting far more involved, you start looking at Bean Post Processors directly (@PostConstruct is a simple variant of sorts).

There's a previous question, with a useful answer, here How exactly does the Spring BeanPostProcessor work?, but for simplicity, you'd do something like

public class CustomBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {

        // fixme: detect if this bean needs fancy initialization

        return bean;
    }
}

Clearly @PostProcess, or InitializingBean are simpler, but the custom post processor has a big advantage... it can be injected with other Spring managed beans. That means you can Spring manage your property injection whatever-thing, and still manually manage the actual injection process.

ptomli
  • 11,730
  • 4
  • 40
  • 68
0

Just try to load everything you need in main before

SpringApplication.run()

call

public static void main(String[] args) {
    // before spring initialization
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    SpringApplication.run(CyberRiskApplication.class, args);
}
sedrakpc
  • 508
  • 4
  • 18
0

You can use ApplicationEnvironmentPreparedEvent but it can't be configured using EventListener annotation. Because by this time Bean drfinitions are not loaded. See the below link on how to cofigure this event. https://www.thetechnojournals.com/2019/10/spring-boot-application-events.html

Ashok Prajapati
  • 374
  • 2
  • 7