《SpringBoot》第03章 自动配置机制(二) 根注解@SpringBootApplication

news/2024/11/24 2:14:45/

前言

之前介绍到了把启动类封装成BeanDefinition注入进IOC容器,那么这个启动类就会跟普通的bean一样在refresh()中被实例化,那么显而易见作为启动类这个实例化并不简单,肯定会存在一些特殊处理,那么就需要研究一下其注解@SpringBootApplication

一、@SpringBootApplication

根注解概述

@SpringBootApplication是SpringBoot实现自动配置的入口,它里面包含另外3个注解:

  1. @SpringBootConfiguration: 将启动类标注为@Configuration,毕竟只有被标记为配置类,才能被扫描进IOC容器
  2. @EnableAutoConfiguration: 自动配置机制,包括:引入自动配置包 + 扫描自动配置机制用到的类
  3. @ComponentScan:扫描bean,默认会扫描启动类同级包和子包下开发人员写的bean
@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 {// 自动配置类需要手动排除的@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};// 自动配置类需要手动排除的@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

根注解组成部分一:@SpringBootConfiguration

【核心功能】:把启动类标记为@Configuration,这样才能被Spring框架扫描到

@SpringBootConfiguration这个注解包含了@Configuration@Configuration里面又包含了一个@Component注解

之前介绍我们需要把启动类注入到IOC容器中,那它也需要被@Component标记

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 只是标注启动类同样为配置类
@Indexed
public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;}

根注解组成部分二:@EnableAutoConfiguration

该注解负责的功能也很简单,通过名字就可以看出来,开启自动配置机制,那么如何开启?

它由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)两部分组成:

  1. @Import(AutoConfigurationImportSelector.class)引入类负责按需加载自动配置类
  2. @EnableAutoConfiguration允许自动配置类扫描开发人员写的类

这里只写了几句话介绍的很简单,下面会分开详细介绍

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动配置包规则的注解
@Import(AutoConfigurationImportSelector.class) // 引入的类
public @interface EnableAutoConfiguration {/*** Environment property that can be used to override when auto-configuration is* enabled.*/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 {};
}

1.加载自动配置类

1) 功能介绍

SpringBoot出现之后,我们使用某个功能,无须在经历引入jar包、写配置文件这样的路径,直接把对应的spring-boot-starter-xxx这样的依赖引入就可以了,那么想一下,其中肯定有对应的自动配置类来负责实现,而且每个依赖肯定有各自的自动配置类,只有这些自动配置类被执行,我们才能调用对应的功能

那么引入的这个类AutoConfigurationImportSelector就是负责读取每个依赖中的自动配置类

2) 获取自动配置类

在这里插入图片描述

上面看到了引入类AutoConfigurationImportSelector的类结构,实现了ImportSelector,那么肯定就要看selectImports()这个方法

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// <1>. 判断自动装配开关是否打开 if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);// <2>. 获取所有需要装配的beanAutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);// <3> 转换成数组返回 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
<1> 自动配置类加载入口
// 按需加载自动配置类
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {// 判断自动装配开关是否打开,默认spring.boot.enableautoconfiguration=trueif (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 获取EnableAutoConfiguration注解的exclude和excludeName属性值AnnotationAttributes attributes = getAttributes(annotationMetadata);// <1> 获取spring.factories中EnableAutoConfiguration的配置值List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 去重,原因是上面会读取所有spring-boot-starter的META-INF/spring.factories文件,可能会存在重复的,需要保证唯一configurations = removeDuplicates(configurations);// 获取限制候选配置的所有排除项(找到不希望自动装配的配置类)Set<String> exclusions = getExclusions(annotationMetadata, attributes);// 对参数exclusions进行验证,exclusion必须为自动装配的类,否则抛出异常checkExcludedClasses(configurations, exclusions);// 移除exclusionsconfigurations.removeAll(exclusions);// <2> 过滤出需要导入的配置类configurations = filter(configurations, autoConfigurationMetadata);// 配置监听事件fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}
<2> 加载全部自动配置类

由上面getAutoConfigurationEntry()进入到该getCandidateConfigurations(),负责获取全部的自动配置的类,

下面显示了3个方法,进行了很多方法的调用

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// getSpringFactoriesLoaderFactoryClass() 就是获取@EnableAutoConfiguration注解类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;
}public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories()

