SpringBoot3.0都要出了,据说JDK最低要求17???我滴乖乖,JDK8还没整明白呢,先学习一下SpringBoot压压惊。
一、什么是SpringBoot
官方描述:
翻译:
通过Spring Boot,可以轻松地创建独立的,基于生产级别的基于Spring的应用程序,并且可以“运行”它们其实Spring Boot的设计是为了让你尽可能快的跑起来Spring应用程序并且尽可能减少你的配置文件。
二、SpringBoot的特征
①SpringBoot Starter:他将常用的依赖分组进行了整合,将其合并到一个依赖中,这样就可以一次性添加到项目的Maven或Gradle构建中。
②使编码变得简单,SpringBoot采用 JavaConfig的方式对Spring进行配置,并且提供了大量的注解,极大地提高了工作效率。
③自动配置:SpringBoot的自动配置特性利用了Spring对条件化配置的支持,合理地推测应用所需的bean并自动化配置他们。
④使部署变得简单,SpringBoot内置了三种Servlet容器,Tomcat,Jetty,undertow。我们只需要一个Java的运行环境就可以跑SpringBoot的项目,SpringBoot的项目可以打成一个jar包。
三、SpringBoot的项目搭建
学习SpringBoot的自动装配必然从Spring的主入口类开始进行。建立一个SpringBoot的测试项目SpringBootTest,并导入SpringMVC的起步依赖,观察如何进行的自动装配。
@RestController
public class TestController {
@RequestMapping("/test")
public String testMethod() {
return "测试SpringBoot";
}
}
四、SpringBoot的自动装配机制
想要分析SpringBoot的自动装配机制,必然要从SpringBoot的主启动类入手,主启动类只有一个run方法和一个@SpringBootApplication注解,我们先分析一下@SpringBootApplication注解,方便后续源码的解读。
@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 {
/**
* 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* 根据classname来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组。
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* 指定扫描包,参数是包名的字符串数组。
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* 扫描特定的包,参数类似是Class类型数组。
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
其中@Target、@Retention、@Documented、@Inherited是自定义注解的四个元注解。
@Target:表示注解的适用范围
@Retention:表示注解的生命周期
@Document:表示注解可以记录在javadoc中
@Inherited:表示子类可以继承该注解
@SpringBootConfiguration:表示该类为一个配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //实际上就是一个配置类
@Indexed
public @interface SpringBootConfiguration {
...
}
@EnableAutoConfigration:启动自动装配功能
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}
在EnableAutoConfiguration中有两个注解分别使用了@Import注解导入相应的类。@Import注解是Spring提供的注解,用来导入配置类或者一些需要前置加载的类。
@ComponentSacn:扫描路径,默认扫描该类的包及其子包
五、run方法的执行流程
简单介绍完@SpringBootApplication注解后,我们开始介绍SpringBoot的run方法。
Tips:本文采用SpringBoot2.6.3。小伙伴可以自身调整,但总体流程变化不大。可以边看边跟着一步一步Debug。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们可以看到执行run方法有两个步骤:第一步实例化SpringApplication对象,第二步执行run方法。
(1)实例化SpringApplication:
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//设置资源加载器为null
this.resourceLoader = resourceLoader;
//断言资源类不可以为null 此时使我们传递的SpringbootTestApplication.class
Assert.notNull(primarySources, "PrimarySources must not be null");
//封装到一个linkedHashSet中
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断当前的应用类型 一般都是servlet
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//初始化classpath下 META-INF/spring.factories中已配置的
//key是BootstrapRegistryInitializer的类
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
//初始化classpath下 META-INF/spring.factories中已配置的
//key是ApplicationContextInitializer的类
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//初始化classpath下 META-INF/spring.factories中已配置的
//key是ApplicationListener的类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//根据调用栈,推断出 main 方法的类名
this.mainApplicationClass = deduceMainApplicationClass();
}
在SpringApplication的构造方法中,有一个最重要的方法就是:getSpringFactoriesInstances,最终会调用loadSpringFactories拿到所有的key和value封装到一个Map<String, List<String>>容器中,通过想要的key获取所有的value,然后创建Spring的工厂实例排序后返回。
查看一下META-INF下的spring.factories文件,就是key和value组成的键值对。
我们可以Debug看一下loadSpringFactories执行结果
(2)执行run方法
/**
* 运行spring应用,并刷新一个新的ApplicationContext(Spring的上下文)
* ConfigurableApplicationContext是ApplicationContext接口的子接口。
* 在ApplicationContext基础上增加了配置上下文的工具。
*/
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
long startTime = System.nanoTime();
//创建一个默认的上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
//创建spring的上下文
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
//从META-INF/spring.factories中获取监听器
//key为 SpringApplicationRunListener.class
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//构造应用上下文环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//处理需要忽略的bean
configureIgnoreBeanInfo(environment);
//打印banner 就是SpringBoot的启动图标,可以自定义
Banner printedBanner = printBanner(environment);
//初始化应用上下文
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//刷新应用上下文前的准备阶段
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新应用上下文
refreshContext(context);
//刷新应用上下文后的扩展接口 模板模式的体现,此处是空实现
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
//发布容器启动完成事件
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
在run方法中,大致分为六步:获取并启动监听器、构造应用上下文环境、初始化应用上下文环境、刷新应用上下文文前的准备工作、刷新应用上下文、刷新应用上下文后的扩展方法。下面我们分别进行分析:
①获取并启动监听器getRunListeners。
事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
观察发现还是调用getSpringFactoriesInstances方法,对META-INF的spring.factories文件进行读取。
②构建应用上下文环境prepareEnvironment。
包括计算机的环境,Java环境,Spring的运行环境,还有咱们自己的application.yml(application.properties)配置文件
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建并配置相应的环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//根据用户配置,配置environment系统环境 就是封装一下我们传入的args configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//启动相应的监听器,其中最重要的
//EnvironmentPostProcessorApplicationListener加载配置文件
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
我们Debug来看一下效果。
没有执行listeners.environmentPrepared
执行完之后,多了我们的配置文件中的属性。
在启动监听器时,
SpringBoot2.4.0之后废弃了ConfigFileApplicationListener,3.0就会被移除。
/**
* @deprecated since 2.4.0 for removal in 3.0.0 in favor of
* {@link ConfigDataEnvironmentPostProcessor}
*/
@Deprecated
public class ConfigFileApplicationListener
然后改用了EnvironmentPostProcessorApplicationListener来加载我们的配置文件。小伙伴可以在此处进行深入Debug探究,此处就主线来说已经结束了。
③初始化应用上下文createApplicationContext。
在SpringBoot工程中,应用类型分为三种,如下代码所示。
public enum WebApplicationType {
/**
* 应用程序不是web应用,也不应该用web服务器去启动
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet web(tomcat)服务器。
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* 应用程序应作为 reactive web应用程序运行,并应启动嵌入式 reactive web服务器。
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
}
对应三种应用类型,SpringBoot项目有三种对应的应用上下文,我们以web工程为例,即其应用上下文为:
AnnotationConfigServletWebServerApplicationContext。
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
应用上下文可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一些高级功能。应用上下文对IoC容器是持有的关系。属性beanFactory就是IoC容器DefaultListableBeanFactory。所以他们之间是持有和扩展的关系。
类AnnotationConfigServletWebServerApplicationContext继承了类GenericApplicationContext,GenericApplicationContext的构造方法中实例化了beanFactory(DefaultListableBeanFactory)。所以在此处也创建了IoC容器。我们Debug观察执行createApplicationContext()方法。
执行前:
执行后:
④刷新应用上下文前的准备工作prepareContext()
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置上下文环境
context.setEnvironment(environment);
//执行后置处理器
postProcessApplicationContext(context);
//执行上下文中的ApplicationContextInitializer
applyInitializers(context);
//向各个监听器发送上下文已经准备好的事件
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//将main函数中的args参数封装成单例Bean,注册进容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
//将printedBanner也封装成单例,注册进容器
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
//设置是否允许循环引用
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
//设置是否允许覆盖bean
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
//懒加载
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件
listeners.contextLoaded(context);
}
getAllSources就是获取我们的主启动类
load(context, sources.toArray(new Object[0]))将我们的启动类注入到容器中。load方法将我们的对象封装为一个BeanDefinition后最终调用registerBeanDefinition()方法将对象注册到beanDefinitionMap中。
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();
}
load前:
load后:
⑤刷新应用上下文refreshContext(),也就是对IoC容器进行初始化。
对IoC容器进行初始化大致分为三步:BeanDefinitiond的Resource定位、BeanDefinition的载入、将BeanDefinition注册到IoC容器中。
在执行refreshContext,就是执行Spring的refresh方法,此时SpringBoot就是将Spring进行了一次封装,最终执行的还是refresh方法。
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
//刷新上下文环境
prepareRefresh();
//这里是在子类中启动refreshBeanFactory()的地方
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//准备bean工厂,以便在此上下文中使用
prepareBeanFactory(beanFactory);
try {
//设置 beanFactory 的后置处理器
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
//调用BeanFactory的后处理器,这些处理器是在Bean定义中向容器注册的
invokeBeanFactoryPostProcessors(beanFactory);
//注册Bean的后处理器,在Bean创建过程中调用
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
//对上下文中的消息源进行初始化
initMessageSource();
//初始化上下文中的事件机制
initApplicationEventMulticaster();
//初始化其他特殊的Bean
onRefresh();
//检查监听Bean并且将这些监听Bean向容器注册
registerListeners();
//实例化所有的(non-lazy-init)单例
finishBeanFactoryInitialization(beanFactory);
//发布容器事件,结束Refresh过程
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
由于上述代码是Spring的源码,本次不进行解读,小伙伴感兴趣可继续深入研究细节,本人直接跳到和SpringBoot自动装配有关的方法invokeBeanFactoryPostProcessors进行解读。
在invokeBeanFactoryPostProcessors方法中,完成了对IoC容器的初始化。大致分为三步:
1>Resource资源定位
在SpringBoot中,我们知道包扫描是从主类开始的,在prepareContext方法中,已经将主类封装为BeanDefinition,然后在此方法中解析主类的BeanDefinition获取basePackage的路径,完成了资源的定位。其次SpringBoot的starter通过SPI机制实现自动装配,还有@Import注解指定的类也进行了定位加载。
2>BeanDefinition的载入
所谓载入就是通过上述定位的basePackage,SpringBoot将路径拼接为:classpath:com/study/**/.class这样的形式,然后进行加载,判断有没有@Component注解,有就装载BeanDefinition。
3>注册BeanDefinition
注册过程就是把载入过程中解析得到的BeanDefinition向IoC容器进行注册。一路跟踪会进入到ConfigurationClassParser的parse方法(由于Spring源码篇幅太多,不是本次的重点,直接给出调用栈)
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
//加载配置 (SpringBoot项目则 此处为SpringBoot自动装配的入口)
this.deferredImportSelectorHandler.process();
}
再进入parse方法
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
看到do开头的方法,有经验的小伙伴已经知道了这个方法是真正干活的方法(doProcessConfigurationClass)。
//此方法很长,小伙伴可以打开源码浏览,此处只截取片段
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
...
// Process any @ComponentScan annotations
//根据 @ComponentScan 注解,扫描项目中的Bean(SpringBoot 启动类上有该注解)
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
//立即执行扫描,(SpringBoot项目为什么是从主类所在的包扫描,这就是关键 了)
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
//递归处理 @Import 注解
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
...
}
在doProcessConfigurationClass方法中会对主类的@Component注解和@Import注解进行读取执行响应的逻辑。我们继续往下看
this.componentScanParser.parse方法
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
...
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
...
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
在进入doScan方法,将有@Component的类注入到容器中。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//从指定的包中扫描需要装载的Bean
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//将该 Bean 注册进 IoC容器(beanDefinitionMap)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
我们看到解析完com.study包之后,testController被封装加载到。
然后就是重要的@Import解析
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
//递归找到@Import注解
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
这里只是解析了@import,并没有进行处理。
我们回到ConfigurationClassParser的parse方法,在方法的最后会执行
// 去执行组件类
this.deferredImportSelectorHandler.process();
进入process后一直进入到方法processGroupImports()
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
此处调用了getImports()。
public Iterable<Group.Entry> getImports() {
//遍历DeferredImportSelectorHolder对象集合deferredImports,
//deferredImports集合装了各种ImportSelector,
//当然这里装的是AutoConfigurationImportSelector
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
//利用AutoConfigurationGroup的process方法来处理自动配置
//的相关逻辑,决定导入哪些配置类
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
//经过上面的处理后,然后再进行选择导入哪些配置类
return this.group.selectImports();
}
然后就会调用类AutoConfigurationImportSelector中的内部类AutoConfigurationGroup中的process和selectImports方法。
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
//又将封装了自动配置类的autoConfigurationEntry对象装进 autoConfigurationEntries集合
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
//这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
@Override
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
processedConfigurations.removeAll(allExclusions);
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
进入getAutoConfigurationEntry方法
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
//获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//获得@Congiguration标注的Configuration类
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//得到spring.factories文件配置的所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//利用LinkedHashSet移除重复的配置类
configurations = removeDuplicates(configurations);
//得到要排除的自动配置类,比如注解属性exclude的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
//将要排除的配置类移除
configurations.removeAll(exclusions);
//因为从spring.factories文件获取的自动配置类太多,
//如果有些不必要的自动配置类都加载 进内存,会造成内存浪费,
//因此这里需要进行过滤,这里会调用AutoConfigurationImportFilter
//的match方法来判断是否符合 @ConditionalOnBean,
//@ConditionalOnClass或@ConditionalOnWebApplication
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
//将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象
return new AutoConfigurationEntry(configurations, exclusions);
}
在getCandidateConfigurations方法中就会调用loadFactoryNames方法加载位于META-INF下的spring.factories文件
本次的key是EnableAutoConfiguration。
AutoConfigurationImportSelector可以帮助Springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IOC 容器( ApplicationContext )中。
在spring.factories中有很多的自动装配类,但是不是所有的都会进行装载,只有符合条件的才会被加载到IoC容器中。
过滤前:
过滤后:
因为在spring.factories文件中有很多的配置类并不是我们所需要的,比如下图的RabbitAutoConfiguration,我们没有使用就不会被装配,我们导入了SpringMVC,则DispatcherServlet和HttpEncoding就会被装配,只有满足自动装配类上的Condition才会被装配。
我们来看一下执行refreshContext方法的效果。
执行前:
执行后:
此时符合自动装配的所有bean就会被自动装载到Spring的IoC容器中。
⑥刷新应用上下文后的扩展方法afterRefresh
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
扩展方法,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。
至此,SpringBoot的自动装配全部完成。
六、总结
SpringBoot自动配置的原理,主要做了以下事情:
①从spring.factories配置文件中加载自动配置类
②加载的自动配置类中排除掉@EnableAutoConfiguration注解的 exclude 属性指定的自动配置类
③然后再用AutoConfigurationImportFilter接口去过滤自动配置类是否符合其标注注解@ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication的条件,若都符合的话则返回匹配结果。
④然后触发 AutoConfigurationImportEvent 事件
告诉ConditionEvaluationReport条件评估报告器对象来分别记录符合条件和exclude的自动配置类。
⑤最后由spring将最后筛选后的自动配置类导入IOC容器中
今天的分享就到此结束啦,喜欢的小伙伴记得点赞呦。
关注公众号JavaGrowUp,回复【面试】获取面试题,备战金三银四,我们下期再见。