ApplicationContext is Spring’s core interface or container. It has many functions. It allows you to create, retrieve, and manage beans. Can publish events; Can contain resource files; You can get the environment in which the container is currently running. Spring ApplicationContext to more flexible configuration, in the process of container initialization, Spring allows the user to modify the object, the specific method is extended ApplicationContextInitializer.

Spring Boot some built-in ApplicationContextInitializer, used to implement the Web configuration, the log configuration, and other functions, this paper takes the Spring Boot, for example, Talk about ApplicationContextInitializer usage.

ApplicationContextInitializer

ApplicationContextInitializer is an interface, this interface defines a initialize method, this method can be carried in ApplicationContext initialization time. We can think of this as the hook function of ApplicationContext. @functionalinterface is an annotation added in JDK1.8 to indicate that this interface has a single method.

@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}
Copy the code

We are in use, only need to inherit ApplicationContextInitializer, realize the initialize method. The initialize method takes the current ApplicationContext as an input. From ApplicationContext, we can get the current Environment and add or modify some values. We can add listeners by calling the addApplicationListener method. Anyway, a lot of initialization can be done here.

@Order(1)
public class UserInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("UserInitializer"); }}Copy the code

ApplicationContextInitializer there can be multiple, support @ Order comments, said execution Order, yue xiaoyue early.

How SpringFactoriesLoader loading ApplicationContextInitializer

ApplicationContextInitializer subclasses of want and need to register to the ApplicationContext, Spring Boot project startup process of the first step is to create SpringApplication object, In the constructor of the object, the program loaded ApplicationContextInitializer implementation class. Let’s look at this method in detail.

public SpringApplication(ResourceLoader resourceLoader, Class
       ... primarySources) {

  / / will ApplicationContextInitializer implementation class instance to join this. Initializers. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); . }Copy the code

In this method, the SpringFactoriesLoader gets the name of the implementation class based on the interface type, creates the instance through reflection, and sorts the instance based on the value of the SORT annotation. With reflection, we can easily get the constructor and annotation of a class, so creating instances and sorting is relatively simple. The interesting point is how does the SpringFactoriesLoader find the implementation class for the specified interface?

private <T> Collection<T> getSpringFactoriesInstances(Class
       
         type, Class
        [] parameterTypes, Object... args)
        {
  ClassLoader classLoader = getClassLoader();

  // SpringFactoriesLoader gets the name of the implementation class based on the interface type
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

  // reflection creates an instance
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

  // Sort by the value of the sort annotation
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
}
Copy the code

The SpringFactoriesLoader is a loading method provided by Spring. The SpringFactoriesLoader loads a meta-INF/Spring. factories file in the classpath with the full names of the interface and implementation classes in the Properties format. If there are multiple implementation classes, separate them with commas. SpringFactoriesLoader plays an important role in Spring Boot. It is used not only for loading initializers, but also for subsequent load listeners, profilers, preprocessors, and post-processors.

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
  ...
  try {
    // Essentially calls the classLoader.getResources method
    Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
    while(urls.hasMoreElements()) { ... }}... }Copy the code

The initialize method of UserInitializer will be used when Spring Boot is started.

org.springframework.context.ApplicationContextInitializer=xxx.xxx.UserInitializer
Copy the code

Do you notice that getting an instance of a class through an interface is a bit like JDBC? In fact, it all belongs to the Java Service Provider Interface (SPI) mechanism, which separates the Service Interface from the Service implementation to achieve decoupling and improve scalability. The interface and implementation of the interface in SPI are not in a project, so to speak, and the SPI mechanism is project-level isolation, which is common in framework design. In Spring SPI is implemented by extending meta-INF/spring.Factories, and in Dubbo we use a similar approach by extending meta-INF/Dubbo and other files.

ApplicationContextInitializer besides registered through the SPI, actually also can register by means of hard coded. Change the Boot method of Spring Boot and manually add an Initializer.


@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication springApplication = new SpringApplication(DemoApplication.class);
		springApplication.addInitializers(newDemoInitializer()); springApplication.run(args); }}Copy the code

This provides the same effect as the first method, essentially adding an Initialalizer instance to the Initializers of the SpringApplication. However, the first approach is superior to the second. Using SPI’s extension approach, you can implement the extension without changing the original code, conforming to the open closed principle.

Another option is to use the Spring Boot configuration file application.properties. We’ll talk about this later when we look at Spring Boot’s built-in initializer.

context.initializer.classes=xxx.xxx.DemoInitializer
Copy the code

ApplicationContextInitializer execution phase

Spring Boot executes the main method, which is essentially the run method of the SpringApplication.

public static void main(String[] args) {
  SpringApplication.run(DemoApplication.class, args);
}
Copy the code

The Run method is a static method of SpringApplication in which the SpringApplication instance object is generated and the run method of the instance object is actually executed. SpringFactoriesLoader loading ApplicationContextInitializer occurs in the process of generating SpringApplication instance. Once the class is loaded and the instance is generated, when do these initializers take effect? The following is the run method execution flow.

