自动配置、起步依赖、Actuator、命令行界面(CLI)是Spring Boot最重要的4大核心特性。
spring-boot使用“习惯优于配置”(项目中存在大量的配置,此外还内置一个习惯性的配置,让你无须手动进行配置)的理念让你的项目快速运行起来。关于自动配置的源码在spring-boot-autoconfigure-xxx.jar中。
![909baac8b163ecd98fb9a0324874040b.png](https://i-blog.csdnimg.cn/blog_migrate/57ac4bb8ae84ca60e3b0dfc7989ff7df.jpeg)
一、查看项目中的自动配置报告
可通过下面方式查看当前项目中已启用(Positive matches)和未启用(Negative matches)的自动配置的报告。
1)运行jar 时增加--debug 参数,如java -jar demo_work-20181024-SNAPSHOT.jar --debug
2)在application.yml中设置属性(debug: true)
3)运行时设置jvm启动参数,如java -Ddebug -jar demo_work-20181024-SNAPSHOT.jar
常见错误:mvn打包时可能会报No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?错误。
可人为指定javac.exe的地址,示例如下:
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<executable>
C:Program FilesJavajdk1.8.0_73binjavac.exe
</executable>
</configuration>
</plugin>
</plugins>
二、Spring Boot的运行原理
2.1 @EnableAutoConfiguration
它的核心功能自动配置是由@EnableAutoConfiguration 注解提供的
@EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@Import 注解导入的配置功能, EnableAutoConfigurationlmportSelector 使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。而spring. factories 文件存在于spring-boot-autoconfigure- xxx.jar中。
![b7480d8b4469a19594ec08f880a3a67a.png](https://i-blog.csdnimg.cn/blog_migrate/2aa300028d9ad7cba5cffd32203dc20c.png)
加载自动配置的方法
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
// 扫描META-INF/spring.factories文件
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
补充:SpringBoot 的 @Import 用于将指定的类实例注入之Spring IOC Container中。 EnableAutoConfigurationlmportSelector 继承了AutoConfigurationImportSelector,间接实现 ImportSelector 注入Bean。
2.2 spring.factories文件
![53b9cfd2dbf2eef0c6382d09ad7621f5.png](https://i-blog.csdnimg.cn/blog_migrate/2c2a3dfc9edb50baa06e73e785a0f690.png)
打开上面任意一个AutoConfiguration 文件,一般都有下面的条件注解,在spring-boot-autoconfigure-xxx.jar 的org.springframwork.boot.autoconfigure.condition 包下,条件注解如下。
@ConditionalOnBean :当容器里有指定的Bean 的条件下。
@ConditionalOnClass :当类路径下有指定的类的条件下。
@ConditionalOnCloudPlatform:当指定了云平台的时候
@ConditionalOnExpression :基于SpEL 表达式作为判断条件
@ConditionalOnJava:基于JVM 版本作为判断条件。
@ConditionalOnJndi :在JNDI 存在的条件下查找指定的位置。
@ConditionalOnMissingBean :当容器里没有指定Bean 的情况下。
@ConditionalOnMissingClass :当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication :当前项目不是Web 项目的条件下。
@ConditionalOnProperty :指定的属性是否有指定的值。
@ConditionalOnResource :类路径是否有指定的值。
@ConditionalOnSingleCandidate:当指定Bean 在容器中只有一个,或者虽然有多个但是指定首选的Bean
@Conditional On WebApplication:当前项目是Web 项目的条件下。
这些注解都是组合了@Conditional 元注解,只是使用了不同的条件( Condition ),下面我分析一下@ConditionalOnWebApplication 注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
}
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends SpringBootCondition {
private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
+ "support.GenericWebApplicationContext";
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean required = metadata
.isAnnotated(ConditionalOnWebApplication.class.getName());
ConditionOutcome outcome = isWebApplication(context, metadata, required);
//最终通过ConditionOutcome.isMatch 方法返回布尔值来确定条件
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
return ConditionOutcome.match(outcome.getConditionMessage());
}
private ConditionOutcome isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadata, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(
ConditionalOnWebApplication.class, required ? "(required)" : "");
// GenericWebApplicationContext 是否在类路径中
if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
return ConditionOutcome
.noMatch(message.didNotFind("web application classes").atAll());
}
//容器里是否有名为session 的scope;
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
//当前容器的Enviroment 是否为StandardServletEnvironment
if (context.getEnvironment() instanceof StandardServletEnvironment) {
return ConditionOutcome
.match(message.foundExactly("StandardServletEnvironment"));
}
//当前的ResourceLoader 是否为WebApplicationContext ( ResourceLoader 是ApplicationContext 的顶级接口之一):
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
return ConditionOutcome.noMatch(message.because("not a web application"));
}
}
三、自动配置实例分析
@ConfigurationProperties(
prefix = "spring.http.encoding"
)
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Charset charset;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
...
}
@Configuration
@EnableConfigurationProperties({HttpEncodingProperties.class}) //1
@ConditionalOnWebApplication //6
@ConditionalOnClass({CharacterEncodingFilter.class}) //2
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
) //3
public class HttpEncodingAutoConfiguration {
private final HttpEncodingProperties properties; //4
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean //5
@ConditionalOnMissingBean({CharacterEncodingFilter.class})
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}
...
}
1)开启属性注入,通过@EnableConfigurationProperties 声明,使用@Autowired 注入;
2)当CharacterEncodingFilter 在类路径的条件下;
3)当设置spring.http.encoding=enabled 的情况下,如果没有设置则默认为true ,即条件符合;
4)像使用Java 配置的方式配置CharacterEncodingFilter 这个Bean。
5)当容器中没有这个Bean 的时候新建Beane
6)在web环境下才创建Bean
四、SpringApplication.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;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
//①
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//②
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// ③
Banner printedBanner = printBanner(environment);
// ④
context = createApplicationContext();
// ⑤
analyzers = new FailureAnalyzers(context);
// ⑥
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// ⑦
refreshContext(context);
// ⑧
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, analyzers, ex);
throw new IllegalStateException(ex);
}
}
① 通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners,通过调用starting()方法通知所有的SpringApplicationRunListeners,应用开始启动了。其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。还记得初始化流程中,SpringApplication加载了一系列ApplicationListener吗?这个启动流程中没有发现有发布事件的代码,其实都已经在SpringApplicationRunListeners这儿实现了。
简单的分析一下其实现流程,首先看下SpringApplicationRunListener的源码:
SpringApplicationRunListeners
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log,
Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<SpringApplicationRunListener>(listeners);
}
// 运行run方法时立即调用此方法,可以用于非常早期的初始化工作
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
// Environment准备好后,并且ApplicationContext创建之前调用
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
// ApplicationContext创建好后立即调用
public void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}
// ApplicationContext加载完成,在refresh之前调用
public void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
// 当run方法结束之前调用
public void finished(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFinishedListener(listener, context, exception);
}
}
private void callFinishedListener(SpringApplicationRunListener listener,
ConfigurableApplicationContext context, Throwable exception) {
try {
// 发布事件
listener.finished(context, exception);
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message == null ? "no error message" : message);
this.log.warn("Error handling failed (" + message + ")");
}
}
}
}
② 创建并配置当前应用将要使用的Environment,Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties)。不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。
总结起来,②处的两句代码,主要完成以下几件事:
- 判断Environment是否存在,不存在就创建(如果是web项目就创建StandardServletEnvironment,否则创建StandardEnvironment)
- 配置Environment:配置profile以及properties
- 调用SpringApplicationRunListener的environmentPrepared()方法,通知事件监听者:应用的Environment已经准备好
③、SpringBoot应用在启动时会输出这样的东西:
![ef8dc7bd347e1f06a7c4a722bb9fa877.png](https://i-blog.csdnimg.cn/blog_migrate/55888ac0718626a0f2f78e0c65f7b918.png)
④、根据是否是web项目,来创建不同的ApplicationContext容器。
⑤、创建一系列FailureAnalyzer,创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class,然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。
⑥、初始化ApplicationContext,主要完成以下工作:
- 将准备好的Environment设置给ApplicationContext
- 遍历调用所有的ApplicationContextInitializer的initialize()方法来对已经创建好的ApplicationContext进行进一步的处理
- 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
- 将所有的bean加载到容器中
- 调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕
⑦、调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。从名字上理解为刷新容器,那何为刷新?就是插手容器的启动
rehash()方法中一句代码
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
invokeBeanFactoryPostProcessors
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
获取到所有的BeanFactoryPostProcessor来对容器做一些额外的操作。BeanFactoryPostProcessor允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做一些额外的操作。这里的getBeanFactoryPostProcessors()方法可以获取到3个Processor:
ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
不是有那么多BeanFactoryPostProcessor的实现类,为什么这儿只有这3个?因为在初始化流程获取到的各种ApplicationContextInitializer和ApplicationListener中,只有上文3个做了类似于如下操作:
@Override
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(
new ConfigurationWarningsPostProcessor(getChecks()));
}
然后你就可以进入到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法了,这个方法除了会遍历上面的3个BeanFactoryPostProcessor处理外,还会获取类型为BeanDefinitionRegistryPostProcessor的bean:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,对应的Class为ConfigurationClassPostProcessor。
ConfigurationClassPostProcessor用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理@import注解的时候,就会调用EnableAutoConfigurationImportSelector.selectImports()来完成自动配置功能。
⑧、查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。
⑨、执行所有SpringApplicationRunListener的finished()方法。
这就是Spring Boot的整个启动流程,其核心就是在Spring容器初始化并启动的基础上加入各种扩展点,这些扩展点包括:ApplicationContextInitializer、ApplicationListener以及各种BeanFactoryPostProcessor等等。
![79cb769fd5749c8e46740e5f5d6e6386.png](https://i-blog.csdnimg.cn/blog_migrate/d8ee88a677e68ed62b0c6fb8d5dcfd33.jpeg)