SpringBoot系统初始化器使用及源码解析(ApplicationContextInitializer)

1,200 阅读5分钟

初始化器解析

系统初始化器介绍

先看看接口的注释

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
 * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the @{@link org.springframework.core.annotation.Order Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @param <C> the application context type
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

简单来说:Spring容器刷新之前执行的一个回调函数,用于向SpringBoot容器中注册属性,可以用Order接口排序

容器初始化器的使用方式

三种使用它的方式,先写个简单的例子

编写三个Initializer,分别用三种不同的方式注册

@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        HashMap<String, Object> map = new HashMap<>();
        map.put("key1", "value1");
        MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run firstInitializer");
    }
}
@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        HashMap<String, Object> map = new HashMap<>();
        map.put("key2", "value2");
        MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run secondInitializer");
    }
}
@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        HashMap<String, Object> map = new HashMap<>();
        map.put("key3", "value3");
        MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run thirdInitializer");
    }
}

1、在/META-INF/spring.facotries下

org.springframework.context.ApplicationContextInitializer=top.luyuni.sb2.initializer.FirstInitializer

2、SpringApplication实例化的时候硬编码

@SpringBootApplication
@MapperScan("top.luyuni.sb2.mapper")
public class Sb2Application {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Sb2Application.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run(args);
    }

}

3、在application.properties中配置

context.initializer.classes=top.luyuni.sb2.initializer.ThirdInitializer

为了测试,编写service和controller

@Service
public class TestService implements ApplicationContextAware {

    ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public String test(Long id){
        return applicationContext.getEnvironment().getProperty("key" + id.toString());
    }
}
@RestController
public class TestController {
    @Autowired
    private TestService testService;
    
    @GetMapping("/testInitializer/{id}")
    public String testInitializer(@PathVariable("id") Long id){
        return testService.test(id);
    }
}

启动工程

看看控制台输出

咦?怎么回事和Order中指定的不一样?这个等我们后面分析

打开浏览器测试一个环境属性有没有设置进去

看起来没有什么问题。使用我们就讲完了

来看看我们是怎么加载系统初始化器的
第一种方式,配置spring.factores,使用SpringFactoriesLoader

从SpringApplication.run中一步步点进去

org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

org.springframework.boot.SpringApplication#SpringApplication(java.lang.Class<?>...)

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}

org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // 就在这里这是关键方法
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

点到这里我们就发现,SpringApplication初始化的时候,会使用SpringFactoriesLoader来加载ApplicationContextInitializer

来我们来看看SpringFactoriesLoader是怎么加载的,点进去看看

org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
      Enumeration<URL> urls = (classLoader != null ?
            // SpringFactoriesLoader#FACTORIES_RESOURCE_LOCATION
                               // = "META-INF/spring.factories"
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryClassName = ((String) entry.getKey()).trim();
            for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryClassName, factoryName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

看到这里我们知道了SpringFactoriesLoader他会读取/META-INF/spring.factories的配置

读取到配置后我们回到org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class, java.lang.Class<?>[], java.lang.Object...)里的org.springframework.boot.SpringApplication#createSpringFactoriesInstances进去看一看

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
      ClassLoader classLoader, Object[] args, Set<String> names) {
   List<T> instances = new ArrayList<>(names.size());
   for (String name : names) {
      try {
         Class<?> instanceClass = ClassUtils.forName(name, classLoader);
         Assert.isAssignable(type, instanceClass);
         Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
          // 实例化系统初始化器是在这里做的啊
         T instance = (T) BeanUtils.instantiateClass(constructor, args);
         instances.add(instance);
      }
      catch (Throwable ex) {
         throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
      }
   }
   return instances;
}

发现它会在这里实例化系统初始化器

然后又回去看看org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class, java.lang.Class<?>[], java.lang.Object...)看到这个方法就是给系统初始化器拍一个序ok了~

org.springframework.core.annotation.AnnotationAwareOrderComparator#sort(java.util.List<?>)

拿到排过序的Initializer后就把其放在

org.springframework.boot.SpringApplication#setInitializers

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
   this.initializers = new ArrayList<>();
   this.initializers.addAll(initializers);
}

至此org.springframework.boot.SpringApplication#initializers完成赋值

第二种,直接硬编码设置

org.springframework.boot.SpringApplication#addInitializers

public void addInitializers(ApplicationContextInitializer<?>... initializers) {
   this.initializers.addAll(Arrays.asList(initializers));
}

这太好理解了,就是直接把org.springframework.boot.SpringApplication#initializers硬编码赋值

第三种方式,在application.properties中配置

这个的话我们要关注org.springframework.boot.context.config.DelegatingApplicationContextInitializer这个类

分析它的org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize

public void initialize(ConfigurableApplicationContext context) {
   ConfigurableEnvironment environment = context.getEnvironment();
   List<Class<?>> initializerClasses = getInitializerClasses(environment);
   if (!initializerClasses.isEmpty()) {
      applyInitializerClasses(context, initializerClasses);
   }
}

点进来org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
   // 我们要到它这里会从环境变量中读取配置
   //DelegatingApplicationContextInitializer#PROPERTY_NAME
   //   = "context.initializer.classes"
   String classNames = env.getProperty(PROPERTY_NAME);
   List<Class<?>> classes = new ArrayList<>();
   if (StringUtils.hasLength(classNames)) {
      for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
         classes.add(getInitializerClass(className));
      }
   }
   return classes;
}

所以它是通过DelegatingApplicationContextInitializer的初始化方法找到的

其他的初始化逻辑和SpringFactoriesLoader的方式相似,这里感兴趣可以自己点进去看看

public class DelegatingApplicationContextInitializer
      implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

   // NOTE: Similar to org.springframework.web.context.ContextLoader

   private static final String PROPERTY_NAME = "context.initializer.classes";

   private int order = 0;

我们还可以看到这个初始化器的order=0所以它最先加载,这也是为什么thridInitializer最先打印的原因了

关于系统初始化器的执行时机源码解析

一步步点进去

org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

org.springframework.boot.SpringApplication#run(java.lang.String...)

public ConfigurableApplicationContext run(String... args) {
   // .....
      // 找到了在这里,点进去
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 这个是传说中的刷新上下文方法
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      
   // ... 
   
   return context;
}

org.springframework.boot.SpringApplication#prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   // 找到了在这里,点进去
   applyInitializers(context);
   
   // ...
}

org.springframework.boot.SpringApplication#applyInitializers

protected void applyInitializers(ConfigurableApplicationContext context) {
   // 遍历调用初始化器
   for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
            ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

大致调用流程图