SpringBoot自动配置

自动配置:根据添加的jar包依赖,会自动将一些配置类的bean注册进IOC容器,可以在需要的地方使用@Autowired或者@Resource等注解使用它。
Spring Boot如何自动配置的,都把哪些组件进行了自动配置?
springboot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication:springboot应用标注在某个类上说明这个类是springboot的主配置类,springboot就应该运行这个类的main()方法启动springboot应用。

查看@SpringBootApplication内部源码进行分析,核心代码具体如下:

@Target(ElementType.TYPE)//注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME)//表示注解的生命周期,Runtime运行时
@Documented//表示注解可以记录在javadoc中
@Inherited//表示可以被子类继承该注解
@SpringBootConfiguration//表明该类为配置类
@EnableAutoConfiguration//启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  
       //根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

       //根据classname来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	//指定扫描包,参数是包名的字符串数组
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};


        //扫描特定的包,参数类似是class类型数组
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

@SpringBootApplication注解是一个组合注解,前面4个是注解的元数据信息, 主要看后面3个注解。

@SpringBootConfiguration
springboot的配置类,标注在某个类上。
查看注解源码,如下所示

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration//配置类的作用等同于配置文件,配置类也是容器中的一个对象
public @interface SpringBootConfiguration {
}

从上述源码可以看出,注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被SpringBoot进行了重新封装命名而已。

接着看下@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//自动配置包
@AutoConfigurationPackage
//spring的底层注解,给容器导入一个组件
@Import(AutoConfigurationImportSelector.class)
//告诉springboot开启自动配置功能,这样自动配置才能生效
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	//返回不会被导入到spring容器中的类
	Class<?>[] exclude() default {};

	//返回不会被导入到Spring容器中的类名
	String[] excludeName() default {};
}

spring中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IOC容器。
@AutoConfigurationPackage如下代码所示:

@Import(AutoConfigurationPackages.Registrar.class)//导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
}

@Import就是将Register这个组件类导入到容器中,查看Register

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));
	}
}

在register处打个断点。
在这里插入图片描述

再看register方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	if (registry.containsBeanDefinition(BEAN)) {
	        //如果该bean已经注册,则将要注册包名称添加进去
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
	}
	else {
	       //如果尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

AutoConfigurationPackages.Registrar这个类此时就注册了一个bean,这个bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,这个参数使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后使用。

接着分析@Import(AutoConfigurationImportSelector.class
将AutoConfigurationImportSelector类导入到spring容器中,这个类可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前springboot创建并使用的IOC容器中。

通过调用栈
在这里插入图片描述

确定自动配置实现逻辑的入口方法,看下DeferredImportSelectorGrouping 类的getImports方法处。

 public Iterable<Entry> getImports() {
        //集合中装了各种ImportSelector
         Iterator var1 = this.deferredImports.iterator();
         while(var1.hasNext()) {
           ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
          //利用AutoConfigurationGroup的process方法来处理自动配置相关逻辑,决定导入哪些配置类(重点)
           this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
         }
         //经过上面的处理后,然后进行选择导入哪些配置类
         return this.group.selectImports();
     }

接着进入AutoConfigurationImportSelector 类中的AutoConfigurationGroup的process方法

 //用来处理自动配置类,比如过滤不符合匹配条件的自动配置类
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 autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
             .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    //封装了自动配置类的autoConfigurationEntry对象进入autoConfigurationEntries集合
     this.autoConfigurationEntries.add(autoConfigurationEntry);
     for (String importClassName : autoConfigurationEntry.getConfigurations()) {
           //遍历刚获取的自动配置类,符合条件的自动配置类作为key,annotationMetadata放入集合
     this.entries.putIfAbsent(importClassName, annotationMetadata);
	}
}

getAutoConfigurationEntry 这个方法主要是用来获取自动配置类有关。

//获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	//得到spring.factories文件配置的所有自动配置类
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	//移除重复的配置类
	configurations = removeDuplicates(configurations);
	//得到要排除的自动配置类,比如注解属性exclude的配置类
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	//检查要排除的配置类,因为有些不是自动配置类,要抛出异常
	checkExcludedClasses(configurations, exclusions);
	//将要排除的配置类移除
	configurations.removeAll(exclusions);
	//会调用AutoConfigurationImportFilter的match方法判断是否符合@ConditionalOnBean或@ConditionalOnClass
	configurations = filter(configurations, autoConfigurationMetadata);
	//获取了符合条件的自动配置类后,会触发AutoConfigurationImportEvent事件
	//目的告诉ConditionEvaluationReport条件评估报告器记录符合条件的自动配置类
	//什么时候被触发----刷新容器时调用invokeBeanFactoryPostProcessor后置处理器时触发
	fireAutoConfigurationImportEvents(configurations, exclusions);
	//将符合条件的和要排除的自动配置类封装对象并返回
	return new AutoConfigurationEntry(configurations, exclusions);
}

看下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 方法,这个方法就是让SpringFactoryLoader 去加载一些组件的名字。进入方法内部

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

从代码中可以看出,这个方法会遍历整个ClassLoader所有jar包下的spring.factories文件,文件里保存着springboot默认提供的自动配置类。
META-INF/spring.factories部分内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\

总结下AutoConfigurationEntry 主要做的事情:
1.从spring.factories配置文件中加载EnableAutoConfiguration 自动配置类
2.若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类 排除掉;
3.排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些 不符合条件的自动配置类;
4.经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
5.后再将符合条件的自动配置类返回。

然后看一下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;
	//拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition 然后遍历去过滤spring.factories加载的大量配置类
	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
		invokeAwareMethods(filter);
		  // 各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的   
		  //@ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里 面的注解值)是否匹配,          
		  //注意candidates数组与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);
}

filter方法主要做的事情是调用AutoConfigurationImportFilter 接口的match方法来判断每一个自动配置类上的条件注解是否满足条件。

关于条件注解的讲解
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册 bean。 @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。 @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。 @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式 的条件判断。 @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。 @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。 @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。 @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。 @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。 @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。 @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。 @ConditionalOnResource:当类路径下有指定的资源时触发实例化。 @ConditionalOnJndi:在JNDI存在的条件下触发实例化。 @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首 选的Bean时触发实例化。

接下来有选择的导入自动配置类。
跟着断点走进this.group.selectImport方法,代码如下:

public Iterable<Entry> selectImports() {
	if (this.autoConfigurationEntries.isEmpty()) {
		return Collections.emptyList();
	}
	//得到所有要排除的自动配置类的set集合
	Set<String> allExclusions = this.autoConfigurationEntries.stream()
			.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
	Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
			.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
			.collect(Collectors.toCollection(LinkedHashSet::new));
	processedConfigurations.removeAll(allExclusions);

	return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
			.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
			.collect(Collectors.toList());
}

总结:
springboot自动配置主要做了从spring.factories配置文件中加载自动配置类;加载的自动配置类中排除掉@EnableAutoConfiguration注解的 exclude 属性指定的自动配置类; 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评 估报告器对象来分别记录符合条件和 exclude 的自动配置类;后spring再将后筛选后的自动配置类导入IOC容器中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值