spring boot 系列之六:深入理解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,基於條件配置,我們可以實現在不同的操作系統下返回不同的值。

  1. 判斷條件定義
    1. )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");
          }
      
          
      }
      復制代碼
    2. )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");
          }
      
          
      }
      復制代碼
  2. 不同系統下的Bean的類
    1. )接口
      public interface ListService {
      
          public String showListLine();
      }
    2. )windows下的Bean類
      復制代碼
      public class WindowsListService implements ListService{
      
          @Override
          public String showListLine() {
              return "dir";
          }
      
      }
      復制代碼
    3. )linux下的Bean的類
      復制代碼
      public class LinuxListService implements ListService{
      
          @Override
          public String showListLine() {
              return "ls";
          }
      
      }
      復制代碼
  3. 配置類
    復制代碼
    @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();
        }
    }
    復制代碼
  4. 測試類
    復制代碼
    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());
        }
    }
    復制代碼
  5. 運行測試類,由於我的是windows7 系統,因此結果是
    Windows 7 系統下的列表命令為: dir

    如果你的是linux系統,則結果就會是

    Linux 系統下的列表命令為: ls

二、spring boot 的條件化配置

在spring boot項目中會存在一個名為spring-boot-autoconfigure的jar包

條件化配置就是在這個jar里面實現的,它用到了如下的條件化注解,這些注解都是以@ConditionalOn開頭的,他們都是應用了@Conditional的組合注解:

接下來我們看個源碼的列子:

以JdbcTemplateAutoConfiguration為例,它里面有這段代碼:

@Bean
    @Primary
    @ConditionalOnMissingBean(JdbcOperations.class)
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(this.dataSource);
    }

只有在不存在JdbcOperations(如果查看JdbcTemplate的源碼,你會發現JdbcTemplate類實現了JdbcOperations接口)實例的時候,才會初始化一個JdbcTemplate 的Bean。

基於以上內容,我們就可以閱讀自動配置相關的源碼了。

 

三、spring boot 自動配置源碼分析

spring boot項目的啟動類用的注解--@SpringBootApplication是一個組合注解,其中@EnableAutoConfiguration是自動配置相關的。

而這個@EnableAutoConfiguration注解里面有個@Import注解導入了EnableAutoConfigurationImportSelector用來實現具體的功能

(注:由於我本地的spring boot版本不是最新的,這里的EnableAutoConfigurationImportSelector已經不建議使用了,新版本可能已經換成了其他類,但是不影響我們看代碼)

 這個類繼承了AutoConfigurationImportSelector

進入父類,里面有個方法selectImports()調用了方法getCandidateConfigurations(),進而調用了SpringFactoriesLoader.loadFactoryNames()方法

在SpringFactoriesLoader.loadFactoryNames()方法里面,我們看到會查詢META-INF/spring.factories這個配置文件

SpringFactoriesLoader.loadFactoryNames方法會掃描具有META-INF/spring.factories文件的jar包,而我們的spring-boot-autoconfigure.jar里面就有一個這樣的文件,此文件中聲明了具體有哪些自動配置:

我們上面提到的JdbcTemplateAutoConfiguration自動配置類就在里面。

 四、編寫自己的spring boot starter pom

接下來,我們就來寫一個簡單的spring boot starter pom。

步驟如下:

  1. 新建starter maven項目spring-boot-starter-hello
  2. 修改pom文件
    復制代碼
    <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>
        <groupId>com.sam</groupId>
        <artifactId>spring-boot-starter-hello</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <dependencies>
            <!-- 這里需要引入spring boot的自動配置作為依賴 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
                <version>1.5.1.RELEASE</version>
            </dependency>
    
    
        </dependencies>
    </project>
    復制代碼
  3. 屬性配置
    復制代碼
    /**
     * @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;
        }
        
        
    }
    復制代碼
  4. 判定依據類
    復制代碼
    /**
     * 后面的代碼會依據此類是否存在,來決定是否生產對應的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;
        }
    }
    復制代碼
  5. 自動配置類
    復制代碼
    @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

  6. 注冊配置,需要到META-INF/spring.factories文件中注冊改自動配置類:在src/main/source目錄下新建改文件,然后進行配置。
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.sam.spring_boot_starter_hello.HelloServiceAutoConfiguration
  7. 對該工程進行mvn clean install,將jar推送到本地maven倉庫,供后續使用。
  8. 使用starter ,使用我們這個starter 需要新建一個或使用既存的一個spring boot工程(這里我用的是既存的),然后
    1. )修改pom,引入上述的依賴
      <dependency>
                  <groupId>com.sam</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接口

       

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

       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM