logo头像
Snippet 博客主题

Spring Boot自动配置原理剖析

本文于635天之前发表,文中内容可能已经过时

神奇魔法-自动配置

前篇文章讲述了Spring 4 条件注解,其实Spring Boot自动配置神奇实现也是基于这一原理的.
若想知道Spring Boot为我们做了哪些的自动配置,可以查看这里的源码.
可以通过以下三种方式查看项目中已启用和未启用的自动配置报告.

  • 运行jar时增加–debug参数: java -jar xxx.jar –debug
  • 在application.properties中设置属性: debug=true
  • 运行项目时通过IDE设置JVM参数: -Ddebug

启动后,可在控制台输出相关自动配置报告.
已启用的自动配置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

CodecsAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

CodecsAutoConfiguration.JacksonCodecConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

CodecsAutoConfiguration.JacksonCodecConfiguration#jacksonCodecCustomizer matched:
- @ConditionalOnBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found bean 'jacksonObjectMapper' (OnBeanCondition)

DispatcherServletAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
- found ConfigurableWebEnvironment (OnWebApplicationCondition)

......
......
......

未启用的自动配置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Negative matches:
-----------------

ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)

AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice', 'org.aspectj.weaver.AnnotatedElement' (OnClassCondition)

ArtemisAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory' (OnClassCondition)

......
......
......

Spring Boot关于自动配置的源码也是在spring-boot-autoconfigure-1.5.9.RELEASE.jar内,包含的模块配置,如下图:
spring-boot-autoconfigure

Spring Boot条件注解

在spring-boot-autoconfigure-1.5.9.RELEASE.jar的org.springframework.boot.autoconfigure.condition包下,有许多注解组合了@Conditional的元注解,只是使用了不同的条件,形成了新的条件注解:

  • @ConditionalOnBean: 当容器里有指定的Bean的条件下.
  • @ConditionalOnClass: 当classpath下有指定的类的条件下.
  • @ConditionalOnExpression: 基于SpEL表达式作为判断条件.
  • @ConditionalOnJava: 基于JVM版本作为判断条件.
  • @ConditionalOnJndi: 在JNDI村长的条件下查找指定的位置.
  • @ConditionalOnMissingBean:当容器里没有指定的Bean的条件下.
  • @ConditionalOnMissingClass: 当classpath下没有指定的类的条件下.
  • @ConditionalOnNotWebApplication: 当前项目不是Web项目的条件下.
  • @ConditionalOnProperty: 指定的属性是否有指定值的情况下.
  • @ConditionalOnResource: classpath下是否有指定资源的情况下.
  • @ConditionalOnSingleCandidate: 当指定的Bean在容器中只有一个,或虽然有多个但是有指定的首选(@Primary)的Bean
  • @ConditionalOnWebApplication: 当前项目是Web项目的条件下.

@Enable*注解的工作原理

在Spring体系中,有大量的@Enable*的注解:

  • @EnableAspectAutoProxy:用于开启对Aspect自动代理的支持.
  • @EnableAsync:用于开启异步方法的支持.
  • @EnableScheduling:用于开启定时任务的支持.
  • @EnableWebMvc:用于开启WebMvc配置的支持.
  • @EnableConfigurationProperties:用于开启对@ConfigurationProperties注解配置Bean的的支持.
  • @EnableJpaRepositories:用于开启对Spring Data Jpa的的支持.
  • @EnableTransactionManagement:用于开启对注解式事务的的支持.
  • @EnableCaching:用于开启对注解式缓存的的支持.

这些注解用于开启某项功能的支持,从而避免自己配置大量的代码.那么这个神奇的功能的上线原理是什么呢?我们来看看.

通过观察这些@Enable*注解的源码,我们发现所有的注解都有一个@import注解,@import是用来导入配置类的,这也就意味着这些自动开启的实现是导入了一些自动配置的Bean.这些导入的配置方式主要分为以下三种类型.

  1. 第一类:直接导入配置类
1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

