关于SpringBoot的自动配置原理

it2026-03-27  6

一. 加载自动配置类

@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自动配置就拿到了需要配置的资源 我们可以去看看这些资源 我们进去其中去看看

# 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)