由上面loadFactoryNames()调用到方法loadSpringFactories(),该方法就负责读取配置文件,方法代码没有展示,知道做了什么就可以

读取的配置文件路径:META-INF/spring.factories,下面截图只展示了一个配置文件,实际不只是这一个配置文件,下面会继续介绍

在这里插入图片描述

通过上面截图了解到会读取配置文件,那么配置文件中有好多内容,怎么知道读取的就是org.springframework.boot.autoconfigure.EnableAutoConfiguration

在下面截图中会看到,其实很简单,这个路径就是@EnableAutoConfiguration这个注解的路径

在这里插入图片描述

通过Debug,可以看到读取到的配置类有一百多个

在这里插入图片描述

【注意】

读取到配置类不光是这个依赖下的META-INF/spring.factories被读取到,所有spring-boot-starter-xxx下的META-INF/spring.factories都会被读取到。
所以,你可以清楚滴看到,druid数据库连接池的SpringBoot Starter就创建了META-INF/spring.factories文件。
如果,我们自己要创建一个spring-boot-starter,这一步是必不可少的。

在这里插入图片描述

<3> 筛选自动配置类
@Conditional 条件注解

spring.factories中这么多配置,每次启动都要全部加载么?很明显,这是不现实的。我们Debug到后面你会发现,configurations的值变小了。
在这里插入图片描述

这是因为有了这一步的筛选,才能保证按需加载,那么如何实现,想想都知道肯定是类似于if判断的筛选,具体的实现是借助SpringBoot提供的条件注解

  • @ConditionalOnBean:当容器里有指定Bean的条件下
  • @ConditionalOnMissingBean:当容器里没有指定Bean的情况下
  • @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于SpEL表达式作为判断条件
  • @ConditionalOnJava:基于Java版本作为判断条件
  • @ConditionalOnJndi:在JNDI存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
  • @ConditionalOnWebApplication:当前项目是Web项目的条件下

示例1:SpringMVC自动配置

如下为了适应SpringMVC,需要满足下面的条件

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET) // 是Servlet原生式Web
@ConditionalOnClass(DispatcherServlet.class) // 存在DispatcherServlet这个类
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {/*** The bean name for a DispatcherServlet that will be mapped to the root URL "/".*/public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";/*** The bean name for a ServletRegistrationBean for the DispatcherServlet "/".*/public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";@Configuration(proxyBeanMethods = false)@Conditional(DefaultDispatcherServletCondition.class)@ConditionalOnClass(ServletRegistration.class)// 这里会从WebMvcProperties.class获取配置// WebMvcProperties.class又和配置文件application.properties绑定@EnableConfigurationProperties(WebMvcProperties.class)protected static class DispatcherServletConfiguration {@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());return dispatcherServlet;}// 这个比较有意思// 当存在这个组件,并且没有名字 然后方法就是直接返回  其实它就是为了保证注入的文件上传组件必须是按照这个名字,防止乱改名字@Bean@ConditionalOnBean(MultipartResolver.class) // 存在MultipartResolver这个Bean@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)// 当没有这个multipartResolver名字的Beanpublic MultipartResolver multipartResolver(MultipartResolver resolver) {// Detect if the user has created a MultipartResolver but named it incorrectly// 直接返回return resolver;}}// 省略代码...
}

示例2:Aop自动配置

