2021-09-13

SpringBoot自动配置原理

1.SpringBoot的入口

@SpringBootApplication//表示一个SpringBoot的启动类
@MapperScan("com.dream.mapper")
public class App {
    public static void main(String[] args) {
       SpringApplication application = new SpringApplication(App.class);
       application.setBannerMode(Banner.Mode.CONSOLE);
       application.run(args);
    }
}

​ 相信学过的小伙伴都知道这个类的作用并不是单纯的启动了一个方法,实则是启动了一个服务器,因为加了@SpringBootApplication,那究竟是什么原因让这个主方法(main)启动变成了一个启动服务器的方法呐?那么下面就让小码农带大家看看这个注解底层到底干了啥。

2.@SpringBootApplication

​ 首先我们进入@SpringBootApplication注解的底层可以很清楚的看到其实这个注解是一个复合注解,它其实是由@SpringBootConfiguration@ComponentScan@EnbleAutoConfiguration核心注解构成。

//元注解
@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 {

那么这三个核心注解在底层又干了些什么事呐? 不用着急让小码农给各位小伙伴一一道来!

3.@ComponentScan

​ 看到这个@ComponentScan注解扫相信小伙伴们应该不陌生吧 这个注解就是注解扫描包,此注解一般和@Configuration注解一起使用,指定Spring扫描注解的package。如果没有指定包,那么默认会扫描此配置类所在的package以及子包。这里小码农就的给各位小伙伴说一个SpringBoot的规定,就是启动类要放在父类的包上面,这样才会将项目中的当前包和子包全部扫描到。但是这个也不是必须放在父包上的 ,根据自己项目需求就行,能够理解这个注解的原理就行。

@ComponentScan(excludeFilters = {//这里的意思是TypeExcludeFilter、AutoConfigurationExcludeFilter类不用扫描的
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

4.@SpringBootConfiguration

​ 点进这个注解我们发现这个注解也是个复合注解,它里面的有@Configuration注解,此注解用在class上来定义bean。其作用和xml配置文件相同,表示此bean是一个Spring配置。此外,此类可以使用@Bean注解来初始化定义bean。其实就是把一个类当做配置文件来理解就好啦!这样看来上面的 class Application启动类就可以当做配置类,如果自己项目中需要一些配置,就可以写在启动类里面。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

5.@EnableAutoConfiguration(重点)

​ 说了这么终于到这个主角了,这个注解相比前两个注解就偏难理解了,不过不用慌,小码农也会给各位小伙伴详细讲解分析清楚啦! 这个注解@EnableAutoConfiguration 的作用开启自动配置功能,这个注解由@AutoConfigurationPackage@Import构成。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage作用:自动配置包
1.进入@AutoConfigurationPackage:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**Spring底层注解@import , 给容器中导入一个组件,导入的组件由AutoConfigurationPackages.Registrar.class这个类来指定,
**/
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
这里可以理解为启动类启动通过@ComponentScan扫描到的所有包或者类,通过@AutoConfigurationPackage都封装到Registrar这个类中,然后再将其放入Spring容器中。
      
2.再点进AutoConfigurationPackages.Registrar.class这个里面找到Registrar这个类
    
    
    	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata,
				BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}

	}  

我们发现这个Registrar是AutoConfigurationPackages类中静态内部类,我们现在在这个类registerBeanDefinitions中debug发现这个metadat(元数据)是在springbootApplication下的,而且是作用在启动类里面

在这里插入图片描述

所以@AutoConfigurationPackage的作用是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有的子包里面的所有组件扫描到Spring容器中。所以controller、service之类的包必须在和主启动类在同一包下

@Import(AutoConfigurationImportSelector.class)作用:导入一个自动装配选择器

​ 我们在退到@EnableAutoConfiguration的里面点进@Import(AutoConfigurationImportSelector.class)中去找到process方法,这个方法是AutoConfigurationImportSelector类的程序入口,就是当启动类启动就会进入该方法

第一步:
@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()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                //再点进getAutoConfigurationEntry这个方法里面去
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
							annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}


第二步:
protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    	//执行getCandidateConfigurations这个方法会加载自动化配置的类然后初始化configurations集合
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
第三步:点进getCandidateConfigurations这个方法里面去
    
 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
    //通过文件流的方式加载配置文件,注意有两个参数,第一个参数点进去,是返回这个EnableAutoConfiguration.class类,第二个参数是类加载器
		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;
	}
第四步:再点进SpringFactoriesLoader.loadFactoryNames这个方法
    
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
第五步:最后点进loadSpringFactories这个方法中
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
            //	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
   //这里通过类加载器去加载了类路径下面的META-INF的所有spring.factories文件,得到urls资源作为properties配置文件
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
 //这里会在spring.factories文件里面通过EnableAutoConfiguration类名里面找XXXAutoConfiguration的类,这个 XXXAutoConfiguration--替换-->XML配置文件
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

下面小码农会将SpringBoot中的META-INF/spring.factories文件以及上面相对应的步骤通过debug的方式给各位小伙伴们展示出来
在这里插入图片描述

在上面第二步的时候小码农给大家说执行getCandidateConfigurations这个方法会加载自动化配置的类然后将会初始化configurations集合

在这里插入图片描述

那么问题来了这configurations的集合是在哪里获取到的值呐?
在这里插入图片描述

其实这并不难理解就是上图通过debug发现,底层就是通过获取到的url找到META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration的值罢了,从configurations集合中有120多个配置类被加载了,这里小码农就有个问题想问大家一下,这100多个配置类加载到Spring容器中就都能使用生效吗? 答案是不能

我们点进spring.factories里面 ,在EnableAutoConfiguration中随便点一个配置类进去,我这里点的是WebMvcAutoConfiguration:
在这里插入图片描述

为什么会出现这种情况呐?下面我们就来看看WebMvcAutoConfiguration配置类上的@ConditionalOnClass

6.@ConditionalOn(条件注解)

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
//条件注解,某个class位于类路径上,才会实例化一个Bean
//也可以理解为只要这个系统中有 Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class类这个 WebMvcAutoConfiguration自动化配置才会自动生效,就能自动创建配置
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

其实这个就和场景启动器相关了,看下面我们将WebMvcAutoConfiguration相对应的场景启动器导入系统中这个配置类就能自动给我们配置文件了。
在这里插入图片描述

下面是小码农给各位小伙伴们画的一个SpringBoot自动装配的全过程思维流程图,希望能够帮助到你们。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值