Spring源码解析--初始化器

414 阅读5分钟

在使用Java进行Web开发的时候,Spring应该是避不开的一个框架了,既然如此,作为经常会使用的框架,我们就不应该只是停留在会用的状态,而是可以尝试去了解一些Spring内部的运行机制,这样,当遇到一些比较罕见的问题或者比较棘手的需求时,也能够帮助我们快速地去定位问题和解决问题。

我们首先来了解一下Spring的初始化器是如何实现的,在了解之前,我们首先先看一下我们该如何去定义一个自己的初始化器,使得其可以被Spring加载。

如何实现自定义初始化器

方法一

在Spring中,要自定义一个初始化器,总共有三种方式。首先我们来看一下第一种方式:

public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("This is first initializer");
    }
}

在这种方式中,我们首先要去实现ApplicationContextInitializer接口,然后重写其中的initialize方法,在这个方法里面,我们是可以拿到Spring的上下文的,既然可以拿到上下文,那么就可以做很多事情了。

如果只是简简单单地实现这么一个接口是不够的,我们需要进行额外的配置使其可以被Spring加载,我们可以在resource目录下建立一个文件夹META-INF,然后在这个文件夹中创建一个文件spring.factories,这个文件大家应该很熟悉了,然后就可以在其中进行配置了:

org.springframework.context.ApplicationContextInitializer=com.shiyue.yan.initializers.FirstInitializer

这样,我们自定义的一个初始化器就好了,运行程序可以发现会打印出This is first initializer

方法二

然后,我们来看第二种实现方式,第二种实现方式和第一种实现方式大同小异,同样需要先实现ApplicationContextInitializer,然后重写其中的initialize方法,区别就在于配置的方式不同。

public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("This is second initializer");
    }
}

接下来,我们就是要使得这个初始化器被Spring加载,我们可以调用SpringApplicationaddInitializers方法来实现:

@SpringBootApplication
public class DesignApplication {

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

方法三

这样的话,第二个初始化器就定义好了,我们再来看下第三种实现方式,还是和前两种实现方式一样,实现ApplicationContextInitializer,然后重写其中的initialize方法,然后就是想办法使得其被Spring加载,我们可以在application.properties中进行配置:

public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("This is third initializer");
    }
}
context.initializer.classes=com.shiyue.yan.initializers.ThirdInitializer

这样,我们就已经使用三种方式分贝定义好了三个初始化器了,运行一下观察一下运行结果吧。

image.png

Spring是如何加载初始化器的

我们在运行一个Spring的项目的时候都是从SpringApplication.run(DesignApplication.class, args)开始的,所以我们首先来关注一下run方法的内部实现。

public class SpringApplication {

    public ConfigurableApplicationContext run(String... args) {
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ConfigurableApplicationContext context = null;
       Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
       configureHeadlessProperty();
       SpringApplicationRunListeners listeners = getRunListeners(args);
       listeners.starting();
       try {
          ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
          ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
          configureIgnoreBeanInfo(environment);
          Banner printedBanner = printBanner(environment);
          context = createApplicationContext();
          exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
          prepareContext(context, environment, listeners, applicationArguments, printedBanner);
          refreshContext(context);
          afterRefresh(context, applicationArguments);
          stopWatch.stop();
          if (this.logStartupInfo) {
             new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
          }
          listeners.started(context);
          callRunners(context, applicationArguments);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, listeners);
          throw new IllegalStateException(ex);
       }

       try {
          listeners.running(context);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, null);
          throw new IllegalStateException(ex);
       }
       return context;
    }
}

这个run方法内部已经很清晰地描述了整个Spring运行的过程,但是现在我们不需要关注整个的流程,我们只关注初始化器是如何被加载和执行的。在上面的运行结果中,我们可以发现初始化器的执行是在Banner打印出来之后才执行的,所以我们就目光定位到run方法的printBanner之后,我们可以观察一下prepareContext方法,看下这个方法的内部到底发生了什么。

public class SpringApplication {

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
          SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
       context.setEnvironment(environment);
       postProcessApplicationContext(context);
       applyInitializers(context);
       listeners.contextPrepared(context);
       if (this.logStartupInfo) {
          logStartupInfo(context.getParent() == null);
          logStartupProfileInfo(context);
       }
       // Add boot specific singleton beans
       ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
       beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
       if (printedBanner != null) {
          beanFactory.registerSingleton("springBootBanner", printedBanner);
       }
       if (beanFactory instanceof DefaultListableBeanFactory) {
          ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
       }
       if (this.lazyInitialization) {
          context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
       }
       // Load the sources
       Set<Object> sources = getAllSources();
       Assert.notEmpty(sources, "Sources must not be empty");
       load(context, sources.toArray(new Object[0]));
       listeners.contextLoaded(context);
    }
}

在这个方法的内部可以发现applyInitializers这个方法,不出意外,这个方法就是来执行初始化器的了,我们可以在进入这个方法一探究竟。

public class SpringApplication {

    @SuppressWarnings({ "rawtypes", "unchecked" })
    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);
       }
    }

}

我们可以看到,在这个方法的内部就是去循环遍历所有的初始化器,然后执行其中的initialize方法。

到这里,我们就知道了初始化器是什么时候执行的,那么这些初始化器又是什么时候被加载进来的呢,我们可以把目光放到其中的getInitializers方法上,这个方法就是来获取所有的初始化器的。

public class SpringApplication {
    public Set<ApplicationContextInitializer<?>> getInitializers() {
       return asUnmodifiableOrderedSet(this.initializers);
    }
}

然而,这个方法的内部只是对this.initializers进行一个排序而已,其实就是根据@Order来进行排序的,关键就在于initializers这个属性上了,那么,我们就可以观察一下SpringApplication这个对象中的initializers是什么时候设置的,经过观察,我们可以发现SpringApplication中有这么两个方法可以对initializers进行设置。

public class SpringApplication {

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

我们再回过头去看看之前自定义初始化器的第二种实现方法,就是调用的addInitializers方法来将初始化器加载到Spring中的,我们再来看下setInitializers方法,像这种setter方法一般都是在实例化这个类的时候执行的,所以我们就可以再看下SpringApplication的构造函数。

public class SpringApplication {

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

    @SuppressWarnings({ "unchecked", "rawtypes" })
    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();
    }

}

可以看到其中就有setInitializers方法,其实就是将实现了ApplicationContextInitializer接口的类进行实例化。我们如果进入到getSpringFactoriesInstances这个方法的内部的话,会追踪到这么一个方法loadSpringFactories

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

    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 ?
                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 factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                   result.add(factoryTypeName, factoryImplementationName.trim());
                }
             }
          }
          cache.put(classLoader, result);
          return result;
       }
       catch (IOException ex) {
          throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
       }
    }

}

不难发现,这个方法其实就是读取META-INF/spring.factories中的内容,并且对其进行解析。这其实就对应了自定义初始化器的第一种方式了。

那么,到目前为止就只剩下第三种方法不知道是如何被加载进去的了,那么我们可以在SpringApplication.applyInitializers方法处打上一个断点,观察一下总共有哪些初始化器。

image.png

可以发现,除了我们自定义的初始化器之外,Spring也会实现自己的初始化器,我们首先观察第一个初始化器 DelegatingApplicationContextInitializer

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;

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

    private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
       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;
    }
}

可以发现,在initialize的内部,其实就是去读取配置文件application.properties中的context.initializer.classes属性的值,然后对其实例化,调用它们的initialize方法,这也就是我们之前提到的第三种自定义初始化器的方法了。

至此,我们就了解了如何去自定义一个初始化器,以及Spring的初始化器加载时机和执行时机。