SpringBoot的自动配置原理

334 阅读5分钟

SpringBoot的自动配置原理

一、简介

不知道大家第一次搭SpringBoot环境的时候,有没有觉得非常简单。无须各种的配置文件,无须各种繁杂的pom坐标,一个main方法,就能run起来了。与其他框架整合也贼方便,使用EnableXXXXX注解就可以搞起来了!

所以今天来讲讲SpringBoot是如何实现自动配置的~

二、原理

自动配置流程图

www.processon.com/view/link/5…

源码的话就先从启动类开始入手, @SpringBootApplication

SpringBootApplication标注在某个类上,说明这个类是SpringBoot的主配置类,SpringBoot 需要运行这个类的main方法来启动SpringBoot应用。

该注解是一个组合注解,下面看下它的注解

@Target(ElementType.TYPE)  // 设置当前注解可以标记在哪
@Retention(RetentionPolicy.RUNTIME) // 当注解标注的类编译以什么方式保留,RUNTIME表示会被jvm加载
@Documented // java doc 会生成注解信息
@Inherited // 是否会被继承
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {// 省略。。。}

注解说明:

@SpringBootConfiguration

标注在某个类上,表示这是一个Spring Boot的配置类。由@Configuration(相当于spring中配置bean的xml)组成。阅读过spring源码都知道,这玩意会被后置处理器来分析类上面的注解。如分析EnableAutoConfiguration

@EnableAutoConfiguration

@EnableAutoConfiguration告诉SpringBoot开启自动配置,springboot会帮我们自动去加载自动配置类

@ComponentScan

扫描包.相当于在spring.xml 配置中context:comonent-scan 但是并没有指定basepackage,如果没有指定spring底层会自动扫描当前配置类所有在的包

TypeExcludeFilter

springboot对外提供的扩展类, 可以供我们去按照我们的方式进行排除

AutoConfigurationExcludeFilter

排除所有配置类并且是自动配置类中里面的其中一个

@EnableAutoConfiguration【重点】

这个注解里面,最主要的就是@EnableAutoConfiguration,这么直白的名字,一看就知道它要开启自动配置,SpringBoot要开始骚了,于是默默进去@EnableAutoConfiguration的源码

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @AutoConfigurationPackage
6 @Import(EnableAutoConfigurationImportSelector.class)
7 public @interface EnableAutoConfiguration {
8 // 略
9 }

@AutoConfigurationPackage

@AutoConfigurationPackage
// 保存扫描路径, 提供给spring‐data‐jpa 需要扫描 @Entity
@Import(AutoConfigurationPackages.Registrar.class) 
// 就是注册了一个保存当前配置类所在包的一个Bean
public @interface AutoConfigurationPackage {}

将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用。

一句话:@AutoConfigurationPackage 注解就是将主配置类(@SpringBootConfiguration标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器中。所以说,默认情况下主配置类包及子包以外的组件,Spring 容器是扫描不到的。

@Import(EnableAutoConfigurationImportSelector.class) 【关键点】

可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,而

EnableAutoConfigurationImportSelector 实现了DeferredImportSelectorSpring内部在解析@Import注解时会调用getAutoConfigurationEntry方法,这块属于Spring的源码,有点复杂,我们先不管它是怎么调用的。

下面是2.3.5.RELEASE 实现源码:

getAutoConfigurationEntry方法进行扫描具有META-INF/spring.factories文件的jar包。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 从META‐INF/spring.factories中获得候选的自动配置类[重点]
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 排重
    configurations = removeDuplicates(configurations);
    //根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 根据:@EnableAutoConfiguration.exclude
    // @EnableAutoConfiguration.excludeName
    // spring.autoconfigure.exclude 进行排除
    checkExcludedClasses(configurations, exclusions);
    // exclusions 也排除
    configurations.removeAll(exclusions);
    // 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
    configurations = getConfigurationClassFilter().filter(configurations);
    // 这个方法是调用实现了AutoConfigurationImportListener的bean..
    //分别把候选的配置名单,和排除的配置名单传进去做扩展
    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;
    }

任何一个springboot应用,都会引入spring-boot-autoconfigure,而spring.factories文件就在该包下面。

spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信

息,而真正使自动配置生效的key是 org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:等同于

@Import({

1 # Auto Configure
2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
3 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
4 ...省略
5 org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
6 org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

})

每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置; 所有自动配置类表 每一个自动配置类进行自动配置功能;

后续: @EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中

举例

以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理

// 表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ServerProperties.class}):;
/*
将配置文件中对应的值和 ServerProperties绑定起来
并把 ServerProperties加入到 IOC 容器中。并注册ConfigurationPropertiesBindingPostProcessor用于将@ConfigurationProperties的类和配置进行绑定ServerProperties
ServerProperties通过 @ConfigurationProperties 注解将配置文件与自身属性绑定。
*/
@EnableConfigurationProperties(ServerProperties.class)
// 判断当前环境是否是web环境,如果是 。配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 判断当前项目有没有这个类CharacterEncodingFilter(SpringMVC中进行乱码解决的过滤器。),如果有,才生效
@ConditionalOnClass(CharacterEncodingFilter.class)
// 如果有下面属性则生效,matchIfMissing = true 没有也生效
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

	private final Encoding properties;

	public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}

	@Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
		return filter;
	}

	@Bean
	public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
		return new LocaleCharsetMappingsCustomizer(this.properties);
	}

	static class LocaleCharsetMappingsCustomizer
			implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

		private final Encoding properties;

		LocaleCharsetMappingsCustomizer(Encoding properties) {
			this.properties = properties;
		}

		@Override
		public void customize(ConfigurableServletWebServerFactory factory) {
			if (this.properties.getMapping() != null) {
				factory.setLocaleCharsetMappings(this.properties.getMapping());
			}
		}

		@Override
		public int getOrder() {
			return 0;
		}

	}

}