在使用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加载,我们可以调用SpringApplication
的addInitializers
方法来实现:
@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
这样,我们就已经使用三种方式分贝定义好了三个初始化器了,运行一下观察一下运行结果吧。
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
方法处打上一个断点,观察一下总共有哪些初始化器。
可以发现,除了我们自定义的初始化器之外,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的初始化器加载时机和执行时机。