006 自动配置

一 .概述

  在springboot之中最令我们喜欢的特性大概就是自动配置了.springboot根据自动配置帮助我们实现整个开发环境的配置,这可以让我们不需要每次都完成那些重复的配置工作了.

  本此,我们就分析一下自动配置的原理.


 二 .程序的启动类  

@SpringBootApplication
public class SpringbootRunnerClass {
    
    public static void main(String[] args) {
        SpringApplication.run(SpringbootRunnerClass.class, args);
    }
}

在我们springboot的程序启动类之中,我们使用了一个注解@SpringBootApplicatiion注解.

我们下面看看这个注解帮助我们完成了什么.  

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

我们发现在这个注解上面带有三个子注解,我们下面来看看这写注解是干什么用的.  

@Configuration
public @interface SpringBootConfiguration {

}

第一个注解,我们发现就是一个配置类,只不过是springvoot特意定义的一个注解而已.  

@Repeatable(ComponentScans.class)
public @interface ComponentScan

第三个注解,我们非常的熟悉了,那就是扫包注解,在springboot之中,默认的扫包策略就是启动包的子包进行扫描.

  现在,我们所有的注意都放在了第二个注解上面.

@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

我们发现在这个注解上面有两个子注解.我们看看他们是干什么用的.

/**
 * Indicates that the package containing the annotated class should be registered with
 * {@link AutoConfigurationPackages}.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

从上面的注视之中,我们可以看到,它帮助我们注册该包的所有的bean.

  我们再来看看帮助我们引入的那个类是干什么用的.

public class EnableAutoConfigurationImportSelector
        extends AutoConfigurationImportSelector {

    @Override
    protected boolean isEnabled(AnnotationMetadata metadata) {
        if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
            return getEnvironment().getProperty(
                    EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
                    true);
        }
        return true;
    }

}

我们看看它的父类:  

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            configurations = sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

我们发现,这个就是一个导入bean的组件的@Import而已,我们现在需要关注的就是到底为我们导入了什么?

    protected static final String PATH = "META-INF/"
            + "spring-autoconfigure-metadata.properties";

我们发现从该配置之中加入了很多了bean.这些bean很多都是一些配置类,基本上都存在springboot的自动配置包下.也就是说,最终这个路径下的配置文件之中的bean大多都会注册到我们的bean之中.  

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

另外我们发现也从这个配置文件之中引入了很多的组件.

  也就是说,springboot会从上面的一些配置文件之中加载很多的bean.  


 三 .自动配置类

  我们从上面看到了springboot几乎为我们加载了自动配置包下面的所有的自动配置类.

  我们看看我们这个包下面到底有些什么东西.

  里面所有的类都是自动配置类,我们看一个简单的就好了. 

@Configuration
@ConditionalOnClass(Gson.class)
public class GsonAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Gson gson() {
        return new Gson();
    }

}

我们发现上面就是一个简单的配置类,如果类路径下面由一个Gson类,而且容器之中没有gson对象,springboot就会帮助我们向容器之中注入一个bean.


四 .@Conditional注解

  在springboot之中(实际上在spring4之中引入),为我们引入了一些条件注解,这些注解会帮助我们在运行环境下确定是否向容器之中注册bean.

  实际上就是一个boolean的判断,如果条件满足,就会注入,否则就不会注入.

  我们看看几个常见的注解:

  [1]ConditionalOnBean : 当容器之中含有这个bean的时候,就会注入

  [2]ConditionalOnClass : 当类路径下有这个class的时候就会注册

  [3]ConditionalOnProperty : 当环境变量之中有这个属性的时候就会注册

  等等等,springboot就是通过这些注解完成的自动的装配.


 五 .自动装配的分析

  我们使用一个比较简单的配置类进行自动配置的一般流程的分析:

@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnWebApplication
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

    private final HttpEncodingProperties properties;

    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }

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

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

    private static class LocaleCharsetMappingsCustomizer
            implements EmbeddedServletContainerCustomizer, Ordered {

        private final HttpEncodingProperties properties;

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

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

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

    }

}

[1]首先,我们发现这个是一个配置类.

[2]@ConditionalOnWebApplication 表示需要在web环境下才注册

[3]@ConditionalOnClass(CharacterEncodingFilter.class) 表示需要在类路径下有一个类才进行注册

[4]@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) 这个表示在配置文件下有这个属性才可以,但是在这里给出了默认值,也就是说一定成立的,除非用户主动取消掉这个配置.

[5]@EnableConfigurationProperties(HttpEncodingProperties.class)  这个注解表示完成对HttpEncodingProperties这个属性配置类进行属性的填充.

下面,我们来分析一下源代码了:

    private final HttpEncodingProperties properties;

    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }

我们从上面的注解之中看到了会向容器之中注册一个HttpEncodingProperties类,然后本自动配置类就实例化了.也就是说,现在的HttpEncodingProperties 的属性被填充好了.

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

表示向容器之中添加一个字符编码过滤器.

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

表示向容器之中添加一个自定义配置器.


 六 .自动配置的基本流程

[1]一般会有一个属性配置类与属性文件进行绑定

[2]然后通过这个属性的对自动配置i类进行属性的填充

[3]然后就是创建不同bean到容器之之中,直到完成整个springboot的自动配置.


 七 .自动配置报告

  我们现在知道了springvoot的自动配置原理了,但是我们看到自动配置非常的复杂,我们如何好能确定一个bean是否已经被初始化好了呢?

  springboot为我们提供了自动配置报告.  

debug=true

我们在主配置文件之中,可以添加如上的配置,然后再启动springboot 的时候就会开启自动配置报告的生成.

  在我们的控制台上就显示了不同的配置报告的信息.

从这里显示的就是springboot帮助我们添加的bean,也就是自动配置成功的类.

下面显示的就是没有自动成功的类.

原文地址:https://www.cnblogs.com/trekxu/p/9739548.html