关于SpringBoot的自动配置原理

一. 加载自动配置类

@SpringBootApplication
public class SpringBoot01ConfigAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBoot01ConfigAutoconfigApplication.class, args);
    }
}

在springboot给我们生成的启动类中我们可以看到这样一个注解**@SpringBootApplication**,这个注解定义的内容为

//@SpringBootApplication注解的内容
@Target(ElementType.TYPE)  //元注解
            //@Target(ElementType.TYPE)   //接口、类、枚举
            //@Target(ElementType.FIELD) //字段、枚举的常量
            //@Target(ElementType.METHOD) //方法
            //@Target(ElementType.PARAMETER) //方法参数
            //@Target(ElementType.CONSTRUCTOR)  //构造函数
            //@Target(ElementType.LOCAL_VARIABLE)//局部变量
            //@Target(ElementType.ANNOTATION_TYPE)//注解
            //@Target(ElementType.PACKAGE) ///包
@Retention(RetentionPolicy.RUNTIME)   //元注解
                //1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
                //2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
                //3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Documented  //元注解
          //在自定义注解的时候可以使用@Documented来进行标注,如果使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。      
@Inherited    //元注解
   //类继承关系中@Inherited的作用
   //类继承关系中,子类会继承父类使用的注解中被@Inherited修饰的注解
@SpringBootConfiguration      //表示这是一个springboot的注解类
@EnableAutoConfiguration      **//开启自动配置功能**
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

这里有一个很重要的注解,就是开启了自动配置功能的注解**@EnableAutoConfiguration**,紧接着,我们去看看这个注解里面定义了什么

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage      // 添加该注解的类所在的package 作为 自动配置package 进行管理。
@Import(AutoConfigurationImportSelector.class)
//将这个类导入到容器中
public @interface EnableAutoConfiguration {

这里把AutoConfigurationImportSelector.class这个类加入到了容器中,我们再去看看这个方法里卖弄做了什么
重点看AutoConfigurationImportSelector的selectImports方法

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

可以看到在这个方法里面获得了一个AutoConfigurationEntry类的对象,并且通过该对象的方法getConfigurations()返回了一个string数组
StringUtils.toStringArray方法如下:

	public static String[] toStringArray(@Nullable Collection<String> collection) {
		return (!CollectionUtils.isEmpty(collection) ? collection.toArray(EMPTY_STRING_ARRAY) : EMPTY_STRING_ARRAY);
	}

这里我们就要去注意AutoConfigurationEntry这个类是什么类,以及它里面封装了什么
接下来我们可以看到AutoConfigurationEntry是AutoConfigurationImportSelector的一个内部类,以下是它的源码:

protected static class AutoConfigurationEntry {
		private final List<String> configurations;
		private final Set<String> exclusions;
		private AutoConfigurationEntry() {
			this.configurations = Collections.emptyList();
			this.exclusions = Collections.emptySet();
		}

这里封装了两个集合类型的成员变量,从名字上可以看出,这两个集合类型应该是封装着配置信息和不需要配置的信息
看完了内部类AutoConfigurationEntry,我们再来看autoConfiguraationEntry这个内部类的对象调用的getConfigurations()方法是干嘛的,源码附上:

		public List<String> getConfigurations() {
			return this.configurations;
		}

在这里可以看到该方法返回了一个List集合,但是这里并不能看出什么,就是返回它的类属性,所以我们回到selectImports方法中,看看autoConfiguraationEntry这个内部类的这些信息是哪里来的

selectImports()方法中有这样一句,我们点进去看看

AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);

以下是getAutoConfigurationEntry()方法的源码

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		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);
	}

在这里我们可以看到这样两行

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);

接下来我们就分析这两个方法是怎么样拿到这些配置信息并封装到autoConfiguraationEntry这个类中的
先分析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;
	}

我们可以看到这样一句

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());

我们点进去看看
这是loadFactoryNames的源码 注意,现在已经是在SpringFactoriedLoader类下了

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

我们来看看它返回的这个list集合是从哪里得到的,于是我们点进loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					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 factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

在这里我们终于找到了一点线索,这里面有加载资源并封装的操作,我们接着看
第一句:

Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

这里表示使用类加载器去FACTORIES_RESOURCE_LOCATION这个路径下加载文件,要是传入的类加载器为空,那么就去加载FACTORIES_RESOURCE_LOCATION这个路径下的文件,这里我们应该直到,这两个字符串都代表着字符串常量,于是我们去揭开庐山真面目

public final class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

我们可以看到,loadSpringFactories()方法中去加载META-INF/spring.factories的资源,并封装为一个properties,最后封装为LinkedMultiValueMap<>()对象并返回,到这一步,springBoot自动配置就拿到了需要配置的资源
我们可以去看看这些资源
jar包类路径下的spring.factories资源
我们进去其中去看看

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
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,\

这里面我们可以看到有很多资源
如:
org.springframework.context.ApplicationContextInitializer
org.springframework.context.ApplicationListener
org.springframework.boot.autoconfigure.AutoConfigurationImportListener
org.springframework.boot.autoconfigure.EnableAutoConfiguration等等资源,那是不是这写资源全部都要自动加载入容器中呢?
肯定不是的
使用内部工具 SpringFactoriesLoader,查找classpath上所有jar包中的 META-INF\spring.factories,找出其中key为
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的属性定义的过滤器类并实例化。AutoConfigurationImportFilter过滤器可以被注册到 spring.factories用于对自动配置类做一些限制,在这些自动配置类的字节码被读取之前做快速排除处理。 spring boot autoconfigure 缺省注册了一个 AutoConfigurationImportFilter :org.springframework.boot.autoconfigure.condition.OnClassCondition

当这些自动配置类进入到容器的时候,它们就可以执行自动配置的功能了
比如以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

@Configuration   //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class)  //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中

@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;    判断当前应用是否是web应用,如果是,当前配置类生效

@ConditionalOnClass(CharacterEncodingFilter.class)  //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;

@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)  //判断配置文件中是否存在某个配置  spring.http.encoding.enabled;如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
  
  	//他已经和SpringBoot的配置文件映射了
  	private final HttpEncodingProperties properties;
  
   //只有一个有参构造器的情况下,参数的值就会从容器中拿
  	public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
		this.properties = properties;
	}
  
    @Bean   //给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}

根据当前不同的条件判断,决定这个配置类是否生效
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类

@ConfigurationProperties(prefix = "spring.http.encoding")  //从配置文件中获取指定的值和bean的属性进行绑定
public class HttpEncodingProperties {

   public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

写的还不完全,也不一定正确,欢迎各位大佬指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值