2020:day0625 --- SpringBoot 自动配置原理

547 阅读9分钟

1. 自动配置原理

    配置文件到底能写什么?怎么写?
    
    配置文件能配置的属性参照:

配置文件能配置的属性参照

1.1 分析一下具体源码步骤:SpringBoot版本:2.3.1

    (1)SpringBoot启动的时候加载主配置类,开启了自动配置功能
       @EnableAutoConfiguration
    
    从主方法进行启动,加载了@SpringBootApplication配置类
    @SpringBootApplication
    public class Day0625Springboot03AutoconfigApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Day0625Springboot03AutoconfigApplication.class, args);
        }
    
    }
    @SpringBootApplication注解最重要的功能,就是开启了@EnableAutoConfiguration
    自动配置。

    (2)@EnableAutoConfiguration:开启了自动配置。
    
    分析一下它的作用:
    @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 {};
    }
    @Import({AutoConfigurationImportSelector.class})
    
        @EnableAutoConfiguration利用其中的选择器,
    AutoConfigurationImportSelector.class来给spring容器中来导入一些组件。
        @Import({AutoConfigurationImportSelector.class})注解的意思就是给
    spring容器导一些组件,导哪些组件由AutoConfigurationImportSelector.class
    来指定。
    
    (3) 接下来分析AutoConfigurationImportSelector.class
        分析一下给容器导入了哪些组件
public String[] selectImports(AnnotationMetadata annotationMetadata)
        A:该类中有一个selectImports()方法。
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
        会得到一个autoConfigurationEntry对象,然后该对象调用getConfigurations()
    方法。将得到的值转成的字符串数组返回。
        
        B:点进getAutoConfigurationEntry()方法
	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<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

            得到了一个名为configurations的list。
            这个方法得到的是所有候选的配置。
        
        C:点进该方法看一下:
	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方法。

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

        D:我们点进去看
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    其中这段代码:

return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());

    返回值中的这个(List)loadSpringFactories(classLoader)方法就在下面

        E:loadSpringFactories
    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();
        注意:
            发现其中的classLoader.getResources("META-INF/spring.factories")
            从类路径下得到一个资源:
        
        扫描所有jar包下类路径下的的这些文件:
            META-INF/spring.factories
            
        把这些文件的url得到,放到枚举中:

Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

        接着:遍历枚举,将其中的url通过loadProperties加载成properties对象。
            意味着扫描到的这些文件的内容会被包装成properties对象。
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());
    }
}
return result;
    F:loadSpringFactories()方法返回的result(保含properties信息)
    返回给了loadFactoryNames()
    
    G:loadFactoryNames()返回了一个list给了getCandidateConfigurations().
    getCandidateConfigurations()只取了返回的list其中的
    getSpringFactoriesLoaderFactoryClass()部分。
    
    H:getSpringFactoriesLoaderFactoryClass()是
        EnableAutoConfiguration.class
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}
    从properties中的获取到EnableAutoConfiguration.class类(类名)对应
的值然后把它们添加在容器中。

    I:我们查看一下导入的jar包中:
        META-INF/spring.factories

        我们找到了EnableAutoConfiguration.class类(类名)对应的值。
        
        相当于把这些组件放到了容器中。
        
小结:将类路径下的 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration
      的值添加到容器中。
      每一个这样的xxxxAutoConfiguration类都是容器中的一个组件,都加入到
      容器中,用他们来做自动配置。
      
      只有这些自动配置类进到容器中,自动配置类注解:
      @EnableAutoConfiguration才能起作用。
      然后自能配置类实现自动配置功能。

2. 以HttpEncodingAutoConfiguration为例分析自动配置

    什么是配置功能?我们以自动配置类为例:
    
    以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理。
    
    1.  点进去里面有几个注解
@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 {

	@Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
		return filter;
	}
	@Bean
	public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
		return new LocaleCharsetMappingsCustomizer(this.properties);
	}
}
       A: @Configuration表示这是一个配置类,好比以前编写的配置文件。一般
    @Configuration标注的类,其中会有多个@Bean注解标注的方法,可以向容器
    中添加组件。@Configuration类似于XML中的<beans/>标签。
    
        B: @EnableConfigurationProperties(ServerProperties.class)启用
    指定类ServerProperties.class的ConfigurationProperties功能。
        点进这个指定类看一下:
            @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
            public class ServerProperties {
            
            	private Integer port;
	        private InetAddress address;
	        ...
	       }
        ServerProperties.class有若干和Http相关属性:
            private Integer port
            private InetAddress address;
        
        里面有一个@ConfigurationProperties这是我们才学过的:从配置文件中获取指定
    的值和ServerProperties类的属性进行绑定注入。
        prefix = "server" : 说明要注入的是值,是配置文件中的server键对应的值。
        
        也就是说ServerProperties.class类中属性通过@ConfigurationProperties
    注解,和配置文件中的值进行绑定注入。这些属性就是HttpEncodingAutoConfiguration
    能配置的信息。
    
    
    @EnableConfigurationProperties(ServerProperties.class)小结:
        HttpEncodingAutoConfiguration配置类通过@EnableConfigurationProperties(ServerProperties.class)
    注解,启动指定类ServerProperties.class的功能,再将其中的属性值和HttpEncodingAutoConfiguration绑定起来。
    
        
        C:@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
        第三个注解:其中@Conditional是Spring底层注解,根据不同的条件可以进行条件判
    断。如果满足指定的条件,整个配置类里面的配置就会生效。
        
        判断的条件:OnWebApplication,当前应用是不是web应用。
        如果是当前HttpEncodingAutoConfiguration配置类生效,否则不生效。
        
        D: @ConditionalOnClass(CharacterEncodingFilter.class)
        第四个注解:判断当前项目有没有CharacterEncodingFilter类。显然这是SpringMVC
    中解决乱码的过滤器。
    
        E:@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
        第五个注解:判断配置文件中是否存在server.servlet.encoding.enabled的配置。
    matchIfMissing = true,意思即使不存在该配置也生效。
        即使配置文件中不配置server.servlet.encoding.enabled=true,该配置类也默认
    生效的。
    
    所以自动配置类HttpEncodingAutoConfiguration根据以上三个不同条件进行判断,决定
