在Spring Boot框架中,最先接触的应该就是SpringApplication这个类。所以源码分析也先从这个类开始。
SpringApplication用来从Java的main方法中启动Spring Boot应用。他的行为包括:
- 创建适当的应用上下文实例
- 对应CommandLinePropertySource和Spring的参数
- 初始化应用上下文,并且加载单例Bean
- 触发CommandLineRunner
其中,我们先看初始化方法。SpringApplication一共包含两个构造方法,两个构造方法均会调用initialize函数。
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
这个方法中,首先deduceWebEnvironment检验网络环境。具体方法是检查默认类加载器是否加载过Servlet和ConfigurableWebApplicationContext这两个类。如果加载过,那么即为Web应用。
第二步,getSpringFactoriesInstances方法接受ApplicationContextInitializer作为参数。然后一直调用到getSpringFactoriesInstances方法。
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
在getSpringFactoriesInstances方法内部,首先会调用getContextClassLoader。这里对应的类加载器为加载器AppClassLoader,负责加载java.class.path下的文件,其父加载器为ext类加载器,关系是:null(BootstrapClassLoader非java实现所以为空)->ext class loader->app class loader。至于为什使用ContextClassLoader,Spring boot中提到:
Launched applications should use Thread.getContextClassLoader()
when loading classes(most libraries and frameworks will do this by default). Trying to load nested jarclasses viaClassLoader.getSystemClassLoader()
will fail.
获得classloader的方法包括:
ClassLoader.getSystemClassLoader()
Thread.getContextClassLoader()
Test.class.getClassLoader()
至于使用getContextClassLoader的具体的原因,下文提到,systemClassLoader仅仅适用于简单的程序。web应用下一定会出错:
http://www.javaworld.com/article/2077344/core-java/find-a-way-out-of-the-classloader-maze.html。
要加载的类是ApplicationContextInitializer.class, 在springframework.context包下,这一类nested jar中的类,无法用systemClassLoader。因为systemClassLoader仅仅加载CLASSPATH路径下的类。无法加载nested jar中的类。如上文所写,获取classloader的一般方法为class.getClassLoader和getContextClassLoader。
之后使用ContextClassLoader加载spring.factories并找到配置文件路径,并从中找到ApplicationContextInitializer路径然后加载。之后用相同的方法加载ApplicationListener。
最后deduceMainApplicationClass方法用来确定是否是main函数启动的应用程序。确认的方法是:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
从stack中查找是否有main方法被调用。 至此,初始化完成。此后通常可以直接通过SpringApplication类中的run方法启动应用。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
context = createAndRefreshContext(listeners, applicationArguments);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, ex);
throw new IllegalStateException(ex);
}
}
首先创建一个StopWatch用来计时。另外configureHeadlessProperty函数用来设置java.awt.headless 属性是true 还是false, java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true.
在之前,我们加载过一批ApplicationListener,这批listener共包括四个事件:
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
*/
void started();
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param environment the environment
*/
void environmentPrepared(ConfigurableEnvironment environment);
/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
* @param context the application context
*/
void contextPrepared(ConfigurableApplicationContext context);
/**
* Called once the application context has been loaded but before it has been
* refreshed.
* @param context the application context
*/
void contextLoaded(ConfigurableApplicationContext context);
/**
* Called immediately before the run method finishes.
* @param context the application context
* @param exception any run exception or null if run completed successfully.
*/
void finished(ConfigurableApplicationContext context, Throwable exception);
此时我们触发started事件。
之后是两个重要的方法:createAndRefreshContext和afterRefresh。
在createAndRefreshContext中:
private ConfigurableApplicationContext createAndRefreshContext(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableApplicationContext context;
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (isWebEnvironment(environment) && !this.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}
if (this.bannerMode != Banner.Mode.OFF) {
printBanner(environment);
}
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext();
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
listeners.contextLoaded(context);
// Refresh the context
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
return context;
}
1.根据之前获得的WebEnvironment,在这里创建StandardServletEnvironment或者StandardEnvironment,前者继承后者,并重写initPropertySources方法,主要目的是初始化propertySources。此后configureEnvironment方法负责配置environment. protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
configurePropertySources中,主要检验是否有用户自定义参数,如果有的话,并且存在commandLineRunner,那么将参数传递给commandLineRunner。
configureProfiles方法将profile保存到environment中。
全部完成后,在createAndRefreshContext方法中触发事件environmentPrepared。
到现在为止,准备工作完成。可以正式初始化应用上下文。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
这里web应用与非web应用分别使用当前类加载器来加载AnnotationConfigEmbeddedWebApplicationContext或者AnnotationConfigApplicationContext。并使用BeanUtils.instantiate调用类加载器返回的class类的默认无参构造函数获得该上下文的实例。
在Create之后,在做一下善后工作,调用postProcessApplicationContext。
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.webEnvironment) {
if (context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
if (this.beanNameGenerator != null) {
configurableContext.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
}
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context)
.setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context)
.setClassLoader(this.resourceLoader.getClassLoader());
}
}
}
如果创建SpringApplication时指定ResourceLoader,这里为上下文指定ResourceLoader。resourceLoader是Spring 中用来加载资源文件的类,默认实现是DefaultResourceLoader类, web环境下还有ServletContextResourceLoader。另外为上下文添加单例bean:internalConfigurationBeanNameGenerator。这个bean用来提供命名策略服务。
在第一步时我们加载了一个ApplicationContextInitializer的列表,这时用这里的ApplicationContextInitializer来初始化上下文。
自定义ApplicationContextInitializer的作用主要是设置active profile和自定义property sources。
至此,实例化完成。开始准备进行初始化操作。此时触发contextPrepared事件。
在初始化之前,先加载sources,然后利用sources创建BeanDefinitionLoader。
protected BeanDefinitionLoader createBeanDefinitionLoader(
BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
在 BeanDefinitionLoader中,有XmlBeanDefinitionReader,AnnotatedBeanDefinitionReader,BeanDefinitionReader三个Reader。例如XMLBeanDefinitionReader中,有特定的注册方法,用来从XML中读取Bean信息并且统一注册至DefaultBeanDefinitionDocumentReader。 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
在DefaultBeanDefinitionDocumentReader中,用来注册BeanDefinition的方法如下:
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
例如我们加载一个Bean,此时会调用到方法parseBeanDefinitions内的:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
此时调用processBeanDefinition。在此方法中,会将ele分析并注册至getReaderContext().getRegistry()。
这里走得有点远了,我们回到load方法,此时,注册器中注册了所有的Bean的信息。此时触发事件contextLoaded。
然后主角登场。refresh方法会调用Spring中应用上下文的refresh方法,在这个方法中会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作。详情可以参考Spring源码分析中的refresh方法的解释。
最后,为当前应用上下文注册shutDownHook。