SpringBoot 自动配置原理

自动配置原理

Spring Boot 项目创建完成后,即使不进行任何的配置,也能够顺利地运行,这都要归功于 Spring Boot 的自动化配置。

Spring Boot 默认使用 application.properties 或 application.yml 作为其全局配置文件,我们可以在该配置文件中对各种自动配置属性(server.port、logging.level.* 等等)进行修改,并使之生效,那么您有没有想过这些属性是否有据可依呢?答案是肯定的。

Spring Boot 官方文档:常见应用属性 中对所有的配置属性都进行了列举和解释,我们可以根据官方文档对 Spring Boot 进行配置,但 Spring Boot 中的配置属性数量庞大,仅仅依靠官方文档进行配置也十分麻烦。我们只有了解了 Spring Boot 自动配置的原理,才能更加轻松熟练地对 Spirng Boot 进行配置。

Spring Boot 自动化配置也是基于 Spring Factories 机制实现的,在 spring-boot-autoconfigure-xxx.jar 类路径下的 META-INF/spring.factories 中设置了 Spring Boot 自动配置的内容

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
......

以上配置中,value 取值是由多个 xxxAutoConfiguration (使用逗号分隔)组成,每个 xxxAutoConfiguration 都是一个自动配置类。Spring Boot 启动时,会利用 Spring-Factories 机制,将这些 xxxAutoConfiguration 实例化并作为组件加入到容器中,以实现 Spring Boot 的自动配置。

步骤

  1. SpringBoot启动的时候加载主配置类,@EnableAutoConfiguration注解开启了自动配置功能。

  2. @EnableAutoConfiguration 作用 (getAutoConfigurationEntry-->getCandidateConfigurations)

扫描所有jar包类路径下 META-INF/spring.factories把扫描到的这些文件的内容包装成properties对象从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用他们来做自动配置。

3.对每一个自动配置类进行自动配置功能。

4.以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

//表示这是一个配置类,跟以前编写的配置文件一样,也可以给容器中添加组件
 @Configuration(
     //proxyBeanMethods为flase,配置类不会被代理了。主要是为了提高性能,如果你的 @Bean 方法之间没有调用关系的话可以把 proxyBeanMethods 设置为 false
    proxyBeanMethods = false 
)

//启动指定类的ConfigurationProperties功能;将配置文件中对应的值和ServerProperties绑定起来;并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties({ServerProperties.class})

//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置  server.servlet.encoding.enabled;如果不存在,判断也是成立的,即使我们配置文件中不配置server.servlet.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "server.servlet.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {

     //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;

     //只有一个有参构造器的情况下,参数的值就会从容器中拿
    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(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
        return filter;
    }

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

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

        LocaleCharsetMappingsCustomizer(Encoding 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类中获取的,这些类里面的每一个属性又是和配置文件绑定的。

5、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者,配置文件能配置什么就可以参照某个功能对应的这个属性类

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private ServerProperties.ForwardHeadersStrategy forwardHeadersStrategy;
    private String serverHeader;
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);
    private Shutdown shutdown;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression;
    @NestedConfigurationProperty
    private final Http2 http2;
    private final ServerProperties.Servlet servlet;
    private final ServerProperties.Tomcat tomcat;
    private final ServerProperties.Jetty jetty;
    private final ServerProperties.Netty netty;
    private final ServerProperties.Undertow undertow;
    .....
    }

总结

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

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

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

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

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

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

@Conditional条件注解

作用

必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效。其最终实现的效果或者语义类似于如下伪代码:

if(符合 @Conditional 规定的条件){
    加载当前配置(enable current Configuration)或者注册当前bean定义;
}

要实现基于条件的配置,我们只要通过 @Conditional 指定自己的 Condition 实现类就可以了(可以应用于类型 Type 的标注或者方法 Method 的标注)

示例

条件

 //判断当前系统是否是windows系统   实现Condition方法matches  
   public class WindowCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return conditionContext.getEnvironment().getProperty("os.name").contains("Windows");
    }
}

接口

public interface OsService {
   public void working();
}

配置类

