spring boot 系列之六:深刻理解spring boot的自動配置

咱們知道,spring boot自動配置功能能夠根據不一樣狀況來決定spring配置應該用哪一個,不該該用哪一個,舉個例子:linux

  • Spring的JdbcTemplate是否是在Classpath裏面?若是是,而且DataSource也存在,就自動配置一個JdbcTemplate的Bean
  • Thymeleaf是否是在Classpath裏面?若是是,則自動配置Thymeleaf的模板解析器、視圖解析器、模板引擎

那個這個是怎麼實現的呢?緣由就在於它利用了Spring的條件化配置,條件化配置容許配置存在於應用中,可是在知足某些特定條件前會忽略這些配置。spring

要實現條件化配置咱們要用到@Conditional條件化註解。apache

本篇隨便講從以下三個方面進行展開:windows

  1. @Conditional小例子,來講明條件化配置的實現方式
  2. spring boot 的條件化配置詳解
  3. spring boot 自動配置源碼分析
  4. 本身動手實現spring boot starter pom

1、@Conditional小例子app

咱們知道在windows下顯示列表的命令是dir,而在linux系統下顯示列表的命令是ls,基於條件配置,咱們能夠實如今不一樣的操做系統下返回不一樣的值。maven

  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系統,則結果就會是ide

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

2、spring boot 的條件化配置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。

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

 

3、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自動配置類就在裏面。

 4、編寫本身的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接口

       

相關文章
相關標籤/搜索