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

前言

之前介绍到了把启动类封装成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 {

   @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
      // <1>. 判断自动装配开关是否打开 
      if (!isEnabled(annotationMetadata)) {
         return NO_IMPORTS;
      }
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
      // <2>. 获取所有需要装配的bean
      AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
      // <3> 转换成数组返回 
      return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
   }
              
}
<1> 自动配置类加载入口
// 按需加载自动配置类
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   // 判断自动装配开关是否打开,默认spring.boot.enableautoconfiguration=true
   if (!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);
   // 移除exclusions
   configurations.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名字的Bean
		public 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 {

		@Bean
		static 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 {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      // 参数:new PackageImports(metadata).getPackageNames().toArray(new String[0]) 会获取到启动类所在的包路径
      register(registry, new PackageImport(metadata).getPackageName());
   }

   @Override
   public 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.class
      beanDefinition.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;

   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      this.beanFactory = beanFactory;
   }

   @Override
   public 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;
   }	

   // 从容器中获取TypeExcludeFilter
   private 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;

   @Override
   public void setBeanClassLoader(ClassLoader beanClassLoader) {
      this.beanClassLoader = beanClassLoader;
   }
	
   // 匹配方法
   @Override
   public 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团队强大的创造力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5712.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI只会淘汰不进步的程序员

最近AI界的大新闻有点多&#xff0c;属于多到每天很努力都追不上&#xff0c;每天都忙着体验各种新产品或申请试用新产品。各种自媒体肯定也不会放过这个机会&#xff0c;AI取代程序员的文章是年年有&#xff0c;今天特别多。那么AI到底会不会取代程序员的工作呢&#xff1f;先…

[chapter4][5G-NR][传输方案]

前言&#xff1a; 多天线传输的基本过程传输方案 前面见过数据加扰&#xff0c;调制&#xff0c;层映射的一些基本原理&#xff0c;算法。 这里重点讲一下传输方案 目录&#xff1a; 1&#xff1a; 下行传输方案 2&#xff1a; 上行传输方案 3&#xff1a; 资源块映射 备注&…

.net开发安卓从入门到放弃 最后的挣扎(排查程序闪退问题记录-到目前为止仍在继续)

安卓apk闪退问题排查记录logcat程序包名先看日志&#xff08;以下日志是多次闪退记录的系统日志&#xff0c;挑拣几次有代表性的发上来&#xff09;最近一次闪退adb shell tophelp一个demo说明adb shell dumpsys meminfo <package_name>ps&#xff1a;写在前面&#xff0…

训练中文版chatgpt

文章目录1. 斯坦福的模型——小而低廉&#xff1a;Alpaca: A Strong Open-Source Instruction-Following Model2. Meta 模型&#xff1a;LLaMA&#xff1a;open and efficient foundation language models3.ChatGLM4.斯坦福开源机器人小羊驼Vicuna&#xff0c;130亿参数匹敌90%…

SSM+LayUi实现的学籍管理系统(分为管理员、教师、学生三个角色,实现了专业管理,班级管理,学生管理,老师管理,课程管理,开课管理以及用户管理等)

博客目录jspservletmysql实现的停车场管理系统实现功能截图系统功能使用技术完整源码jspservletmysql实现的停车场管理系统 本系统是一个servlet原生框架实现的停车场管理系统&#xff0c;总共分为两个角色&#xff0c;普通用户和管理员&#xff0c;实现了用户管理、停车信息管…

Linux基础IO

本篇博客来讲述Linux中的新一模块--文件IO&#xff0c;我们来做简单的介绍和陈述。 在笔者之前的文章之中&#xff0c;已经对C语言中的文件操作做了简要介绍&#xff0c;我们旧事重提&#xff0c;再次进行一个简要的回顾。 目录 1.文件的操作 1.1打开文件 1.2向文件写入数…

Java多态

目录 1.多态是什么&#xff1f; 2.多态的条件 3.重写 3.1重写的概念 3.2重写的作用 3.3重写的规则 4.向上转型与向下转型 4.1向上转型 4.2向下转型 5.多态的优缺点 5.1 优点 5.2 缺点 面向对象程序三大特性&#xff1a;封装、继承、多态。 1.多态是什么&#xff1…

七结(4.2)遍历集合与javaFX界面

今天由学长学界们进行了一次授课&#xff0c;算是温习了一遍面向对象的知识&#xff0c;同时配置了关于javaFX的环境&#xff0c;以及一些关于项目的知识。 java学习总结&#xff1a; Collection的遍历&#xff1a; 迭代器遍历&#xff08;Iterator&#xff09;&#xff1a;…