这个配置类是否生效。

    
    2. 当自动配置类生效后
    
    A:会将标注了@Bean的方法的返回值注入到IOC中。
        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);
    	}
    B:更科学的是,这些注入到IOC中的返回值,是从properties中获取的。而properties
 是由第二个注解@EnableConfigurationProperties(ServerProperties.class)来开启的。
    这个属性已经和SpringBoot配置文件映射了。
    
    HttpEncodingAutoConfiguration只有一个构造方法:
        public HttpEncodingAutoConfiguration(ServerProperties properties) {
    		this.properties = properties.getServlet().getEncoding();
    	}
    C:那么只有一个构造方法的情况下,参数的值就会从容器中拿。怎么拿到呢?
    通过第二个注解:@EnableConfigurationProperties(ServerProperties.class)
它启用了指定类ServerProperties功能,而ServerProperties通过@ConfigurationProperties
注解将自生属性值和SpringBoot配置文件绑定起来。

    所以@EnableConfigurationProperties(ServerProperties.class)开启了
ServerProperties类的绑定功能,并将其注入到IOC容器中。

    然后HttpEncodingAutoConfiguration自动配置类,就可以从容器中取ServerProperties
类中的Encoding properties传给构造方法的参数。
    properties.getServlet().getEncoding();

    D:构造方法生效后,继续从Encoding properties中获取如下值。
        this.properties.getCharset().name()
        this.properties.shouldForce(Encoding.Type.REQUEST)
        this.properties.shouldForce(Encoding.Type.RESPONSE)
    可以在Encoding类中找到这些对应的属性值:
        public Charset getCharset() {
            return this.charset;
        }
        public boolean shouldForce(Encoding.Type type) {
        }

3. 整体再看一下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 {

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

	}

}
1.  首先指明了这是一个配置类:
    @Configuration(proxyBeanMethods = false)
    
2.  开启了一个ServerProperties功能:和绑定配置文件内容
    @EnableConfigurationProperties(ServerProperties.class)
    
    能绑定前缀是server的配置文件中的内容
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    所以这个server前缀对应的默认配置,如果我们想改的话,可以在主配置文件中修改。

3.  满足了后面三个注解的条件后,这个配置类就生效了。
    给IOC容器中添加一些组建。 @Bean标注的方法的返回值,会被放到容器中。
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
4. 这些返回值的属性是从对应的ServerProperties种获取的。

    ServerProperties里的每一个属性有事和配置文件绑定的,那么如果我们
想要修改这些默认配置的属性,就可以在主配置文件中修改。

    所以自动配置类添加了哪些组件,这些组件涉及到ServerProperties的哪些属性,就是
我们可以为这个自动配置类在主配置文件配置进行修改的内容。

5. 精髓
    
    SpringBoot启动会加载大量的配置类。
    我们看一下我们需要的功能有没有SpringBoot默认写好的自动配置类
    
    我们再来看看这个自动配置类向IOC中添加了哪些组件。(关键点)
    如果这些组件满足了我得需求,我就不配了,否则在主配置文件进行配置。
    (因为properties类和主配置文件进行了绑定。)
    
    自动配置给容器中类添加组件时,会从properties类中获取某些属性,我们就可以
在主配置文件中指定这些属性的值。

    xxxAutoconfiguration:自动配置类,给容器添加组件
    
    xxxProperties: 封装配置文件中的相关属性,组件需要的属性来自xxxProperties

4. @Conditional派生注解

1.  @Conditional
    (Spring的原生注解@Conditional)
    
    作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置类里面的内容才能生效。
    

2. @Conditional派生注解

    SpringBoot中有很多@Conditional的派生注解,我们来看一下:

    自动配置类正式在这些@Conditional注解的约束下,有选择的进行自动配置注入。
    
3.  我们怎么知道哪些自动配置类生效了呢?
    
    只有知道了哪些自动配置类生效了,我们才能知道注入了哪些组件,做了什么事。
    
    
    我们可以通过在主配置文件配置:
        debug=true 属性来开启开启SpringBoot的debug
        
    控制台会打印一份
        CONDITIONS EVALUATION REPORT
        条件评估报告
        显示自动配置类的生效情况。
        
    其中:
        Positive matches:是生效的自动配置类
        Negative matches:是没有生效的。