SpringBoot源码学习笔记之启动流程分析
1概述
本文基于Spring Boot版本2.1.4(SringBoot最新版本2.4.2。(参考文档: https://docs.spring.io/spring-boot/docs/current/api/)
2SpringBoot启动流程分析
2.1分析入口
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
// spring-boot-2.1.4.RELEASE-sources.jar!\org\springframework\boot\SpringApplication.java
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1.选择ApplicationContentType容器类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2.初始化,得到 ApplicationContextInitializer.class的springFactories
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//3.设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4.设置主类(有main的那个类)
this.mainApplicationClass = deduceMainApplicationClass();
}
2.2容器选择deduceFromClasspath
/**
* An enumeration of possible types of web application.
*
* @author Andy Wilkinson
* @author Brian Clozel
* @since 2.0.0
*/
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
2.2setInitializers(ApplicationContextInitializer)
0 = "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer"
2 = "org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
3 = "org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"
4 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer"
5 = "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener"
2.3setListeners(ApplicationListener)
0 = "org.springframework.boot.ClearCachesApplicationListener"
1 = "org.springframework.boot.builder.ParentContextCloserApplicationListener"
2 = "org.springframework.boot.context.FileEncodingApplicationListener"
3 = "org.springframework.boot.context.config.AnsiOutputApplicationListener"
4 = "org.springframework.boot.context.config.ConfigFileApplicationListener"
5 = "org.springframework.boot.context.config.DelegatingApplicationListener"
6 = "org.springframework.boot.context.logging.ClasspathLoggingApplicationListener"
7 = "org.springframework.boot.context.logging.LoggingApplicationListener"
8 = "org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener"
9 = "org.springframework.boot.autoconfigure.BackgroundPreinitializer"
2.5run
/**
* 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;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 1.获取 spring:SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 2.这里涉及容器的创建、刷新等,类似于Spring
// org.springframework.boot.web.servlet.context.
//AnnotationConfigServletWebServerApplicationContext@25ce9dc4, started on Thu Jan 01 08:00:00 CST 1970
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
3源码细节
3.1getSpringFactoriesInstances
// spring-boot-2.1.4.RELEASE-sources.jar!\org\springframework\boot\SpringApplication.java
//根据 type和 classLoader得到springFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 1.根据 ApplicationContextInitializer
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 2.springFactories实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 3.排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
// spring-core-5.1.6.RELEASE-sources.jar!\org\springframework\core\io\support\SpringFactoriesLoader.java
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 1.得到 org.springframework.context.ApplicationContextInitializer:ConfigurableApplicationContext
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
// 0 = "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
// 1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer"
// 2 = "org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
// 3 = "org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"
// 4 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer"
// 5 = "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener"
}
// spring-core-5.1.6.RELEASE-sources.jar!\org\springframework\core\io\support\SpringFactoriesLoader.java
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// todo,cache数据来源
// 启动的时候会扫描所有jar包下META-INF/spring.factories这个文件。第二段代码的意思是将这些扫描到的文件转成Properties
// 对象,后面两个核心代码的意思就是说将加载到的Properties对象放入到缓存中
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 加载路径
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
3.2启动类上的注解
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.2.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//1.@SpringBootConfiguration这个注解说明再点进去查看详情发现就是一个@Configuration注解,这说明启动类就是一个配置类。支持Spring以JavaConfig的形式启动。
@SpringBootConfiguration
//3.springboot的核心注解
@EnableAutoConfiguration
//2.组件扫描的意思,即默认扫描当前package以及其子包下面的spring的注解,例如:@Controller、@Service、@Component等等注解。
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
3.2.1SpringBootConfiguration
3.2.2ComponentScan
3.2.3EnableAutoConfiguration
该注解是springboot的核心注解,本质都是利用了spring framework的 import功能。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//1.
@AutoConfigurationPackage
//2.
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
3.2.3.1AutoConfigurationPackage
自动配置包,最终依赖的确实@Import这个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 后置处理器,将BeanDef注入到ioc容器
// 导入类型为:Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
3.2.3.2AutoConfigurationImportSelector
自动引入组件
/**
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}. This class can also be subclassed if a custom variant of
* {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.3.0
* @see EnableAutoConfiguration
*/
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
private static final String[] NO_IMPORTS = {};
private static final Log logger = LogFactory
.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
// spring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/
// org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
// 执行时机:refresh(AbstractApplicationContext.java):invokeBeanFactoryPostProcessors(beanFactory);
/*
spring-boot-autoconfigure\2.1.4.RELEASE\spring-boot-autoconfigure-2.1.4.RELEASE.jar!\
META-INF\spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
*/
/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param autoConfigurationMetadata the auto-configuration metadata
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 这就是一种自定义SPI的实现方式的功能
// 获取所有jar中类型为:AutoConfiguration.class的子类,例如:
// 0 = "org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration"
// 1 = "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration"
// 2 = "org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration"
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
/**
* 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) {
// 加载路径:META-INF/spring.factories
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;
}
@Configuration
@AutoConfigureAfter({JmxAutoConfiguration.class})
// 条件注解,加载SpringApplicationAdminJmxAutoConfiguration的bean条件
@ConditionalOnProperty(
prefix = "spring.application.admin",
value = {"enabled"},
havingValue = "true",
matchIfMissing = false
)
public class SpringApplicationAdminJmxAutoConfiguration {
@Configuration
@ConditionalOnClass({ MBeanExporter.class })
@ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true",
matchIfMissing = true)
public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware {
4总结
SpringBoot在启动的时候,首先会构造SpringApplication对象,获取并设置Initializers和Listeners,得到main启动类。
-
接着调用run()方法,run()方法执行容器刷新,
2.1.刷新容器的时候,执行到容器的后置处理器invokeBeanFactoryPostProcessors方法时,会去解析启动类上的@SpringBootApplication复合注解。
2.2.这个注解中有一个@EnableAutoConfiguration注解,表示开启自动配置功能(实际是import :AutoConfigurationPackages.Registrar.class)
2.3.另外还有个@Import注解引入了一个AutoConfigurationImportSelector这个类(它去扫描我们所有jar包下的META-INF下的spring.factories 自动配置类文件,而从这个配置文件中取找key为EnableAutoConfiguration类的全路径的值,并加载到容器)
然后在根据这些配置类中的条件注解,来判断是否将这些配置类及关联的Bean在容器中进行实例化,
(判断的条件:主要是判断项目是否有相关jar包或工程是否引入了相关的bean)。