package org.springframework.boot.autoconfigure.aop;import org.aspectj.weaver.Advice;import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration(proxyBeanMethods = false)
// 必须存在spring.aop这个值才可以
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {@Configuration(proxyBeanMethods = false)@ConditionalOnClass(Advice.class) // 必须包含Advice这个组件static class AspectJAutoProxyingConfiguration {@Configuration(proxyBeanMethods = false)@EnableAspectJAutoProxy(proxyTargetClass = false)@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")static class JdkDynamicAutoProxyConfiguration {}@Configuration(proxyBeanMethods = false)@EnableAspectJAutoProxy(proxyTargetClass = true)@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",matchIfMissing = true)static class CglibAutoProxyConfiguration {}}@Configuration(proxyBeanMethods = false)@ConditionalOnMissingClass("org.aspectj.weaver.Advice")@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",matchIfMissing = true)static class ClassProxyingConfiguration {@Beanstatic BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {return (beanFactory) -> {if (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}};}}
}
筛选执行 filter()
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {long startTime = System.nanoTime();String[] candidates = StringUtils.toStringArray(configurations);boolean[] skip = new boolean[candidates.length];boolean skipped = false;// 获取筛选器,也是从META-INF/spring.factories中获取,会获取到3个for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {invokeAwareMethods(filter);// 这里会调用接口的match()方法进行匹配boolean[] match = filter.match(candidates, autoConfigurationMetadata);for (int i = 0; i < match.length; i++) {if (!match[i]) {skip[i] = true;candidates[i] = null;skipped = true;}}}if (!skipped) {return configurations;}// 获取筛选后的自动配置类List<String> result = new ArrayList<>(candidates.length);for (int i = 0; i < candidates.length; i++) {if (!skip[i]) {result.add(candidates[i]);}}if (logger.isTraceEnabled()) {int numberFiltered = configurations.size() - result.size();logger.trace("Filtered " + numberFiltered + " auto configuration class in "+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");}return new ArrayList<>(result);
}

2.自动配置包

1) 注入bean

【提前声明】:首先说结果,@AutoConfigurationPackage这个注解折腾了一大圈,最终只是向容器中注入了一个BeanDefinition

通过名字可以看到该注释只是@Import了一个类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {};
}

进入到这个类中,看到是个静态内部类,那么肯定关注registerBeanDefinitions()这个方法了

// 实现了 ImportBeanDefinitionRegistrar 注册类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 参数:new PackageImports(metadata).getPackageNames().toArray(new String[0]) 会获取到启动类所在的包路径register(registry, new PackageImport(metadata).getPackageName());}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImport(metadata));}}

进入到register(),发现它只是向容器中注入了一个BeanDefinition

  1. 对应的bean名称为那个静态常量:BEAN
  2. 会把启动类的包路径存入BeanDefinition
// 这个 BEAN 为需要注入的 BeanDefinition 的名称 
// AutoConfigurationPackages.class.getName()  = org.springframework.boot.autoconfigure.AutoConfigurationPackages
private static final String BEAN = AutoConfigurationPackages.class.getName();public static void register(BeanDefinitionRegistry registry, String... packageNames) {// 如果已经注册完毕,但第一次启动一般不可能if (registry.containsBeanDefinition(BEAN)) {BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));}else {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();// 这个 BeanDefinition 的类型为 BasePackages.classbeanDefinition.setBeanClass(BasePackages.class);// 由于上面是 BeanDefinition,还没有实例化BasePackages,所以这里定义在实例化的时候,使用对应的构造方法,把包路径传进去beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(BEAN, beanDefinition);}
}

通过Debug看一下
在这里插入图片描述

2) 思路分析

上面可能看的比较懵,注入的这个BeanDefinition有啥用?猜猜都知道肯定和那个包路径有关

举个例子:例如最常见的Mybatis,我们使用的时候它会扫描被@Mapper标记的接口,这是看到的效果,如何实现的呢?首先Mybatis的自动配置类会通过这个之前注入的自动化配置bean来决定是否自动扫描mapper,也就是把注入的那个BeanDefinition当作一个开关,代表允许扫描开发人员写的类,那么接下来就顺理成章了,那么包路径肯定是扫描哪个路径下的类

