Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot 2 - Do something before the beans are initialized

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.

like image 256
Dilip Raj Baral Avatar asked Nov 06 '19 07:11

Dilip Raj Baral


People also ask

Is @component and @bean same?

@Component is a class-level annotation, but @Bean is at the method level, so @Component is only an option when a class's source code is editable. @Bean can always be used, but it's more verbose. @Component is compatible with Spring's auto-detection, but @Bean requires manual class instantiation.

Which does early initializing of beans?

By default, Spring “application context” eagerly creates and initializes all 'singleton scoped' beans during application startup itself. It helps in detecting the bean configuration issues at early stage, in most of the cases.

What annotation can be used to force one bean to be initialized before another?

We should use this annotation for specifying bean dependencies. Spring guarantees that the defined beans will be initialized before attempting an initialization of the current bean.

Can we use @bean without @configuration?

@Bean methods may also be declared within classes that are not annotated with @Configuration. For example, bean methods may be declared in a @Component class or even in a plain old class. In such cases, a @Bean method will get processed in a so-called 'lite' mode.


1 Answers

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");
  }
  //...
}
like image 139
Evgeniy Khyst Avatar answered Oct 09 '22 09:10

Evgeniy Khyst