ApplicationContextInitializer was prepared in the context of the Application stage is carried out. We know that spring is in the context of time through the BeanFactory loading beans, so ApplicationContextInitializer execution in Bean before loading, However, at this point, the Environment has been initialized, and we can obtain the instance of Environment at this stage, which is convenient to add or modify some values. At this point, the ApplicationContext instance is also created, and you can pre-insert listeners, handlers, and so on into the context.

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
  context.setEnvironment(environment);
  postProcessApplicationContext(context);
  
  / / ApplicationContextInitializer executionapplyInitializers(context); listeners.contextPrepared(context); bootstrapContext.close(context); . }Copy the code

In applyInitializers, the initialize methods are called as initializers are iterated through previously registered initializers.

protected void applyInitializers(ConfigurableApplicationContext context) {
  for (ApplicationContextInitializer initializer : getInitializers()) {
    ...
    // Execute the initialize methodinitializer.initialize(context); }}Copy the code

Spring Boot built-in initializer

Spring provides extended ApplicationContextInitializer method, Spring the Boot to carry forward. We can in the spring – the boot jar package found in the meta-inf spring. The factories, the following is the ApplicationContextInitializer configuration.

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,
org.springframework.boot.context.ContextIdApplicationContextInitializer,
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
Copy the code

ConfigurationWarningsApplicationContextInitializer used to report the Spring container some of the common configuration errors, you can see, the initializer for the context added a rear Bean processors. This handler takes effect after the BeanDefinition instance is registered, and is used to handle alarms generated during the instance registration, essentially printing alarms through logs.

public class ConfigurationWarningsApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {...@Override
	public void initialize(ConfigurableApplicationContext context) {
		context.addBeanFactoryPostProcessor(newConfigurationWarningsPostProcessor(getChecks())); }... }Copy the code

ContextIdApplicationContextInitializer is used to set the Spring application context ID, this ID can be ApplicationContext# getId ().

public class ContextIdApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {...@Override
	public void initialize(ConfigurableApplicationContext context) { ContextId contextId = getContextId(applicationContext); applicationContext.setId(contextId.getId()); applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId); }... }Copy the code

DelegatingApplicationContextInitializer see Delegating to know, the initializer is service to others. The initializer gets application. The properties of the configured to the context., initializer. Classes of values, the value is the full path of the initializer, between multiple use commas. Once you get the name, instantiate it using reflection and fire the initialize method of the initializer in turn. DelegatingApplicationContextInitializer makes Spring the Boot users can in application. The properties configured in the initializer. It is important to note that DelegatingApplicationContextInitializer priority is zero, so no matter what the context., initializer. Classes configuration how much is the order of the initializer, Will be executed with a priority of 0.

public class DelegatingApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
  private int order = 0; .@Override
	public void initialize(ConfigurableApplicationContext context) {
    // The environment contains the Spring Boot configuration, including the application.properties configuration
		ConfigurableEnvironment environment = context.getEnvironment();
    Find the configuration / / from the application. The properties of the context., initializer. The value of the classes
    / / the context, initializer. Classes of value is the full path of the initializer. You can have more than one.List<Class<? >> initializerClasses = getInitializerClasses(environment);// Trigger initializers in sequence
		if(! initializerClasses.isEmpty()) { applyInitializerClasses(context, initializerClasses); }}... }Copy the code

RSocketPortInfoApplicationContextInitializer and ServerPortInfoApplicationContextInitializer ApplicationContext Adds a listener, both are listening RSocketServerInitializedEvent events, an attribute is added to the Environment, the source for the Environment, the difference is that one is the increase SocketPort, one is to increase ServerPort. I won’t post the code.

The “meta-INF /spring.factories” factories under spring-boot can also be configured. There are also two initializers defined here. As you can see here, the way SPI is used does reduce coupling between projects, with each project being able to define its own implementation.

# Initializers
org.springframework.context.ApplicationContextInitializer=
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
Copy the code

conclusion

  1. ApplicationContextInitializer is one of the Spring provides extension points, used in ApplicationContext container loading beans before the context of the current configuration.

  2. ApplicationContextInitializer implementation has three, one is in the classpath path under the meta-inf/spring. Factories file to fill in interface and implementation class name, multiple implementations of words separated by commas. Is the second in the Spring the Boot startup code manually add an initializer, the third is in the application. The context that is configured in the properties, initializer. Classes.

  3. The SpringFactoriesLoader is a spring-provided loader for loading external project configurations. It routinely reads a meta-INF /spring.factories file and parses it to get the implementation class for the specified interface. SpringFactoriesLoader is a typical SPI mode, which is widely used in Spring Boot. This mode separates the service interface from the service implementation to decouple and improve scalability.

  4. Some Spring Boot with built-in initializer, most of the function is to configure the environment variables, such as ServerPortInfoApplicationContextInitializer, implementing method is to add listeners to ApplicationContext. Also used to configure the log, such as ConfigurationWarningsApplicationContextInitializer implementation approach is to increase the Bean processors do after calibration. DelegatingApplicationContextInitializer is rather special, it will get application. The properties in the configuration of the context., initializer. Classes, Load and execute it as an initializer.

If you think you’ve learned something, please give it a thumbs up!