spring boot源码分析之SpringApplication
spring boot 提供了 sample 程序,学习 spring boot 之前先跑一个最简单的示例:
/* * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package sample.simple;
import sample.simple.ExitException;
import sample.simple.service.HelloWorldService;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;@SpringBootApplication
public class SampleSimpleApplication implements CommandLineRunner {@Autowired private HelloWorldService helloWorldService;</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Simple example shows how a command line spring application can execute an </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> injected bean service. Also demonstrates how you can use @Value to inject </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> command line args ('--name=whatever') or application properties</span>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run(String... args) { System.out.println(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.helloWorldService.getHelloMessage()); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (args.length > 0 && args[0].equals("exitcode"<span style="color: rgba(0, 0, 0, 1)">)) { </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ExitException(); } } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { SpringApplication application </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SpringApplication( SampleSimpleApplication.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); application.setApplicationContextClass(AnnotationConfigApplicationContext.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); SpringApplication.run(SampleSimpleApplication.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">, args); }
}
可以发现在主方法 main 里启动了一个 SpringApplication,启动方法是 run 方法。
SpringApplication 用来从 java main 方法启动一个 spring 应用,默认的启动步骤如下:
1) 创建一个合适的 ApplicationContext 实例, 这个实例取决于 classpath。
2) 注册一个 CommandLinePropertySource,以 spring 属性的形式来暴露命令行参数。
3) 刷新 ApplicationContext,加载所有的单例 bean。
4) 触发所有的命令行 CommanLineRunner 来执行 bean。
大部分场景下,可以从你的 application 的 main 方法中直接调用它的 run() 静态方法。示例如下:
@Configuration @EnableAutoConfiguration public class MyApplication {// ... Bean definitions
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
}
定制则可以这样:
public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(MyApplication.class); // ... customize app settings here app.run(args) }
springApplication 可以读取不同种类的源文件:
- 类- java 类由
AnnotatedBeanDefinitionReader加载。
Resource
- xml 资源文件由XmlBeanDefinitionReader读取
, 或者 groovy 脚本由GroovyBeanDefinitionReader读取
Package
- java 包文件由ClassPathBeanDefinitionScanner扫描读取。
CharSequence
- 字符序列可以是类名、资源文件、包名,根据不同方式加载。如果一个字符序列不可以解析程序到类,也不可以解析到资源文件,那么就认为它是一个包。
1. 初始化过程
public SpringApplication(Object... sources) {initialize(sources); } 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();}
2. 运行方法 run
/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ 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); } }
2.1 配置属性
private void configureHeadlessProperty() { System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty( SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));}
2.2 获取监听器
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args));}
2.3 启动监听器
public void started() { for (SpringApplicationRunListener listener : this.listeners) {listener.started(); } }
listener 最终会被初始化为 ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener 这几个类的对象组成的 list。
下图画出了加载的 ApplicationListener,并说明了他们的作用。
2.4 创建并刷新容器 (重点)
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); }</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.bannerMode !=<span style="color: rgba(0, 0, 0, 1)"> Banner.Mode.OFF) { printBanner(environment); } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Create, load, refresh and run the ApplicationContext</span> <span style="color: rgba(255, 0, 0, 1)">context =</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> createApplicationContext();</span> context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.logStartupInfo) { logStartupInfo(context.getParent() </span>== <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); logStartupProfileInfo(context); } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add boot specific singleton beans</span> context.getBeanFactory().registerSingleton("springApplicationArguments"<span style="color: rgba(0, 0, 0, 1)">, applicationArguments); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Load the sources</span> Set<Object> sources =<span style="color: rgba(0, 0, 0, 1)"> getSources(); Assert.notEmpty(sources, </span>"Sources must not be empty"<span style="color: rgba(0, 0, 0, 1)">); <span style="color: rgba(255, 0, 0, 1)">load(context, sources.toArray(</span></span><span style="color: rgba(255, 0, 0, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> Object[sources.size()]));</span> listeners.contextLoaded(context); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Refresh the context</span>
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
return context;
}
2.4.1 获取或者创建环境
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } if (this.webEnvironment) { return new StandardServletEnvironment();} return new StandardEnvironment();}
若是有指定环境,则返回指定的 ConfigurableEnvironment
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver
使用示例如下:
以最高搜索级别增加一个属性
ConfigurableEnvironment environment = new StandardEnvironment(); MutablePropertySources propertySources = environment.getPropertySources(); Map myMap = new HashMap(); myMap.put("xyz", "myValue"); propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
移除默认系统属性
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)
测试环境 mock 系统属性
MutablePropertySources propertySources = environment.getPropertySources(); MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue"); propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
若是 web 环境,则使用 StandardServletEnvironment
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment
它用于基于 server 相关的 web 应用,所有 web 相关 (Servlet 相关)Application 类默认初始化一个实例。
默认返回 StandardEnvironment
public class StandardEnvironment extends AbstractEnvironment
StandardEnvironment 例如非 web 环境等
2.4.2 配置环境
/** * Template method delegating to * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order. * Override this method for complete control over Environment customization, or one of * the above for fine-grained control over property sources or profiles, respectively. * @param environment this application's environment * @param args arguments passed to the {@code run} method * @see #configureProfiles(ConfigurableEnvironment, String[]) * @see #configurePropertySources(ConfigurableEnvironment, String[]) */ protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {configurePropertySources(environment, args); configureProfiles(environment, args); }
2.4.3 创建 ApplicationContext
/** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) */ 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); }
2.4.4 加载 bean 到 ApplicationContext
/** * Load beans into the application context. * @param context the context to load beans into * @param sources the sources to load */ protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug( "Loading source" + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator);} if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader);} if (this.environment != null) { loader.setEnvironment(this.environment);} loader.load();}
2.4.5 刷新 ApplicationContext
/** * Refresh the underlying {@link ApplicationContext}. * @param applicationContext the application context to refresh */ protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
小结:
上面仅仅是入门,若有谬误,请指正。后面随着学习的深入会修改。
参考文献:
【1】http://www.cnblogs.com/java-zhao/p/5540309.html
【2】http://zhaox.github.io/java/2016/03/22/spring-boot-start-flow