@Configuration
public class EnvConfig {
    @Bean
    @Conditional(WindowCondition.class)
    public OsService getOsService(){
       return new OsService() {
           @Override
           public void working() {
              System.out.println("系统在window系统下工作....");
           }
       };
    }
}

测试

@SpringBootApplication
@Import(EnvConfig.class)
public class DemoApplication {
   public static void main(String[] args) {
      ConfigurableApplicationContext context= SpringApplication.run(DemoApplication.class,args);
      OsService osService=context.getBean(OsService.class);
      osService.working();
   }
}

最主要的是,@Conditional 可以作为一个 Meta Annotation 用来标注其他 Annotation 实现类,从而构建各色的复合 Annotation,比如 SpringBoot 的 autoconfigure 模块就基于这一优良的革命传统,实现了一批 Annotation(位于 org.springframework.boot.autoconfigure.condition 包下),条件注解如下:

注解

作用

@ConditionalOnBean

当容器里有指定的 Bean 的条件下。

@ConditionalOnClass

当类路径下有指定的类的条件下。

@ConditionalOnExpression

基于 SpEL 表达式作为判断条件。

@ConditionalOnJava

基于 JVM 版本作为判断条件。

@ConditionalOnJndi

在 JNDI 存在的条件下查找指定的位置。

@ConditionalOnMissingBean

当容器里没有指定 Bean 的情况下。

@ConditionalOnMissingClass

当类路径下没有指定的类的条件下。

@ConditionalOnNotWebApplication

当前项目不是 Web 项目的条件下。

@ConditionalOnProperty

指定的属性是否有指定的值。

@ConditionalOnResource

路径下是否存在指定资源文件

@ConditionalOnSingleCandidate

当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选的 Bean。

@ConditionalOnWebApplication

当前项目是 Web 项目的条件下。

有了这些复合 Annotation 的配合,我们就可以结合 @EnableAutoConfigurationn 实现基于条件的自动配置了。SpringBoot 能够风靡,很大一部分功劳需要归功于它预先提供的一系列自动配置的依赖模块,而这些依赖模块都是基于以上 @Conditional 复合 Annotation 实现的,这也意味着所有的这些依赖模块都是按需加载的,只有符合某些特定条件,这些依赖模块才会生效,这也就是我们所谓的“智能”自动配置。

debug模式

我们怎么知道哪些自动配置类生效?

我们可以通过在properties(yml)启用 debug=true 属性来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。

debug=true

运行结果:

============================
CONDITIONS EVALUATION REPORT
============================


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

   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

   AopAutoConfiguration.ClassProxyingConfiguration matched:
      - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
      - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)

   DispatcherServletAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
      - found 'session' scope (OnWebApplicationCondition)

...

Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

   AopAutoConfiguration.AspectJAutoProxyingConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice' (OnClassCondition)

   ArtemisAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
...

自动配置的顺序

三大注解:

注解

作用

@AutoConfigureBefore

让当前配置或者组件在某个其他组件之前

@AutoConfigureAfter

让当前配置或者组件在某个其他组件之后

@AutoConfigureOrder

指定外部依赖的 AutoConfig 的加载顺序(即定义在/META-INF/spring.factories文件中的配置 bean 优先级),值越小优先级越高

Spring Boot内置的控制配置顺序举例

##########################WebMvcAutoConfiguration ########################################
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration { ... }

##################################DispatcherServletAutoConfiguration #######################
@AutoConfigureOrder(-2147483648)
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({DispatcherServlet.class})
@AutoConfigureAfter({ServletWebServerFactoryAutoConfiguration.class})
public class DispatcherServletAutoConfiguration { ... }

###########################ServletWebServerFactoryAutoConfiguration ##########################
@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration { ... }

这几个配置是WebMVC的核心配置,他们之间是有顺序关系的:

  • WebMvcAutoConfiguration被加载的前提是:DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration、ValidationAutoConfiguration这三个哥们都已经完成初始化

  • DispatcherServletAutoConfiguration被加载的前提是:ServletWebServerFactoryAutoConfiguration已经完成初始化

  • ServletWebServerFactoryAutoConfiguration被加载的前提是:@AutoConfigureOrder(-2147483648) 值小优先级很高,而且它无其它依赖,希望自己是最先被初始化的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这孩子叫逆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值