下面截图Debug展示一下Mybatis的自动配置类MybatisAutoConfiguration,在这之前肯定先导入对应的依赖mybatis-spring-boot-starter
在这里插入图片描述

通过这个例子应该就明白了,其实把这个包路径包装成BeanDefinition存入IOC容器,我觉得是变相的把IOC容器当作是一个缓存了,因为IOC容器是始终存在的,贯穿这个项目的

那么在总结一下,@AutoConfigurationPackage的作用是允许其它自动配置类去扫描开发人员写的代码,然后根据不同的扫描规则来执行,可能有人好奇Spring框架不是都扫描了嘛,咋这还给扫描呢,肯定是有区分的,Spring框架扫描的@Component注解,Mybatis扫描的是@Mapper注解,自己定义的注解自己扫描

3) 理解误区

在网上也搜了一些文章,也包括自己最开始的错误想法,认为这个是扫描包路径下那些被@Component标记的需要注入的IOC容器的bean,我们之所以在里面写个类,加个@Component就能注入到IOC容器中,就是因为这个@AutoConfigurationPackage,但这样理解是错误的,网上很多文章也是错误的

而且通过直观的角度来看,它是放在@EnableAutoConfiguration这个注解里面的,父注解是开启自动配置机制,那么这个肯定也是和自动配置机制有关的了

那么扫描包路径下的@Component,这个是怎么实现呢,是通过下面要介绍的组成部分三@ComponentScan来实现的

3.总结

上面介绍了2个注解以后,应该整体就清晰了,自动配置机制,首先会通过@Import(AutoConfigurationImportSelector.class)按需加载需要的自动配置类,如果某些自动配置类需要扫描自己写的类,那么会通过@AutoConfigurationPackage来获取允许扫描的路径,这样就保证将依赖的jar包会注入到IOC容器中,自己写的类会和jar包绑定起来

根注解组成部分三:@ComponentScan

1.功能介绍

在这里会负责扫描启动类同级和子包下的bean

@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 {}

2.TypeExcludeFilter 没啥用

它是一种扩展机制,子类可以继承TypeExcludeFilter,重写match(),自定义将某些类排除注册,平常开发用不到

