天天看點

springboot 自定義starter類

不論在工作中,亦或是求職面試,Spring Boot已經成為我們必知必會的技能項,如今的各行各業都在飛速的擁抱這個已經不是很新的Spring啟動架構。

當然,作為Spring Boot的精髓,自動配置原理的工作過程往往隻有在“面試”的時候才能用得上,但是如果在工作中你能夠深入的了解Spring Boot的自動配置原理,将無往不利。

Spring Boot的出現,得益于“習慣優于配置”的理念,沒有繁瑣的配置、難以內建的内容(大多數流行第三方技術都被內建),這是基于Spring 4.x提供的按條件配置Bean的能力。

自動配置原理

Spring Boot關于自動配置的源碼在spring-boot-autoconfigure-x.x.x.x.jar中:

springboot 自定義starter類

當然,自動配置原理的相關描述,官方文檔貌似是沒有提及。不過我們不難猜出,Spring Boot的啟動類上有一個@SpringBootApplication注解,這個注解是Spring Boot項目必不可少的注解。那麼自動配置原理一定和這個注解有着千絲萬縷的聯系!

@EnableAutoConfiguration

springboot 自定義starter類

 @SpringBootApplication是一個複合注解或派生注解,在@SpringBootApplication中有一個注解@EnableAutoConfiguration,翻譯成人話就是開啟自動配置,其定義如下:

springboot 自定義starter類

 而這個注解也是一個派生注解,其中的關鍵功能由@Import提供,其導入的AutoConfigurationImportSelector的selectImports()方法通過SpringFactoriesLoader.loadFactoryNames()掃描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar裡就有一個這樣的spring.factories檔案。

這個spring.factories檔案也是一組一組的key=value的形式,其中一個key是EnableAutoConfiguration類的全類名,而它的value是一個xxxxAutoConfiguration的類名的清單,這些類名以逗号分隔,如下圖所示:

springboot 自定義starter類

這個@EnableAutoConfiguration注解通過@SpringBootApplication被間接的标記在了Spring Boot的啟動類上。在SpringApplication.run(...)的内部就會執行selectImports()方法,找到所有JavaConfig自動配置類的全限定名對應的class,然後将所有自動配置類加載到Spring容器中。

自動配置生效

每一個XxxxAutoConfiguration自動配置類都是在某些條件之下才會生效的,這些條件的限制在Spring Boot中以注解的形式展現,常見的條件注解有如下幾項:

@ConditionalOnBean:當容器裡有指定的bean的條件下。

@ConditionalOnMissingBean:當容器裡不存在指定bean的條件下。

@ConditionalOnClass:當類路徑下有指定類的條件下。

@ConditionalOnMissingClass:當類路徑下不存在指定類的條件下。

@ConditionalOnProperty:指定的屬性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表當xxx.xxx為enable時條件的布爾值為true,如果沒有設定的情況下也為true。

以ServletWebServerFactoryAutoConfiguration配置類為例,解釋一下全局配置檔案中的屬性如何生效,比如:server.port=8081,是如何生效的(當然不配置也會有預設值,這個預設值來自于org.apache.catalina.startup.Tomcat)。

springboot 自定義starter類

在ServletWebServerFactoryAutoConfiguration類上,有一個@EnableConfigurationProperties注解:開啟配置屬性,而它後面的參數是一個ServerProperties類,這就是習慣優于配置的最終落地點。

springboot 自定義starter類

在這個類上,我們看到了一個非常熟悉的注解:@ConfigurationProperties,它的作用就是從配置檔案中綁定屬性到對應的bean上,而@EnableConfigurationProperties負責導入這個已經綁定了屬性的bean到spring容器中(見上面截圖)。那麼所有其他的和這個類相關的屬性都可以在全局配置檔案中定義,也就是說,真正“限制”我們可以在全局配置檔案中配置哪些屬性的類就是這些XxxxProperties類,它與配置檔案中定義的prefix關鍵字開頭的一組屬性是唯一對應的。

至此,我們大緻可以了解。在全局配置的屬性如:server.port等,通過@ConfigurationProperties注解,綁定到對應的XxxxProperties配置實體類上封裝為一個bean,然後再通過@EnableConfigurationProperties注解導入到Spring容器中。

而諸多的XxxxAutoConfiguration自動配置類,就是Spring容器的JavaConfig形式,作用就是為Spring 容器導入bean,而所有導入的bean所需要的屬性都通過xxxxProperties的bean來獲得。

可能到目前為止還是有所疑惑,但面試的時候,其實遠遠不需要回答的這麼具體,你隻需要這樣回答:

Spring Boot啟動的時候會通過@EnableAutoConfiguration注解找到META-INF/spring.factories配置檔案中的所有自動配置類,并對其進行加載,而這些自動配置類都是以AutoConfiguration結尾來命名的,它實際上就是一個JavaConfig形式的Spring容器配置類,它能通過以Properties結尾命名的類中取得在全局配置檔案中配置的屬性如:server.port,而XxxxProperties類是通過@ConfigurationProperties注解與全局配置檔案中對應的屬性進行綁定的。

什麼是starter

Starter可以了解為一個可拔插式的插件,提供一系列便利的依賴描述符,您可以獲得所需的所有Spring和相關技術的一站式服務。應用程式隻需要在maven中引入starter依賴,SpringBoot就能自動掃描到要加載的資訊并啟動相應的預設配置。用一句話描述,就是springboot的場景啟動器。