leetcode 87. Scramble String(扰乱字符串)

scramble(字符串s) 算法&#xff1a; s长度为1时结束。 s可以拆分成2部分x和y&#xff0c;sxy, 这两部分可以交换&#xff0c;也可以不交换&#xff0c;即 s xy 或 s yx. 上面scramble还会递归作用于x 和 y. 给出相同长度的字符串s1, s2, 问s2是否可通过scramble(s1)获得。 …

WTW-16P 应用电路

1、WTW-16P 按键控制 PWM 输出应用电路 软件设置&#xff1a; 按键控制模式。 I/O 口定义&#xff1a; 选取 I/O 口 P00、P01、P02、P03 作为触发口&#xff0c;在编辑 WT588D 语音工程时&#xff0c;把触发口的按键定义为可触发播放的触发方式&#xff0c;就可进行工作。 BUS…

如何提高网站安全防护?

网站的安全问题一直是很多运维人员的心头大患&#xff0c;一个网站的安全性如果出现问题&#xff0c;那么后续的一系列潜在危害都会起到连锁反应。就好像网站被挂马&#xff0c;容易遭受恶意请求呀&#xff0c;数据泄露等等都会成为杀死网站的凶手。 1、让服务器有一个安全稳定…

百度双塔召回引擎MOBIUS

1. 概述 对于一个搜索或者推荐系统来说&#xff0c;分阶段的设计都是当下的一个标配&#xff0c;主要是为了平衡效率和效果&#xff0c;在百度的广告系统中&#xff0c;也是分成了如下的三层结构&#xff1a; 最上层的Matching阶段负责从全库中找到与query相关的候选集&#x…

KD2511N系列微电阻测试仪

一、产品概述 KD2511N系列微电阻测试仪是一款对变压器、电机、开关、继电器、接插件等各类直流电阻进行测试的仪器。其基本测试精度最高可达 0.05%&#xff0c;并具有较高的测量速度。 KD2511N微电阻测试仪使用了高精度恒流流经被测件以及四端测量&#xff0c;有效的消除了 引线…

Kafka 如何保证消息的消费顺序

文章目录先直接给出答案吧。在集群或者多partition下无法保障完全顺序消费&#xff0c;但是可以保障分区顺序消费。具体下面讲解。我们在使用消息队列的过程中经常有业务场景需要严格保证消息的消费顺序&#xff0c;比如我们同时发了 2 个消息&#xff0c;这 2 个消息对应的操作…

蓝桥杯——根据手册写底层

一、 DS18B20温度传感器 1.官方所给源码 /* # DS1302代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码…

ssm入门

文章目录1.介绍ssm2.Spring篇基础内容&#x1fa85;什么是IOC&#xff08;控制反转&#xff09;Spring和IOC之间的关系IOC容器的作用以及内部存放IoC入门案例&#x1f4ec;DI&#xff08;Dependency Injection&#xff09;依赖注入依赖注入的概念IOC容器中哪些bean之间要建立依…

函数微分和导数的定义

1.我们先来看可导的定义&#xff1a; 相信这个大家都看的懂。 2.接下来我们看可微的定义&#xff1a; 你们有没用想过为什么会有可微&#xff0c;他是用来干什么的&#xff0c;我们接下来看下面这张图&#xff0c;特别是结合图2-11来说&#xff0c; 我们可以看到书上说可微是在…

【day2】Android Jetpack Compose环境搭建

【day2】Android Jetpack Compose环境搭建 以下是适用于 Jetpack Compose 的环境要求&#xff1a; Android Studio 版本&#xff1a;4.2 Canary 15 或更高版本Gradle 版本&#xff1a;7.0.0-beta02 或更高版本Android 插件版本&#xff1a;4.2.0-beta15 或更高版本Kotlin 版本…

MySQL 幻读问题

承接上文MySQL多版本并发控制MVCC实现原理 幻读现象 因为在RR&#xff08;可重复读&#xff09;隔离级别里&#xff0c;事务1的第二次查询没有生成新的readview&#xff0c;而是用的第一次查询时生成的readview&#xff0c;所以第二次查询返回2条数据&#xff0c;而不是3条数据…

看过来,这里有JavaScript技术干货?

今天是一篇正经的技术分享&#xff0c;针对JavaScript技能的十来个专业小技巧&#xff0c;如果你想提升一下JS方面的能力成为一个更好的前端开发人员&#xff0c;那么就可以接着看下去哦。 1、使用逻辑运算符进行短路评估 您可以使用逻辑运算符进行短路评估&#xff0c;方法是…