天天看点

浅谈SpringBoot的自动配置原理一:二:

SpringBoot的运行原理就是基于SpringBoot的自动配置来实现。我们要想熟练的使用SpringBoot这一框架技术,就必须对其深入学习。

一:

选择SpringBoot的依赖版本:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath/>
</parent>
           

首先,我们先来看一下,当我们启动一个SpringBoot项目时,SpringBoot为我们自动启动了哪些自动配置类。

操作(以下三种方式任选其一):

(1)在application.properties中设置属性:

debug=true
           

(2)通过cmd命令窗口运行xxx.jar文件

java -jar xxx.jar --debug
           

(3)在Idea 中设置运行时参数

浅谈SpringBoot的自动配置原理一:二:
浅谈SpringBoot的自动配置原理一:二:

结果:

--------------------

浅谈SpringBoot的自动配置原理一:二:

--------------------

浅谈SpringBoot的自动配置原理一:二:

二:

接下来,我们需要学习一个注解@SpringBootApplication,这个注解是一个组合注解,他的核心功能是由@EnableAutoConfiguration注解提供。

浅谈SpringBoot的自动配置原理一:二:

好的,然后我们来看一下@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类导入至Spring IOC容器。于是我们就进去看看,这个类中有一个selectImports方法。具体源码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }
           

在这个方法中,调用了AutoConfigurationMetadataLoader类的一个静态方法loadMetadata(ClassLoader classLoader)。

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }
           

这个方法用来扫描具有"META-INF/spring-autoconfigure-metadata.properties"文件的jar包,而我们的spring-boot-autoconfigure-2.0.1RELEASE.jar里就有spring.factories文件,然后我们将其打开,查看一下此文件具体有哪些自动配置。如图所示:

浅谈SpringBoot的自动配置原理一:二:

然后我选择一个我们较为熟悉的HttpEncodingAutoConfiguration类

@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties({HttpEncodingProperties.class}) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@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 {
    private final HttpEncodingProperties properties;
    
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }

    @Bean    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @ConditionalOnMissingBean      //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
        return filter;
    }

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

    private static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
        private final HttpEncodingProperties properties;

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

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

        }

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

以上源码就是根据当前不同的条件判断,决定这个配置类是否生效?一但这个配置类生效 ;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

 

所有在配置文件中能配置的属性都是在XxxxProperties类中封装者;配置文件能配置什么就可以参照某个功

能对应的这个属性类,比如:

@ConfigurationProperties(
    prefix = "spring.http.encoding"
)        //从配置文件中获取指定值和bean的属性进行绑定
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET;
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;

    public HttpEncodingProperties() {
        this.charset = DEFAULT_CHARSET;
    }

    public Charset getCharset() {
        return this.charset;
    }

    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    public boolean isForce() {
        return Boolean.TRUE.equals(this.force);
    }

    public void setForce(boolean force) {
        this.force = force;
    }

    public boolean isForceRequest() {
        return Boolean.TRUE.equals(this.forceRequest);
    }

    public void setForceRequest(boolean forceRequest) {
        this.forceRequest = forceRequest;
    }

    public boolean isForceResponse() {
        return Boolean.TRUE.equals(this.forceResponse);
    }

    public void setForceResponse(boolean forceResponse) {
        this.forceResponse = forceResponse;
    }

    public Map<Locale, Charset> getMapping() {
        return this.mapping;
    }

    public void setMapping(Map<Locale, Charset> mapping) {
        this.mapping = mapping;
    }

    public boolean shouldForce(HttpEncodingProperties.Type type) {
        Boolean force = type == HttpEncodingProperties.Type.REQUEST ? this.forceRequest : this.forceResponse;
        if (force == null) {
            force = this.force;
        }

        if (force == null) {
            force = type == HttpEncodingProperties.Type.REQUEST;
        }

        return force;
    }

    static {
        DEFAULT_CHARSET = StandardCharsets.UTF_8;
    }

    public static enum Type {
        REQUEST,
        RESPONSE;

        private Type() {
        }
    }
}
           

总结:

1) SpringBoot启动会加载大量的自动配置类

2) 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;

3) 我们再来看这个自动配置类中到底配置了哪些组件; (只要我们要用的组件有,我们就不需要再来配置了)

4) 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;

5) xxxxAutoConfigurartion :自动配置类;给容器中添加组件

    xxxxProperties:封装配置文件中相关属性;