public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {private BeanFactory beanFactory;private Collection<TypeExcludeFilter> delegates;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {// 从容器中获取TypeExcludeFilter,然后遍历匹配for (TypeExcludeFilter delegate : getDelegates()) {if (delegate.match(metadataReader, metadataReaderFactory)) {return true;}}}return false;}	// 从容器中获取TypeExcludeFilterprivate Collection<TypeExcludeFilter> getDelegates() {Collection<TypeExcludeFilter> delegates = this.delegates;if (delegates == null) {delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();this.delegates = delegates;}return delegates;}}

3.AutoConfigurationExcludeFilter

如果类上同时有@Configuration@EnableAutoConfiguration自动配置类,那么该类需要被排除

public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {private ClassLoader beanClassLoader;private volatile List<String> autoConfigurations;@Overridepublic void setBeanClassLoader(ClassLoader beanClassLoader) {this.beanClassLoader = beanClassLoader;}// 匹配方法@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {// 1.判断类是否有 @Configuration 注解// 2.判断是否有 @EnableAutoConfiguration 注解return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);}private boolean isConfiguration(MetadataReader metadataReader) {return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());}private boolean isAutoConfiguration(MetadataReader metadataReader) {return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());}protected List<String> getAutoConfigurations() {if (this.autoConfigurations == null) {this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,this.beanClassLoader);}return this.autoConfigurations;}}

二、部分逻辑点Debug介绍

在上面的章节中我们介绍了主启动类启动SpringApplication.run(DroolsWys4822Application.class, args);以及根注解@SpringBootApplication,那么这里再次对一些逻辑点进行Debug介绍,会展示一些重要的截图

1.启动类注入IOC容器

SpringBoot会把启动类封装成BeanDefinition注入到IOC容器,然后以配置类的方式被初始化,执行代码是在SpringApplication -> run() -> prepareContext() -> load(),这个之前已经介绍了
在这里插入图片描述
在这里插入图片描述

2.初始化启动类

之前介绍过启动类就是一个配置类,这个已经重复多次了,那么负责解析它的是ConfigurationClassPostProcessor,这个研习过Spring框架的肯定知道

首先回忆一下代码在refresh() -> invokeBeanFactoryPostProcessors(),这里执行后置处理器,其中就有ConfigurationClassPostProcessor,当然这个后置处理器是Spring框架自带的,启动以后默认就会注入到容器中的

在这里插入图片描述

接下来肯定就是执行ConfigurationClassPostProcessor中的方法,负责解析各个配置类的内容

在这里插入图片描述

到这里以后启动类就会像普通的配置类一样,被逐步解析,具体解析步骤在Spring中已经介绍了

3.自动扫描注入bean

都知道SpringBoot默认会加载启动类同级和子包下的bean注入到IOC容器,那么Debug展示一下:

在这里插入图片描述
在这里插入图片描述

三、自动配置总结

  1. SpringBoot启动的时候会创建一个SpringApplication对象,在对象的构造方法里面会进行一些参数的初始化工作,最主要的是判断当前应用程序的类型以及设置初始化器以及监听器,并在这个过程中会加载整个应用程序的spring.factories文件,将文件中的内容放到缓存当中,方便后续获取;
  2. SpringApplication对象创建完成之后会执行run()方法来完成整个应用程序的启动,启动的过程中有两个最主要的方法prepareContext()refreshContext(),在这两个方法中完成了自动装配的核心功能,在run()方法里还执行了一些包括上下文对象的创建,打印banner图,异常分析器的准备等各个准备工作,方便后续进行调用;
  3. prepareContext()中主要完成的是对上下文对象的初始化操作,包括属性的设置,比如设置环境变量。在整个过程中有一个load()方法,它主要是完成一件事,那就是将启动类作为一个beanDefinition注册到registry,方便后续在进行BeanFactoryPostProcessor调用执行的时候,可以找到对应执行的主类,来完成对@SpringBootApplication@EnableAutoConfiguration等注解的解析工作;
  4. refreshContext()方法中会进行整个容器的刷新过程,会调用spring中的refresh()方法,refresh()方法中有13个非常关键的方法,来完成整个应用程序的启动。而在refresh()中会调用的关键的一个方法就是invokeBeanFactoryPostProcessors()方法,在这个方法中主要是对ConfigurationClassPostProcessor类的处理,这个类负责解析被@Configuration标记的配置类(这里需要研习过refresh()方法),之前介绍过启动类是被@Configuration标记了,所以会在这里进行处理解析,会解析处理各种的注解,包含@PropertySource@ComponentScan@Bean@Import等注解,最主要的是对@Import注解的解析
  5. 总结一下,我之前的错误思想以为SpringBootSpring是两码事,而且主启动类很神秘,仔细研究了原理之后,发现SpringBoot的启动类它就是一个@Configuration配置类,在refresh()的时候也会像我们自己写的配置类一样被初始化,然后SpringBoot就是利用这点引入了很多默认配置,所以需要领悟的是SpringBootSpring是包含关系,SpringBoot = Spring + 默认配置 ,它利用了Spring框架提供的良好扩展性,封装出这一套框架协助开发人员快速开发,再来体会一下SpringBoot的优点开箱即用,当然也要感叹一下作者Pivotal团队强大的创造力。
    esh()方法中有13个非常关键的方法,来完成整个应用程序的启动。而在refresh()中会调用的关键的一个方法就是invokeBeanFactoryPostProcessors()方法,在这个方法中主要是对ConfigurationClassPostProcessor类的处理,这个类负责解析被@Configuration标记的配置类(这里需要研习过refresh()方法),之前介绍过启动类是被@Configuration标记了,所以会在这里进行处理解析,会解析处理各种的注解,包含@PropertySource@ComponentScan@Bean@Import等注解,最主要的是对@Import`注解的解析
  6. 总结一下,我之前的错误思想以为SpringBootSpring是两码事,而且主启动类很神秘,仔细研究了原理之后,发现SpringBoot的启动类它就是一个@Configuration配置类,在refresh()的时候也会像我们自己写的配置类一样被初始化,然后SpringBoot就是利用这点引入了很多默认配置,所以需要领悟的是SpringBootSpring是包含关系,SpringBoot = Spring + 默认配置 ,它利用了Spring框架提供的良好扩展性,封装出这一套框架协助开发人员快速开发,再来体会一下SpringBoot的优点开箱即用,当然也要感叹一下作者Pivotal团队强大的创造力。

http://www.ppmy.cn/news/37403.html

相关文章

QT Qwidget 事件处理机制

qlineEdit Qt事件处理是指在Qt应用程序中处理各种事件的过程。事件是指在应用程序中发生的各种操作&#xff0c;例如按键、鼠标点击、窗口移动等。Qt提供了一个事件处理机制&#xff0c;使得开发者可以对这些事件进行处理&#xff0c;以实现应用程序的各种功能。 Qt中的事件处…

面试官问我为什么Integer a = (128) != Integer b (128) 装箱拆箱及IntegerCache问题

前言 基本数据类型与包装类型&#xff1a; 什么是基本数据类型&#xff1f;什么是包装类型&#xff1f; 装箱拆箱 什么是装箱&#xff1f;什么是拆箱&#xff1f; 怎么进行装箱的&#xff1f; 为什么这样设计&#xff1f; 如何进行拆箱 有了基本数据类型为什么还要有包…

【计算机视觉 | 目标检测】DETR风格的目标检测框架解读

文章目录一、前言二、理解2.1 DETR的理解2.2 DETR的细致理解2.2.1 Backbone2.2.2 Transformer encoder2.2.3 Transformer decoder2.2.4 Prediction feed-forward networks (FFNs)2.2.5 Auxiliary decoding losses2.3 更具体的结构2.4 编码器的原理和作用2.5 解码器的原理和作用…

【前端】jQuery

jQuery 是 JavaScript 的一个库&#xff0c;可以简化 JavaScript 编程 点击下载 jQuery 语法 $(selector).action()jquery(selector).action() selector 为选择器&#xff0c;使用选择器选择 HTML 元素 action() 为操作&#xff0c;对选中的元素进行操作 选择器 CSS 选择…

大数据学完好就业么

Python的普及与数据挖掘、人工智能和数值计算等领域的蓬勃发展相关&#xff0c;但同时也与普遍编程需求的增加有关。 Python应用领域广泛&#xff0c;意味着选择Python的同学在学成之后可选择的就业领域有很多&#xff0c;加上Python本身的优势&#xff0c;致使现在越来越多的…

Java Script

一.初识js 1.与css html的关系 HTML 网页的结构(骨CSS:网页的表现(皮JavaScript :网页的行为2.运行过程 编写的代码是保存在文件上,也就是存储到硬盘(外存zhong)双击以后,html文件浏览器(引用程序)就会读取文件,将文件内容加载到内存中,(数据流向:硬盘->内存)浏览器会解析用…

奇异值分解(SVD)和图像压缩

在本文中&#xff0c;我将尝试解释 SVD 背后的数学及其几何意义&#xff0c;还有它在数据科学中的最常见的用法&#xff0c;图像压缩。 奇异值分解是一种常见的线性代数技术&#xff0c;可以将任意形状的矩阵分解成三个部分的乘积&#xff1a;U、S、V。原矩阵A可以表示为&#…

第09章_子查询

第09章_子查询 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公…