我们这里详细讲解下SpringApplication的注解@SpringBootApplication,理解了该注解就能理解SpringBoot的自动配置原理。
话不多说直接上代码:
@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 {
通过上面的代码可以看到其实主要的注解有三个
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponenmentScan
1. @SpringBootConfiguration
代码如下:
/**
* Indicates that a class provides Spring Boot application
* {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
* standard {@code @Configuration} annotation so that configuration can be found
* automatically (for example in tests).
* <p>
* Application should only ever include <em>one</em> {@code @SpringBootConfiguration} and
* most idiomatic Spring Boot applications will inherit it from
* {@code @SpringBootApplication}.
*
* @author Phillip Webb
* @since 1.4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
通过上面的代码可以看到其实最主要的就是@Configuration注解,该注解的主要作用其实就是表示了其类可以当做是SpringIoc容器的配置类。
比如:
@Configuration
public class FirstConfiguration{
@Bean
public FirstService firstService(){
return new FirstServiceImpl(SecondService());
}
@Bean
public SecondService secondService(){
return new SecondServiceImpl();
}
}
上面的Configuration类相当于就是:
<bean id="firstService" class="XXXXXXX">
<property name="secondService" ref="secondService">
</bean>
<bean id="secondService" class="XXXXXX">
</bean>
2. @ComponentScan
- @ComponentScan的功能其实是自动扫描并加载符合条件的组件,比如加载类似@Component, @Service, @Controller的注解等,将这些注解所表示的类加载到IOC容器中,由Spring对其进行管理。
- 我们可以通过basePackages等属性来进行细粒度的制定自动扫描类的范围。如果不对其进行制定,我们默认会进行扫描所在类的包,以及其所有子包
所以我们默认写SpringBoot的启动类的时候会写到root package下面,这样就可以根据默认策略扫描所有需要定义的类。我们可以同时配置多个@ComponentScan注解
3. @EnableAutoConfiguration
该类是一个重点的注解,该注解主要实现了SpringBoot的自动配置功能。该注解同时也是扩展SpringBoot的关键之处。
上代码:
/**
* Enable auto-configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need. Auto-configuration classes are usually
* applied based on your classpath and what beans you have defined. For example, If you
* have {@code tomcat-embedded.jar} on your classpath you are likely to want a
* {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own
* {@link EmbeddedServletContainerFactory} bean).
* <p>
* When using {@link SpringBootApplication}, the auto-configuration of the context is
* automatically enabled and adding this annotation has therefore no additional effect.
* <p>
* Auto-configuration tries to be as intelligent as possible and will back-away as you
* define more of your own configuration. You can always manually {@link #exclude()} any
* configuration that you never want to apply (use {@link #excludeName()} if you don't
* have access to them). You can also exclude them via the
* {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
* after user-defined beans have been registered.
* <p>
* The package of the class that is annotated with {@code @EnableAutoConfiguration},
* usually via {@code @SpringBootApplication}, has specific significance and is often used
* as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
* It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
* not using {@code @SpringBootApplication}) in a root package so that all sub-packages
* and classes can be searched.
* <p>
* Auto-configuration classes are regular Spring {@link Configuration} beans. They are
* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
* often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
*
* @author Phillip Webb
* @author Stephane Nicoll
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
* @see AutoConfigureAfter
* @see SpringBootApplication
*/
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
这里我们关注两个注解
- @AutoConfigurationPackage
- @Import(EnableAutoConfigurationImportSelector.class)
我们对其分开讲解:
- 讲解@AutoConfigurationPackage,该注解其实是自动扫描的关键。这里不做重要的描述,该注解再继续跟踪下去可以发现他会自动加载当前所在类的包以及子包下的所有的注解。
- 这里我们着重看的是 @Import(EnableAutoConfigurationImportSelector.class).
(1)我们跟踪进类. EnableAutoConfigurationImportSelector.class,代码如下:
/**
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}. This class can also be subclassed if a custom variant of
* {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed.
*
* @deprecated as of 1.5 in favor of {@link AutoConfigurationImportSelector}
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.3.0
* @see EnableAutoConfiguration
*/
@Deprecated
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
观察上面的代码,发现其类的父类是AutoConfigurationImportSelector ,追踪代码进入到该类中。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
.....
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> 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);
}
}
......
观察上面的代码,主要看这一行代码:
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> 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;
}
从上面的方法中可以看到我们是通过loadFactoryNames() 方法加载SpringBoot默认约定的配置的类。
通过上面的代码可以看到我们默认加载的是META-INF/spring.factories路径下的配置文件。将所有的AutoConfiguration的配置加载进去。
SpringBoot如何启动
其实这里我们主要要了解的是SpringBoot如何调用其run方法。他的具体的流程是怎么样的呢?
- 如果我们使用的是SpringApplication的静态的方法run(),那么在这个方法里面实例化了一个SpringApplication对象,调用的是该对类的有参构造。
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param source the source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
在SpringApplication实例初始化的时候,他会提前做几件事。
(1)根据classpath里面是否存在某个特征类(Servlet,ConfigurableWebApplicationContext)来决定是否应该创建一个供web应用使用的ApplicationContext类型。
(2)使用SpringFactoriesLoader在应用的ClassPath中查找并加载所有可用的ApplicationContextInitializer。
(3)使用SpringFactoriesLoader在应用的classpath中查找并夹杂爱所有可用的ApplicationListener。初始化以上的配置之后,设置main方法的定义类。
- SpringApplication实例初始化完成并且完成设置后,开始执行run方法,首先遍历执行所有通过SpringFactoriesLoader查找到并加载的SpringApplicationRunListener,调用他们的starting()方法。
- 准备并且配置当前Spring Boot应用程序使用的Environment(包括PropertySource和Profiles)
- 遍历执行所有SpringApplicationRunListener的environmentPrepared()的方法,比如说创建ApplicationContext
- 判断SpringApplication的bannerMode,是CONSOLE则输出banner到控制台,是OFF则不打印,是LOG则输出到日志文件中。
- 判断是否设置applicationContext属性,如果有,则实例化该class;如果没有,则判断是否是web环境,如果是DEFAULT_WEB_CONTEXT_CLASS,则实例化该常量所对应的AnnotationWebApplication,否则实例化该常量所对应的AnnotationConfigApplication类。
- 将之前准备好的environment配置给当前的Application
- 将beanNameGenerator,resourceLoader配置给当前的ApplicationContext
- 创建好ApplicationContext之后,SpringApplication会通过SpringFactoriesLoader查找classpath中所有可用的ApplicationInitializer,遍历加载这些ApplicationInitializer的initialize方法来对当前的ApplicationContext做进一步的处理。
- 遍历执行所有的SpringApplicationRunListener的contextPrepared方法
- 为BeanDefinitionLoader配置beanNameGenerator,resourceLoader,environment(之前是给ApplicationContext配置),并且通过@EnableAutoConfiguration获取所有的配置,以及其余IOC容器配置到当前已经准备完毕的ApplicationContext
- 遍历执行所有的SpringApplicationRunListener的contextLoad方法
- 调用ApplicationContext的refresh方法,完成IOC容器可用的最后工作,并且为Runtime.getRuntime()添加ShutdownHook方法(该方法是一个钩子函数,进行回调),在JVM停止的时候回调、
- 查找当前ApplicationContext中是否注册了ApplicationRunner或哦这CommandLineRunner,如果是,则遍历执行他们。
- 正常情况下,遍历执行SpringApplicationRunListener的finished方法。
截止到目前为止SpringBoot程序就已经正常执行了。并完成了相关的自动注入。