【SpringBoot】自动配置原理

一、什么是Spring Boot的自动配置


Spring Boot的最大的特点就是简化了各种xml配置内容,之前使用SSM框架时我们配置的许多内容,例如数据源、连接池、会话工厂、事务管理···,而现在这些都由Spring Boot来自动配置。简单来说就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使项目能够快速运行。

二、Spring Boot自动配置原理

想了解原理,就需要从头开始追根溯源

首先我们打开Spring Boot启动类

启动类中唯一的注解@SpringBootApplication自然成了我们关注的重点,它是Spring Boot的核心注解,也是一个组合注解,ctrl+鼠标左键点进去

我们进入这个注解可以看到里面又包含了其它很多注解,但其中@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan三个注解尤为重要,这三个注解的作用分别是:

@SpringBootConfiguration:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类,继承自Configuration,支持JavaConfig的方式进行配置。

@EnableAutoConfiguration:开启自动配置,是实现自动配置最主要的注解。

@ComponentScan:自动扫描组件,默认扫描该类所在包及其子包下所有带有指定注解的类,将它们自动装配到bean容器中,会被自动装配的注解包括@Controller、@Service、@Component、@Repository等。也可以指定扫描路径。


继续进入@EnableAutoConfiguration注解

又发现两个特殊的注解:

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

@Import(AutoConfigurationImportSelector.class),它导入了AutoConfigurationImportSelector类。这个类中有一个非常重要的方法——selectImports(),它几乎涵盖了组件自动装配的所有处理逻辑,包括获得候选配置类、配置类去重、排除不需要的配置类、过滤等,最终返回符合条件的自动配置类的全限定名数组。

下面源码图中对该方法进行了详尽的注释

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//检查自动配置功能是否开启,默认开启
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//加载自动配置的元信息
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//获取候选配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		//去掉重复的配置类
		configurations = removeDuplicates(configurations);
		//获得注解中被exclude和excludeName排除的类的集合
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		//检查被排除类是否可实例化、是否被自动注册配置所使用,不符合条件则抛出异常
		checkExcludedClasses(configurations, exclusions);
		//从候选配置类中去除掉被排除的类
		configurations.removeAll(exclusions);
		//过滤
		configurations = filter(configurations, autoConfigurationMetadata);
		//将配置类和排除类通过事件传入到监听器中
		fireAutoConfigurationImportEvents(configurations, exclusions);
		//最终返回符合条件的自动配置类的全限定名数组
		return StringUtils.toStringArray(configurations);
	}

 需要注意如何得到候选的配置类,可以看到所有的配置信息通过getCandidateConfigurations()得到

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		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;
	}

点进去loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
这个方法(类加载器)

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

通过以上代码我们发现:getCandidateConfigurations()方法通过SpringFactoriesLoader.loadFactoryNames()类加载器加载META-INF/spring.factories文件,通过这个文件获取到每个配置类的url,再通过这些url将它们封装成Properties对象,最后解析内容存于Map<String,List<String>>中。

spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件,这个文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,根据文件的内容格式我们可以清晰地了解Map<String,List<String>>中都存了些什么。

 最后通过loadFactoryNames传递过来的class名称作为Key从Map中获得该类的配置列表,而这个class名称是什么呢?回到之前的SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())方法,注意第一个参数,是一个方法,我们进入这个方法,发现返回的是EnableAutoConfiguration.class,这即是我们需要的class名称。

 最终,getCandidateConfigurations()方法获取到了候选配置类并存于List中。之所以说是“候选”,是因为它们后续还需要经过一系列的去重、排除、过滤等操作,通过selectImports()方法返回一个自动配置类的全限定名数组,然后将所有自动配置类加载到Spring容器中,这样就实现了自动装配。


 


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值