配置pom.xml
在其中添加如下依赖:
org.springframework.boot
spring-boot-starter-web
@SpringBootApplication
public class SBApplication {
public static void main(String args[]) throws Exception{
SpringApplication.run(SBApplication.class, args);
}
}
@RestController
public class TestController {
@RequestMapping("/index")
public String index(){
return "hello world";
}
}
server.port=7668
...................
2017-12-30 00:50:35.873 INFO 14033 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-12-30 00:50:35.931 INFO 14033 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 7668 (http)
2017-12-30 00:50:35.937 INFO 14033 --- [ main] springbootext.SBApplication : Started SBApplication in 3.752 seconds (JVM running for 4.089)
http://localhost:7668/index
@SpringBootApplication
public class SBConfiguration {
}
public class SBApplication {
public static void main(String args[]) throws Exception{
SpringApplication.run(SBConfiguration.class, args);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...........................
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
return metadata.isAnnotated(Configuration.class.getName());
}
以及
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
@EnableAutoConfiguration
@ComponentScan
public class SBConfiguration {
}
并不会有任何影响。
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
..........
}
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
protected List getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
..........................
@EnableAutoConfiguration
@ComponentScan(basePackages={"springbootext", "anotherpackage"})
public class SBConfiguration{
}
new SpringApplication(sources).run(args);
public class SBApplication {
public static void main(String args[]) throws Exception{
SpringApplication sa = new SpringApplication(SBConfiguration.class);
sa.run(args);
}
}
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();
}
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private Collection extends T> getSpringFactoriesInstances(Class type,
Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set names = new LinkedHashSet(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
1. 通过SpringFactoriesLoader来获取定义在spring.factories中的SpringApplicationRunListener,SpringBoot框架默认只定义了一个EventPublishingRunListener,其中维护了一个SimpleApplicationEventMulticaster,并将前面initialize阶段中获得的ApplicationListeners注册进去。然后调用其start方法,给所有的SpringApplicationRunListener发送一个start事件,然后EventPublishingRunListener给注册在其中的所有ApplicationListener发送ApplicationStartedEvent。
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
此处包含了两个扩展点,我们可以自定义SpringApplicationRunListener以扩展SpringBoot程序启动过程,也可以自定义ApplicationListener以扩展EventPublishingRunListener。我们后面可以看到在启动的不同阶段,会发送不同的事件给SpringApplicationRunListeners和ApplicationListeners。
2. 将参数包装为ApplicationArguments,DefaultApplicationArguments是用来维护命令行参数的,例如可以方便的将命令行参数中的options和non options区分开,以及获得某option的值等。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
prepareEnvironment方法的代码如下:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
在getOrCreateEnvironment()方法中通过4.1初始化中判断的是否为web应用创建一个StandardServletEnvironment或StandardEnvironment。然后执行configureEnvironment函数:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
其中分别配置属性源(propertySource)以及轮廓(profile),关于Environment中的属性来源分散在启动的若干个阶段,并且按照特定的优先级顺序,也就是说一个属性值可以在不同的地方配置,但是优先级高的值会覆盖优先级低的值。具体属性取值顺序我们将专门在下面详细介绍。 listeners.environmentPrepared(environment);
此时各SpringApplicationRunListener或ApplicationListener已经可以得到environment了, 可以在此处对environment进行一些额外的处理。
4. 根据前面判断的是web应用还是普通应用决定创建什么类型的ApplicationContext:
context = createApplicationContext();
createApplicationContext方法代码如下:
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
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);
}
关于ApplicationContext多说两句:ApplicationContext用于扩展BeanFactory中的功能,ApplicationContext拥有BeanFactory对于Bean的管理维护的所有功能,并且提供了更多的扩展功能,实际上ApplicationContext的实现在内部持有一个BeanFactory的实现来完成BeanFactory的工作。AbstractApplicationContext是ApplicationContext的第一个抽象实现类,其中使用模板方法模式定义了springcontext的核心扩展流程refresh,并提供几个抽象函数供具体子类去实现。其直接子类有AbstractRefreshableApplicationContext和GenericApplicationContext两种。5. 借助SpringFactoriesLoader获得spring.factories中注册的FailureAnalyzers以供当运行过程中出现异常时进行分析:
analyzers = new FailureAnalyzers(context);
其中又用到了SpringFactoriesLoader扩展方案:
List analyzerNames = SpringFactoriesLoader
.loadFactoryNames(FailureAnalyzer.class, classLoader);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
此处就不展开了,在下一节专门对这个函数进行分析。
7. 调用ApplicationContext的refresh函数,开启spring context的核心流程,就是根据配置加载bean(spring beans核心功能)以及在各个时机开放的不同扩展机制(spring context):
refreshContext(context);
这个过程在此不展开了,后续会写关于spring context的文章专门介绍。
8. 获取所有的ApplicationRunner和CommandLineRunner并执行:
afterRefresh(context, applicationArguments);
此时由于context已经refresh完毕,因此bean都已经加载完毕了。所以这两个类型的runner都是直接从context中获取的:
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
两者的执行时机是完全一样的,唯一的区别在于一个接受ApplicationArguments,一个接受String[]类型的原始命令行参数。而ApplicationArguments也只是对原始命令行参数的一个封装,因此本质上是一样的。9. spring-context refresh过程完毕后执行所有SpringApplicationRunListeners的finished函数,然后EventPublishingRunListener给所有注册其中的ApplicationListeners发送一个“应用启动完毕”ApplicationReadyEvent事件:
listeners.finished(context, null);
10. 当运行时出现异常时,向context发送退出码事件ExitCodeEvent,供其内部listener执行退出前的操作;并使用前面第5步获得的analyzers来打印可能的原因:
handleRunFailure(context, listeners, analyzers, ex);
另外,就算运行异常,也会向SpringApplication中的listeners发送“应用启动完毕”的事件,代码如下:
private void handleRunFailure(ConfigurableApplicationContext context,
SpringApplicationRunListeners listeners, FailureAnalyzers analyzers,
Throwable exception) {
try {
try {
handleExitCode(context, exception);
listeners.finished(context, exception);
}
finally {
reportFailure(analyzers, exception);
if (context != null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
此时,EventPublishingRunListener发送给注册其中的ApplicationListeners的事件成了”应用启动异常“ApplicationFailedEvent。至此,SpringApplication的run函数,也就是SpringBoot应用的启动过程就执行完毕了。可以看出,SpringBoot的启动过程是对Spring context启动过程的扩展,在其中定义了若干的扩展点并提供了不同的扩展机制。并提供了默认配置,我们可以什么都不配,也可以进行功能非常强大的配置和扩展。这也正是SpringBoot的优势所在。
1. 将environment设置到context中:
context.setEnvironment(environment);
environment是我们在run过程的第3步创建的。2. 对ApplicationContext应用相关的后处理,子类可以重写该方法来添加任意的后处理功能:
postProcessApplicationContext(context);
该方法代码如下:
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.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设置了beanNameGenerator,则将其注册为singleton类型的bean,并命名为:
org.springframework.context.annotation.internalConfigurationBeanNameGenerator
另外,若SpringApplication设置了resourceLoader,则设置进context中。3. 对initialize阶段得到的通过spring.factories注册进来的所有ApplicationContextInitializer,逐个执行其initialize方法来修改context,并在执行之前对其进行校验:
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);
}
}
此处定义了一个扩展点,可以自定义并通过spring.factories注册ApplicationContextInitializer,这些ApplicationContextInitializer可在ApplicationContext准备完毕后对其进行维护修改,例如可以改变其定义的activeProfiles以改变应用环境。4. 执行所有SpringApplicationRunListeners的contextPrepared函数,注意EventPublishingRunListener并没有给所有注册其中的ApplicationListeners发送对应的事件:
listeners.contextPrepared(context);
此时的listeners可以获得context作为参数,从而对context进行修改。5. 将applicationArguments注册进context.getBeanFactory()中,名字为"SpringApplicationArguments"、若printBanner不为空,将printBanner注册到context.getBeanFactory()中,名字为"SpringBootBanner":
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
Set sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
load函数中会创建一个BeanDefinitionLoader并设置其beanNameGenerator, resourceLoader, environment等属性,然后委托其执行具体的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();
}
其中对于每一个source根据其类型不同执行不同的load逻辑:class, Resource, Package, CharSequence等。将解析出来的所有bean的BeanDefinition注册到BeanDefinitionRegistry中(注意,只是source本身,并不包括其内部定义的@Bean方法):
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class>) {
return load((Class>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
由于我们的source是class类,所以load某一个具体source的行为是委托给了AnnotatedBeanDefinitionReader的register方法:
public void register(Class>... annotatedClasses) {
for (Class> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
}
此处已是spring context的功能了,将通过注释定义的Configuration类的BeanDefinition注册到BeanDefinitionRegistry中。(此时尚不解析Configuration类内部定义的@Bean方法)
listeners.contextLoaded(context);
其中调用到EventPublishingRunListener的contextLoaded函数:
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void finished(ConfigurableApplicationContext context, Throwable exception);
}
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
Logger logger = LoggerFactory.getLogger(MySpringApplicationRunListener.class);
private final SpringApplication application;
private final String[] args;
public MySpringApplicationRunListener(SpringApplication application, String[] args){
this.application = application;
this.args = args;
}
@Override
public void starting() {
System.out.println("===============starting");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
environment.setActiveProfiles("Develop");
logger.info("===============environmentPrepared");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
logger.info("===============contextPrepared");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
logger.info("===============contextLoaded");
}
@Override
public void finished(ConfigurableApplicationContext context,
Throwable exception) {
logger.info("===============finished");
}
}
public MySpringApplicationRunListener(SpringApplication application, String[] args)
然后在META-INF/spring.factories中添加如下配置:
org.springframework.boot.SpringApplicationRunListener=\
springbootext.runlistener.MySpringApplicationRunListener
===============starting
2017-12-31 02:28:39.647 INFO 16329 --- [ main] s.r.MySpringApplicationRunListener : ===============environmentPrepared
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.8.RELEASE)
2017-12-31 02:28:39.768 INFO 16329 --- [ main] s.r.MySpringApplicationRunListener : ===============contextPrepared
2017-12-31 02:28:39.772 INFO 16329 --- [ main] springbootext.SBApplication : Starting SBApplication on wangd-ThinkPad-T450 with PID 16329 (/home/wangd/work/test/springbootext/target/classes started by wangd in /home/wangd/work/test/springbootext)
2017-12-31 02:28:39.773 INFO 16329 --- [ main] springbootext.SBApplication : The following profiles are active: Develop
2017-12-31 02:28:39.824 INFO 16329 --- [ main] s.r.MySpringApplicationRunListener : ===============contextLoaded
2017-12-31 02:28:39.828 INFO 16329 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@58a90037: startup date [Sun Dec 31 02:28:39 CST 2017]; root of context hierarchy
2017-12-31 02:28:41.654 INFO 16329 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 7668 (http)
2017-12-31 02:28:41.672 INFO 16329 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2017-12-31 02:28:41.673 INFO 16329 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
2017-12-31 02:28:41.802 INFO 16329 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-12-31 02:28:41.802 INFO 16329 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1978 ms
2017-12-31 02:28:41.980 INFO 16329 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-12-31 02:28:41.985 INFO 16329 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-12-31 02:28:41.986 INFO 16329 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-12-31 02:28:41.987 INFO 16329 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-12-31 02:28:41.987 INFO 16329 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-12-31 02:28:42.390 INFO 16329 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@58a90037: startup date [Sun Dec 31 02:28:39 CST 2017]; root of context hierarchy
2017-12-31 02:28:42.476 INFO 16329 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/index]}" onto public java.lang.String springbootext.controller.TestController.index()
2017-12-31 02:28:42.482 INFO 16329 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-12-31 02:28:42.483 INFO 16329 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-12-31 02:28:42.526 INFO 16329 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-12-31 02:28:42.526 INFO 16329 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-12-31 02:28:42.581 INFO 16329 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-12-31 02:28:42.786 INFO 16329 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-12-31 02:28:42.852 INFO 16329 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 7668 (http)
2017-12-31 02:28:42.858 INFO 16329 --- [ main] s.r.MySpringApplicationRunListener : ===============finished
2017-12-31 02:28:42.858 INFO 16329 --- [ main] springbootext.SBApplication : Started SBApplication in 3.469 seconds (JVM running for 3.776)
可以看到,在启动过程的不同时机,MySpringApplicationRunListener会执行不同的方法,并得到与当前时机息息相关的参数,因此可以在不同时机执行一些不同的操作。
private final ApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public void started() {
this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(
this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(
this.application, this.args, context));
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
// Listeners have been registered to the application context so we should
// use it at this point
context.publishEvent(getFinishedEvent(context, exception));
}
springApplication.addListeners(new MyApplicationListener());
或者在spring.factories中添加:
org.springframework.context.ApplicationListener=\
springbootext.listener.MyApplicationListener
来将其注册进EventPublishingRunListener中,参与SpringBoot启动过程生命周期。
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);
}
}
其中getInitializers()得到的就是在initialize阶段借助SpringFactoriesLoader工具来获取的所有initializers,详情请翻阅4.1小节。springApplication.addInitializers(new MyInitializer());
或者在spring.factories中添加:
org.springframework.context.ApplicationContextInitializer=\
springbootext.initializer.MyInitializer
来将其注册进SpringBoot的启动流程中。
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List runners = new ArrayList();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
这些runner无法感知applicationContext或者是environment等,只能获得启动应用时候的命令行参数。spring-boot-starter
spring-boot-starter-logging
spring-boot-starter-web
spring-boot-starter-jdbc
spring-boot-starter-aop
spring-boot-starter-secutiry
......
等等,有数十个之多。由于这是一篇介绍实现原理而非介绍使用教程的文章,因此不会过多的介绍各个starter的具体功能或用法,想查看具体教程的同学请参考https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples。
4.0.0
org.springframework.boot
spring-boot-starters
1.5.8.RELEASE
spring-boot-starter-web
Spring Boot Web Starter
Starter for building web, including RESTful, applications using Spring
MVC. Uses Tomcat as the default embedded container
http://projects.spring.io/spring-boot/
Pivotal Software, Inc.
http://www.spring.io
${basedir}/../..
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-tomcat
org.hibernate
hibernate-validator
com.fasterxml.jackson.core
jackson-databind
org.springframework
spring-web
org.springframework
spring-webmvc
里面定义了一个web应用需要依赖的类库,其中通过spring-boot-starters来管理具体的版本,这些版本经过了精心挑选配置,可以认为是经过了充分测试可避免冲突的。org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
其中WebMvcAutoConfiguration就是对于web应用环境的自动配置主类,其注释信息如下:
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class WebMvcAutoConfiguration{
...............
}
内部注册了一系列必要的ViewResolver、Converter、Formatter、Filter等等。
其中@AutoConfigureAfter代表需要在DispatcherServletAutoConfiguration之后加载,因此,在此之前需要先加载DispatcherServletAutoConfiguration,其注释信息如下:@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
......
}
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
.........
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
public void customize(ConfigurableEmbeddedServletContainer container) {
if (getPort() != null) {
container.setPort(getPort());
}
if (getAddress() != null) {
container.setAddress(getAddress());
}
if (getContextPath() != null) {
container.setContextPath(getContextPath());
}
if (getDisplayName() != null) {
container.setDisplayName(getDisplayName());
}
if (getSession().getTimeout() != null) {
container.setSessionTimeout(getSession().getTimeout());
}
container.setPersistSession(getSession().isPersistent());
container.setSessionStoreDir(getSession().getStoreDir());
if (getSsl() != null) {
container.setSsl(getSsl());
}
if (getJspServlet() != null) {
container.setJspServlet(getJspServlet());
}
if (getCompression() != null) {
container.setCompression(getCompression());
}
container.setServerHeader(getServerHeader());
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat().customizeTomcat(this,
(TomcatEmbeddedServletContainerFactory) container);
}
if (container instanceof JettyEmbeddedServletContainerFactory) {
getJetty().customizeJetty(this,
(JettyEmbeddedServletContainerFactory) container);
}
if (container instanceof UndertowEmbeddedServletContainerFactory) {
getUndertow().customizeUndertow(this,
(UndertowEmbeddedServletContainerFactory) container);
}
container.addInitializers(new SessionConfiguringInitializer(this.session));
container.addInitializers(new InitParameterConfiguringServletContextInitializer(
getContextParameters()));
}
也就是说,该customizer负责将配置的属性设置进container中。
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
for (ErrorPageRegistrar registrar : getRegistrars()) {
registrar.registerErrorPages(registry);
}
}
默认注册了一个ErrorPageCustomizer,使用属性中的servletPath与error.path一起构建一个ErrorPage并注册到ConfigurableEmbeddedServletContainer中:
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
+ this.properties.getError().getPath());
errorPageRegistry.addErrorPages(errorPage);
}
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
等等,它们都在一定条件下起作用,并各自实现特定的配置。org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
mypackage1.MyConfiguration1,\
mypackage2.MyConfiguration2,\
.........
来应用Spring的内部扩展机制注册Configuration类。example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
其中example.MyService是接口的全限定名,而MyServiceImpl1和MyServiceImpl2是其实现类。# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
.......
这些配置,在启动时通过启动类上标注的@EnableAutoConfiguration注释中Import的AutoConfigurationImportSelector,来委托SpringFactoriesLoader加载进IOC容器,并在满足各自配置的@Condition条件时生效。
1. environment.getProperty(String key)
任何我们可以获得Environment对象的地方,都可以直接使用这个函数来获得key所定义的属性值。key是类似home.me.name或server.port等等这样的字符串,例如:environment.getProperty("home.me.name");
environment.getProperty("server.port");
2. environment.resolvePlaceholders(String placeholder)
同上,能获得environment对象的地方,都可以使用该函数。所不同的是,其参数为placeholder,也就是${home.me.name}或${server.port}并且支持嵌套表达,例如:environment.resolverPlaceholders("${home.me.name}");
environment.resolverPlaceholders("${home.me.${test.name}}");
3. @Value
当我们在一个Bean中的某属性上定义@Value("home.me.name")时,IOC容器在创建这个Bean的时候就会将对应的属性值注入(Inject)进去,例如:@Component
public class SBConfiguration {
@Value("${home.me.name:hello}")
String name;
......
}
4. @ConfigurationProperties("home.me")
当我们在一个配置类上标注@ConfigurationProperties时,IOC容器会在创建这个配置类时自动为其注入其字段对应的属性(若存在的话)。这是SpringBoot新引入的获取机制。@Configuration
@ConfigurationProperties("home.me")
public class SBConfiguration {
String name;
......
}
protected T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}
从8.2属性获取及绑定过程可知,属性源优先级顺序其实就取决于属性源在environment中注册完毕后的最终顺序,SpringBoot在启动阶段控制这这个属性源的加载及排序,我们跟踪一下启动过程:
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
StandardServletEnvironment的父类是StandardEnvironment,其customizePropertySources方法代码如下:
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
getSystemEnvironment()));
}
此时propertySources的顺序为:
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
其中configurePropertySources用于配置propertySources,为environment增加defaultProperties和commandLineArgs:
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource(
name + "-" + args.hashCode(), args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
区别是defaultProperties加到末尾,而commandLineArgs添加到第一个。此时propertySources的顺序如下:
commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
defaultProperties
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment, application);
}
其中addPropertySources方法首先将RandomValuePropertySource(名为random)注册进environment:
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
RandomValuePropertySource加载的位置如下:
public static void addToEnvironment(ConfigurableEnvironment environment) {
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
logger.trace("RandomValuePropertySource add to Environment");
}
可看到是加在systemEnvironment之后。private void addConfigurationProperties(
ConfigurationPropertySources configurationSources) {
MutablePropertySources existingSources = this.environment
.getPropertySources();
if (existingSources.contains(DEFAULT_PROPERTIES)) {
existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);
}
else {
existingSources.addLast(configurationSources);
}
}
如果存在defaultProperties,则将该applicationConfigurationProperties添加到defaultProperties之前;否则添加到existingSources的最末尾。如此一来,propertySources的顺序如下:commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
random
applicationConfigurationProperties
defaultProperties
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
针对每一个打了PropertySource注释的配置类,执行processPropertySource方法,代码如下:
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException ex) {
// Placeholders not resolvable
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
catch (IOException ex) {
// Resource not found when trying to open it
if (ignoreResourceNotFound &&
(ex instanceof FileNotFoundException || ex instanceof UnknownHostException)) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
private void addPropertySource(PropertySource> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource> existing = propertySources.get(name);
PropertySource> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
}
else {
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
}
this.propertySourceNames.add(name);
}
commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
random
applicationConfigurationProperties
defaultProperties
ResourcePropertySource(若干个)
5. 跟着会由PropertySourceOrderingPostProcessor执行postProcessBeanFactory:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
reorderSources(this.context.getEnvironment());
}
其中对context.getEnvironment()中的propertySources进行重新排序:
private void reorderSources(ConfigurableEnvironment environment) {
ConfigurationPropertySources
.finishAndRelocate(environment.getPropertySources());
PropertySource> defaultProperties = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
}
}
finishAndRelocate函数中将名为"applicationConfigurationProperties"的ConfigurationPropertySources在原位置展开:原来将路径上的多个配置文件生成的propertySource集成成一个ConfigurationPropertySources,现在将其展开成不同的PropertySource。commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
random
PropertiesPropertySource(若干)
ResourcePropertySource(若干)
defaultProperties
经过上述的整个过程以后,environment中参数源列表(propertySources)的排序与前面文档中提到的顺序就完全一致了。