《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团队强大的创造力。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值