【Spring Boot】自动配置原理

Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。
Spring Boot的配置文件
初识Spring Boot时我们就知道,Spring Boot有一个全局配置文件:application.properties或application.yml。

我们的各种属性都可以在这个文件中进行配置,最常配置的比如:server.port、logging.level. 那它是如何配的呢 如何写的呢?

先从pom工程说起
pom.xml在父工程中的核心依赖 spring-boot-dependencies

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

点击进spring-boot-dependencies.pom

<properties>
    <activemq.version>5.15.13</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.82</appengine-sdk.version>
    <artemis.version>2.12.0</artemis.version>
    <aspectj.version>1.9.6</aspectj.version>
    <assertj.version>3.16.1</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.3</awaitility.version>
    .....

我们写或者引入一些spring依赖的时候,不需要指定版本,因为有版本仓库

  • 启动器
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • spring-boot-starter:场景启动器,当我们要使用web模块的时候,它帮我们导入web模块的相关依赖,当然,依赖的版本都受父项目仲裁

  • Spring Boot将所有的功能场景抽取出来,做成一个starter,
    只需在项目里引入这些starter所有的相关的依赖就会
    导入进来,需要什么功能就导入什么场景的starter。

主程序

//@SpringBootApplication标注这个类是个SpringBoot应用
@SpringBootApplication
public class Springboot01Application {
    public static void main(String[] args) {
        //启动 
        SpringApplication.run(Springboot01Application.class, args);
    }
}

点进@SpringBootApplication注解
在这里插入图片描述
标准注解我们无需关注

  • @ComponentScan(): 扫描指定的包

核心注解

  • @SpringBootConfiguration:SpringBoot的配置
  • @EnableAutoConfiguration:自动配置

点进@SpringBootConfiguration
在这里插入图片描述

再点进@Configuration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
    boolean proxyBeanMethods() default true;
}

我们发现这个配置是由@Configuration配置起来的,@Configuration(Spring配置类)说明启动类就是个配置类
@Component:是一个spring组件

@EnableAutoConfiguration自动配置

作用AutoConfigurationImportSelector给容器导入一些组件,

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};

}
  • @AutoConfigurationPackage:自动配置包 进入
    在这里插入图片描述

@Import(AutoConfigurationPackages.Registrar.class) 导入选择器,那到底导入哪些选择器?

Registrar

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        //注册metadata元数据
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

@Import(AutoConfigurationImportSelector.class)导入自动选择配置

	private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

	private static final String[] NO_IMPORTS = {};

	private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);

	private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

	private ConfigurableListableBeanFactory beanFactory;

	private Environment environment; //环境

	private ClassLoader beanClassLoader;

	private ResourceLoader resourceLoader;//资源加载器

	private ConfigurationClassFilter configurationClassFilter;
    
    //选择组件 选择配置的pom.xml的东西
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//加载元数据
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

核心方法

List configurations = getCandidateConfigurations(annotationMetadata, attributes);:获取候选的配置*

** getCandidateConfigurations()获取候选的配置**

loadFactoryNames通过传入的EnableAutoConfiguration的类,并将类名和类加载器传入loadSpringFactories这个方法,通过loadSpringFactories这个方法,获得了一个值并转换成List,这个值是什么呢?(后面我们可以看到这是一个map)
这里先简单解释一下getOrDefault,第一个参数是key,这里是factoryTypeName;第二个参数是defaultValue,这里是Collections.emptyList()。方法的意思是:如果包含key,则返回它的值,否则返回默认值。这里对应的意思就是,将EnableAutoConfiguration的类名作为key,若包含该key则返回相应的值,若不包含则返回Collections.emptyList(),也就是一个空值
在这里插入图片描述
loadFactoryNames
在这里插入图片描述

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

从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后它们添加在容器中

按照我们框架写的找到jar 包or.springframework.boot:spring-boot-autoconfigure下的META-INF spring.factories(部分截图)
在这里插入图片描述

将类路径META-INF spring.factories里面配置所有EnableAutoConfiguration的值添加到容器中。

每一个这样的xxxAutoConfigruation类都是容器中的一个组件,都加入到容器中,用它们来做自动配置
每一个自动配置类进行自动配置功能

我们以HttpEncodingAutoConfiguration解释自动配置原理 ,因为它比较常见嘛

@Configuration(proxyBeanMethods = false) 
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration
  • Configuration: 这是一个配置类,类似编写配置文件,可以给容器中添加组件
  • @EnableConfigurationProperties(ServerProperties.class):启用指定类ConfigurationProperties功能;将配置文件中对应的值和ServerProperties绑定起来
  • @ConditionalOnWebApplication:底层是@Conditional注解,可以根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效,判断当前应用是否是web应用,如果是 配置类生效
  • ConditionalOnClass(CharacterEncodingFilter.class):判断当前项目有没有这个类,CharacterEncodingFilter是springmvc 中进行乱码解决的过滤器;
  • @ConditionalOnProperty(prefix = “server.servlet.encoding”, value = “enabled”, matchIfMissing = true) :判断配置文件是否存在配置server.servlet.encoding.enabled;
    matchIfMissing = true:即使不配置,也生效

根据当前不同的条件判断决定这个配置类是否生效

当以上条件满足后,加载类HttpEncodingAutoConfiguration

   //已经和SpringBoot的配置文件映射了
	private final Encoding properties;
    
    //只有一个有参构造器,参数的值从容器中拿,EnableConfigurationProperties(ServerProperties.class)把ServerProperties加入到容器中,这边直接拿
    public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}

    @Bean//给容器中添加组件,这个组件的某些值需要从properties中获取
	@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;
	}

根据当前不同条件判断 这个配置类是否生效
如果配置类生效;这个配置类会往容器中添加各种组件;这些组件的属性是对应的properties类中获取的,这些类里面的每个属性又是和属性文件绑定的;

通过yml配置

server:
  servlet:
    encoding:
      enabled: true  #相当于@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
      charset: utf-8
      force: true   #是否进行强制编码

我们能配置的属性都是来源于这个功能的properties类

//能在配置文件中配置的属性都是在xxxProperties类中封装着;配置文件能配置能配什么就可以参照某个功对应的属性类

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) //从配置文件中获取指定的值和bean属性进行绑定
public class ServerProperties {

总结

SpringFactoriesLoader.loadFactoryNames 扫描所有jar包类路径下的META-INF/spring.factories文件()
作用:把扫描到文件的内容包装成Properties对象

result是一个map结构,loadSpringFactories方法的返回值就是这个result,也就是说,loadSpringFactories方法的返回值,是由扫描所有jar包下的"META-INF/spring.factories"文件得到properties对象,再存进(factoryTypeName, factoryImplementationName)这若干个键值对,最后存进result并返回

重点来了! 这时,想起之前的getOrDefault了吗?loadSpringFactories方法的返回值,是由扫描所有jar包下的"META-INF/spring.factories"文件而得到的全部的键值对,而getOrDefault(Object key, V defaultValue)只选择包含key的值返回,其余则返回空值,在这里,key就是EnableAutoConfiguration的类名

xxxAutoConfigurartion:自动配置类;给容器中添加组件;
xxxProperties:封装配置文件中的相关属性;

  • SpringBoot启动会加载大量的自动配置类;
  • 看我们需要的功能有没有SpringBoot默认写好的自动配置类;
  • 再来看自动配置类中到底配置了哪些组件(只要用的组件有,就不需要再来配置),若没有 需要自己配置;
  • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。就可以在配置文件中指定这些属性的值;
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值