zoukankan      html  css  js  c++  java
  • Spring Boot自动配置原理分析

      我们知道,spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子:

      Spring的JdbcTemplate是不是在Classpath里面?如果是,并且DataSource也存在,就自动配置一个JdbcTemplate的Bean;

      Thymeleaf是不是在Classpath里面?如果是,则自动配置Thymeleaf的模板解析器、视图解析器、模板引擎。

      那这个是怎么实现的呢?原因就在于它利用了Spring的条件化配置,条件化配置允许配置存在于应用中,但是在满足某些特定条件前会忽略这些配置。

      要实现条件化配置我们要用到@Conditional条件化注解。

      本篇将从如下三个方面进行展开:

    1. @Conditional小例子,来说明条件化配置的实现方式
    2. spring boot 的条件化配置详解
    3. spring boot 自动配置源码分析
    4. 自己动手实现spring boot starter pom

    一、@Conditional小例子

      我们知道在windows下显示列表的命令是dir,而在linux系统下显示列表的命令是ls,基于条件配置,我们可以实现在不同的操作系统下返回不同的值。

    【 判断条件定义】

    • windows下的判定条件

      /**
       * 实现spring 的Condition接口,并且重写matches()方法,如果操作系统是windows就返回true
       *
       */
      public class WindowsCondition implements Condition{
      
          @Override
          public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
              
              return context.getEnvironment().getProperty("os.name").contains("Windows");
          }
      
          
      } 
      
    • linux下的判定条件

      /**
       * 实现spring 的Condition接口,并且重写matches()方法,如果操作系统是linux就返回true
       *
       */
      public class LinuxCondition implements Condition{
      
          @Override
          public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
              
              return context.getEnvironment().getProperty("os.name").contains("Linux");
          }
      
          
      }
      

    【不同系统下的Bean的类】

    • 接口

      public interface ListService {
      
          public String showListLine();
      } 
      
    • windows下的Bean类

      public class WindowsListService implements ListService{
      
          @Override
          public String showListLine() {
              return "dir";
          }
      
      } 
      
    • linux下的Bean的类

      public class LinuxListService implements ListService{
      
          @Override
          public String showListLine() {
              return "ls";
          }
      
      }
      

    【配置类】

    @Configuration
    public class ConditionConfig {
    
        /**
         * 通过@Conditional 注解,符合windows条件就返回WindowsListService实例
         * 
         */
        @Bean
        @Conditional(WindowsCondition.class)
        public ListService windonwsListService() {
            return new WindowsListService();
        }
    
        /**
         * 通过@Conditional 注解,符合linux条件就返回LinuxListService实例
         * 
         */
        @Bean
        @Conditional(LinuxCondition.class)
        public ListService linuxListService() {
            return new LinuxListService();
        }
    }
    

    【测试类】

    public class ConditionTest {
    
        public static void main(String[] args) {
    
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class);
            ListService listService = context.getBean(ListService.class);
            System.out
                    .println(context.getEnvironment().getProperty("os.name") + " 系统下的列表命令为: " + listService.showListLine());
        }
    }
    

    二、Spring Boot的条件化配置

      在spring boot项目中会存在一个名为spring-boot-autoconfigure的jar包

      img

      条件化配置就是在这个jar里面实现的,它用到了如下的条件化注解,这些注解都是以@ConditionalOn开头的,他们都是应用了@Conditional的组合注解:

    img

      接下来我们看个源码的列子:

      以JdbcTemplateAutoConfiguration为例,它里面有这段代码:

    img

      只有在不存在JdbcOperations(如果查看JdbcTemplate的源码,你会发现JdbcTemplate类实现了JdbcOperations接口)实例的时候,才会初始化一个JdbcTemplate的Bean。

      所以说,自动配置类必须在一定的条件下才能生效;

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

      我们可以在主配置文件中启用debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

    三、Spring Boot自动配置源码分析

      spring boot项目的启动类用的注解--@SpringBootApplication是一个组合注解,其中@EnableAutoConfiguration是自动配置相关的。   img

      而这个@EnableAutoConfiguration注解里面有个@Import注解导入了AutoConfigurationImportSelector用来实现具体的功能

       img

      AutoConfigurationImportSelector这个类里面有个selectImports()方法,它调用了getCandidateConfigurations()方法,进而调用了SpringFactoriesLoader.loadFactoryNames()方法

    img

    img

      在SpringFactoriesLoader.loadFactoryNames()方法里面,我们看到会查询META-INF/spring.factories这个配置文件

    img

      SpringFactoriesLoader.loadFactoryNames方法会扫描具有META-INF/spring.factories文件的jar包,把扫描到的这些文件的内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中。

    img

      每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用他们来做自动配置。

      我们上面提到的JdbcTemplateAutoConfiguration自动配置类就在里面。

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

    @Configuration   //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
    @EnableConfigurationProperties(HttpEncodingProperties.class)  //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中
    
    @ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;    判断当前应用是否是web应用,如果是,当前配置类生效
    
    @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 {
      
          //他已经和SpringBoot的配置文件映射了
          private final HttpEncodingProperties properties;
      
       //只有一个有参构造器的情况下,参数的值就会从容器中拿
          public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
            this.properties = properties;
        }
      
        @Bean   //给容器中添加一个组件,这个组件的某些值需要从properties中获取
        @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;
        }
    

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

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

    @ConfigurationProperties(prefix = "spring.http.encoding")  //从配置文件中获取指定的值和bean的属性进行绑定
    public class HttpEncodingProperties {
    
       public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    

    【Spring Boot的精髓总结】

    1. SpringBoot启动会加载大量的自动配置类;
    2. 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
    3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
    4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;

    四、自定义一个spring-boot-starter

      接下来,我们就来写一个简单的spring boot starter pom。

      步骤如下:

    【创建一个maven项目】

      新建starter maven项目:spring-boot-starter-hello

    【修改pom文件】

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.pinyougou</groupId>
        <artifactId>spring-boot-starter-hello</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>spring-boot-starter-hello</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
                <version>2.1.6.RELEASE</version>
            </dependency>
        </dependencies>
    
        <build>
            <resources>
                <!--Maven打jar包把配置文件放在META-INF目录下-->
                <resource>
                    <directory>src/main/resources</directory>
                    <targetPath>META-INF/</targetPath>
                </resource>
            </resources>
        </build>
    </project>
    

    【属性配置】

    /**
     * @ConfigurationProperties
     * 自动匹配application.properties文件中hello.msg的值,然后赋值给类属性msg,这里的msg默认值为“spring boot”
     *
     */
    @ConfigurationProperties(prefix="hello")
    public class HelloServiceProperties {
    
        private static final String MSG = "spring boot";
        
        private String msg = MSG;
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
        
        
    }
    

    【判定依据类】

    /**
     * 后面的代码会依据此类是否存在,来决定是否生产对应的Bean
     *
     */
    public class HelloService {
    
        private String msg;
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public String sayHello() {
            return "hello " + msg;
        }
    }
    

    【自动配置类】

    @Configuration
    @EnableConfigurationProperties(HelloServiceProperties.class)
    @ConditionalOnClass(HelloService.class)
    @ConditionalOnProperty(prefix = "hello", matchIfMissing = true, value = "enabled")
    public class HelloServiceAutoConfiguration {
    
        @Autowired
        HelloServiceProperties helloServiceProperties;
    
        @Bean
        @ConditionalOnMissingBean(HelloService.class)
        public HelloService helloService() {
            HelloService service = new HelloService();
            service.setMsg(helloServiceProperties.getMsg());
            return service;
        }
    }
    

      根据HelloServiceProperties提供的参数,并通过@ConditionalOnClass(HelloService.class)判定HelloService这个类在Classpath中是否存在,存在并且还没有对应的Bean,就生成对应的helloService Bean

    【注册配置】

      需要到META-INF/spring.factories文件中注册该自动配置类:在src/main/resources目录下新建该文件,然后进行配置。

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    com.pinyougou.spring-boot-starter-hello.HelloServiceAutoConfiguration
    

    【安装到本地仓库】

      对该工程进行mvn clean install,将jar推送到本地maven仓库,供后续使用。

    【使用starter】

      使用我们这个starter 需要新建一个或使用既存的一个spring boot工程(这里我用的是既存的),然后

    1. 修改pom,引入上述的依赖

      <dependency>
          <groupId>com.pinyougou</groupId>
          <artifactId>spring-boot-starter-hello</artifactId>
          <version>0.0.1-SNAPSHOT</version>
      </dependency>
      
    2. 实现Controller

      @RestController
      public class HelloController {
        //代码中没有配置这个helloService Bean,但是自动配置能够帮忙实例化,因此可以直接注入
          @Autowired
          HelloService helloService;
          
          @RequestMapping(value="/helloService")
          public String sayHello() {
              return helloService.sayHello();
          }
      }
      
    3. 页面访问/helloService

        img

       4. 在application.properties里面配置hello.msg=sam,然后再次访问/helloService接口

        img

    参考:https://www.cnblogs.com/sam-uncle/p/9111281.html

  • 相关阅读:
    Java实例_队列Queue用法
    JAVA中不同类型的取值范围
    面试被问到的题
    关于Maven启动项目各种.xml文件和.properties文件找不到问题的解决
    jupyter使用
    java中“==”和equal区别
    redis持久化的几种方式
    事务是什么,以及事务四个特性
    Java中的String类能否被继承?为什么?以及final和static的区别
    pytest.mark.parametrize 参数化
  • 原文地址:https://www.cnblogs.com/yft-javaNotes/p/11190888.html
Copyright © 2011-2022 走看看