下面是Spring官方提供的部分starter,全部的請參考官網:

springboot 自定義starter類

自定義starter

官方定義的starter命名都是spring-boot-starter-xxx,我們自己定義的一般都 xxx-spring-boot-starter,建立一個id-gengerate-spring-boot-starter項目

導入springboot 包spring-boot-starter

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!--主要是為了生成spring-configration-metadata.json檔案,友善idea跳轉-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
           

1.建立配置類,定義一個映射配置新的的類IdProperties和LockieProperties,添加注解ConfigurationProperties("machine.id"),我們的配置檔案以machine.id開頭,比如mathine.id.name=boot-order,另外一個配置檔案是已spring.lockie開頭的配置檔案

package com.lockie.starter.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author: lockie
 * @Date: 2020/11/3 15:10
 * @Description: id生成配置類
 */
@Data
@ConfigurationProperties("machine.id")
public class IdProperties {
    private String name;
}




package com.lockie.starter.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author: 鄒細良
 * @Date: 2020/11/3 18:21
 * @Description:
 */
@Data
@ConfigurationProperties("spring.lockie")
public class LockieProperties {
    private String prefix;
    private String suffix;
}
           

2.建立一個接口用來生成對應的業務,IdService裡面有個getId方法用來生成自定義的ID

package com.lockie.starter.service;

import com.lockie.starter.config.IdProperties;
import lombok.Data;

/**
 * @author: lockie
 * @Date: 2020/11/3 15:19
 * @Description: 生成ID接口
 */
@Data
public class IdService {

    private IdProperties idProperties;

    /**
     * 擷取ID
     * @return
     */
    public String getId() {
        return idProperties.getName() + "-" + System.currentTimeMillis();
    }
}



package com.lockie.starter.service;

import com.lockie.starter.config.LockieProperties;
import lombok.Data;

/**
 * @author: lockie
 * @Date: 2020/11/3 18:23
 * @Description:
 */
@Data
public class LockieService {
    LockieProperties lockieProperties;

    public String hello(String name) {
        return lockieProperties.getPrefix() + "-" + name + "-" + lockieProperties.getSuffix();
    }
}
           

3.建立配置類,重要的配置參數

@Configuration:配置類

@EnableConfigurationProperties():開啟某屬性類,裡面封裝的是屬性資訊,相當于@ConfigurationProperties加載application.properties裡面的資訊,并用@Component将類交由spring管理

@ConditionalOnClass():當這個類存在時,再進行下面的步驟,否則就不進行下面的步驟了

@AutoConfigureAfter():當自動配置完成後,再執行裡面的類

@ConfigurationOnMissBean():當這個bean不存在時,再添加bean類

@Bean:添加bean類

/**
 * @author: lockie
 * @Date: 2020/11/3 15:17
 * @Description: 擷取ID配置類
 */
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(IdProperties.class)
public class IdAutoConfiguation {
    @Autowired
    private IdProperties idProperties;

    @Bean
    public IdService idService() {
        IdService idService = new IdService();
        idService.setIdProperties(idProperties);
        return idService;
    }

}




/**
 * @author: lockie
 * @Date: 2020/11/3 18:26
 * @Description:
 */
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties({LockieProperties.class})
public class LockieAutoConfiguration {
    @Autowired
    LockieProperties lockieProperties;

    @Bean
    public LockieService lockieService() {
        LockieService lockieService = new LockieService();
        lockieService.setLockieProperties(lockieProperties);
        return lockieService;
    }
}
           

4.最重要的一步,在resources目錄下建立META-INF目錄,并添加檔案spring.factories。在這個檔案中配置EnableAutoConfiguration,具體如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.lockie.starter.config.IdAutoConfiguation,\
  com.lockie.starter.config.LockieAutoConfiguration
           

測試自定義的Starter 

我們在之前的項目boot-order-service中測試我們的starter, boot-order-service項目pom增加id-generate-spring-boot-starter包

<dependency>
    <groupId>com.lockie-starter</groupId>
    <artifactId>id-generate-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>      

項目包路徑一樣可能還需要在boot-order-starter啟動類上增加掃描id-generate-spring-boot-starter項目包路徑 

@SpringBootApplication(scanBasePackages = "com.lockie.starter")      

boot-order-service項目配置檔案增加配置參數

machine.id.name=boot-order
spring.lockie.prefix=Hello!
spring.lockie.suffix=Nice to meet you      

到這裡我們在boot-order-service服務中就可以使用id-generate-spring-boot-starter項目中的services了,并且隻需要使用@Autowired即可

@SpringBootTest
@ContextConfiguration(classes = BootOrderServiceApplication.class)
class BootOrderServiceApplicationTests {

    @Test
    void contextLoads() {
    }

    @Autowired
    IdService idService;
    @Autowired
    LockieService lockieService;

    @Test
    public void testIdGenerate() {
        String id = idService.getId();
        System.out.println("生成ID:" + id);

        String str = lockieService.hello("小明");
        System.out.println("打招呼:"+ str);
    }
}
           

運作一下檢視結果,看到結果是我們想要的,至此我們自定義一個starter就成功了,總結一下就是要建立3個類1個配置檔案,xxProperties,xxService,xxAutoConfiguation和spring.factories

springboot 自定義starter類

繼續閱讀