直接导入配置类SchedulingConfiguration.class,这个类注解了@Condition,且注册了一个ScheduledAnnotationBeanPostProcessor的Bean,源码如下:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}

}

  1. 第二类:依据条件选择配置类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AsyncConfigurationSelector.class)
    public @interface EnableAsync {
    Class<? extends Annotation> annotation() default Annotation.class;
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default Ordered.LOWEST_PRECEDENCE;
    }

AsyncConfigurationSelector通过条件来选择需要导入的配置类,AsyncConfigurationSelector的根接口为ImportSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] { ProxyAsyncConfiguration.class.getName() };
case ASPECTJ:
return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}
}

自动配置原理

关于Spring Boot的运作原理,我们还是回归到@SpringBootApplication注解上,改注解是一个组合注解.
它是由@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan组成的.
@SpringBootConfiguration也是组合了@Configuration注解,@ComponentScan是组件扫描,而@EnableAutoConfiguration是自动配置功能的核心注解.
我们来看看@EnableAutoConfiguration注解源码:

1
2
3
4
5
6
7
8
9
10
11
12
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};

}

这里的关键功能是@Import注解导入的配置功能,EnableAutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames()方法来扫描具有META-INF/spring.factories文件的jar包.而我们的spring-boot-autoconfigure-1.5.9.RELEASE.jar就有一个spring.factories文件.此文件中声明了有哪些自动配置,如下图:
META-INF/spring.factories文件

实例分析

在了解Spring Boot的运作原理和主要条件注解后,现在我们来分析一个简单的Spring Boot内置的自动配置功能:http的编码配置.
在常规web的war项目中,我们要解决http编码的时候是在web.xml里配置一个filter,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 编码Filter 加载配置 Start -->  
<filter>
<filter-name>CharacterEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

自动配置要满足两个条件:

  • 能配置CharacterEncodingFilter这个Bean;
  • 能配置encoding和forceEncoding这两个参数.
  1. 源码中定义的HttpEncodingProperties配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    package org.springframework.boot.autoconfigure.http;

    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.util.Locale;
    import java.util.Map;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    // 在application.properties或application.ynl文件配置时前缀必须是spring.http.encoding
    @ConfigurationProperties(prefix = "spring.http.encoding")
    public class HttpEncodingProperties {

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    /** 默认编码方式为UTF-8,若修改可使用spring.http.encoding.charset=编码 */
    private Charset charset = DEFAULT_CHARSET;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;

    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(Type type) {
    Boolean force = (type == Type.REQUEST ? this.forceRequest : this.forceResponse);
    if (force == null) {
    force = this.force;
    }
    if (force == null) {
    force = (type == Type.REQUEST);
    }
    return force;
    }

    public enum Type {
    REQUEST, RESPONSE
    }
    }
  2. 源码:HttpEncodingAutoConfiguration自动配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    package org.springframework.boot.autoconfigure.web;

    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
    import org.springframework.boot.autoconfigure.web.HttpEncodingProperties.Type;
    import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
    import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.boot.web.filter.OrderedCharacterEncodingFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.web.filter.CharacterEncodingFilter;

    @Configuration
    @EnableConfigurationProperties(HttpEncodingProperties.class) //开启属性注入
    @ConditionalOnWebApplication //当前项目是Web应用触发
    @ConditionalOnClass(CharacterEncodingFilter.class) //当classpath下存在CharacterEncodingFilter.class时加载该类
    @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //当spring.http.encoding=enable的情况下,如果没有设置则默认为true,即条件符合
    public class HttpEncodingAutoConfiguration {


    private final HttpEncodingProperties properties;

    /** 通过构造方法注入 */
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
    this.properties = properties;
    }

    @Bean //使用Java的方式声明Bean
    @ConditionalOnMissingBean(CharacterEncodingFilter.class) //当Spring容器中没有这个Bean的时候新建Bean.
    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;
    }

    }

    }
支付宝打赏 微信打赏

请作者喝杯咖啡吧