自定義SpringBoot Starter-以Swagger爲例

前言

SpringBoot支持我們自己定義starter,在編寫starter之前,我們需要知道如何自定義Auto-configuration,然後在進一步創建Starter。通過本文讓我們一步步瞭解如何創建一個完整的Starter.

Auto-configured Beans

這是第一個概念,自動配置的Bean。在SpringBoot的底層(spring.factories),提供了很多自動配置的類,實現這樣一個類通常是@Configuration標註的一個類。並且通常使用@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnBean等註解進行裝配時候的約束, 只有檢測到了相關的類或者沒有聲明自己的@Configuration等條件纔會觸發自動裝配。下面看一下DataSource是如何創建Auto Configuration Bean的:

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
        ...
}

定位自動裝配的候選者

上面定義了自動裝配的類,但是SpringBoot並不能直接識別出這些候選者。在SpringBoot中個,需要將自動裝配的類配置在META-INF/spring.factories中,並且需要以org.springframework.boot.autoconfigure.EnableAutoConfiguration作爲key,候選類作爲value,具體定義如下所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

注意: 爲了防止自動裝配的這些類通過component掃描尋找額外的組件,用@Import進行組件的引用。

三個關於裝配順序的註解

  • @AutoConfigureAfter:可以用來在某個具體的裝配類之後進行裝配
  • @AutoConfigureBefore:可以用來在某個具體的裝配類之前進行裝配
  • @AutoConfigureOrder:和@Order作用類似,可以指定任意的加載順序。但是該註解只試用於自動裝配類

Condition註解

SpringBoot提供了很多的@Conditional註解可以在@Configuration類或者@Bean方法中使用。下面一一講解:

Class Condition(類條件註解)

通過Class進行條件判斷主要有:

  • @ConditionalOnClass : 如果指定的類存在就滿足條件
  • @ConditionalOnMissingClass : 如果指定的類不存在就滿足條件

代碼樣例如下:可以看見@ConditionalOnClass聲明瞭如果CustomService類存在就進行裝配,@ConditionalOnMissingBean聲明瞭如果CustomService類不存在就進行裝配

@Configuration
public class MyAutoConfiguration {

	@Configuration
	@ConditionalOnClass(CustomService.class)
	static class CustomConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public CustomService embeddedAcmeService() { ... }

	}

}

需要注意的是,當註解用在method上,那麼JVM會首先進行指定類的加載和引用,而作用在類上則不會有這麼操作。

Bean Conditions(Bean條件註解)

通過Bean進行條件判斷主要有:

  • @ConditionalOnBean : 如果存在指定的Bean就符合條件
  • @ConditionalOnMissingBean :如果不存在指定的Bean就符合條件

代碼樣例如下:這邊聲明瞭如果不存在myService這是bean那麼就會被創建

@Configuration
public class MyAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public MyService myService() { ... }

}

需要注意bean定義的順序,因爲基於bean的判斷會根據已經加載到的結果進行判斷,所以最好用在自動裝配類,可以保證用戶定義的bean已經被裝配。另外上述的兩個註解都不會阻止@Configuration類的創建,但是作用在類上如果不符合要求的不會被註冊到容器中。

Property Conditions(屬性條件註解)

通過Property進行條件判斷主要有:

  • @ConditionalOnProperty: 可以通過指定屬性和environment中是否匹配進行條件判斷。

havingValue可以用來指定是否有期望的值,matchIfMissing如果不設置值返回設置

Resource Conditions

通過Resource進行條件判斷主要有:

  • @ConditionalOnResource :指定資源存在則滿足條件

Web Application Conditions

用來判斷是否是Web環境:

  • @ConditionalOnWebApplication

SpEL Expression Conditions

通過SpEL表達式進行條件判斷:

  • @ConditionalOnExpression

創建自己的Starter

一個完整的Spring Boot Starter庫包含如下組件:

  • autoconfigure模塊:包含了自動裝配的代碼
  • starter模塊:提供對autoconfigure模塊的依賴,以及任何需要的額外的依賴。使用starter模塊就可以使用完整的功能

Swagger Starter的創建

接下來,讓我們看看如何一步步地創建一個完整的swagger starter

命名

針對創建的項目名,比如這邊創建的是swagger項目,那麼就命名auto-configure module爲swagger-spring-boot-autoconfigure,starter module就命名爲demo-spring-boot-starter

預配置屬性

如果想要提供starter的配置屬性,需要指定的命名空間,這邊是swagger。具體的代碼如下:

@ConfigurationProperties(prefix = "swagger")
@EnableSwagger2
public class SwaggerProperties {
    /**
     * swagger scan package
     */
    private String basePackage;

    /**
     * swagger document title
     */
    private String title = "API";

    /**
     * swagger document description
     */
    private String description;

    /**
     * swagger document access link
     */
    private String url;

    /**
     * swagger document's contact
     */
    private String contact = "JoeBig7";

    /**
     * swagger version
     */
    private String version = "1.0";

    setter/getter...
}

屬性條件篩選

對於swagger的basePackage不能爲空,這邊使用Condition來判斷屬性的值。

public class OnSwaggerCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String basePackage = context.getEnvironment().getProperty("swagger.basePackage");
        if (Objects.isNull(basePackage)) {
            throw new RuntimeException("please config basePackage first");
        } else {
            return true;
        }
    }
}

同時指定義SwaggerCondition註解進行標註

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSwaggerCondition.class)
public @interface SwaggerCondition {
}

自動裝配具體代碼

在所有的準備工作都做好以後,對Swagger具體的裝配進行編寫,具體的SwaggerAutoConfiguration代碼如下:

@Configuration
@SwaggerCondition
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
@ConditionalOnClass(name = {"javax.servlet.ServletRegistration", "springfox.documentation.spring.web.plugins.Docket"})
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerAutoConfiguration {


    private SwaggerProperties swaggerProperties;

    public SwaggerAutoConfiguration(SwaggerProperties swaggerProperties) {
        this.swaggerProperties = swaggerProperties;
    }

    @ConditionalOnMissingBean(Docket.class)
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage(this.swaggerProperties.getBasePackage()))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(this.swaggerProperties.getTitle())
                .description(this.swaggerProperties.getDescription())
                .termsOfServiceUrl(this.swaggerProperties.getUrl())
                .contact(this.swaggerProperties.getContact())
                .version(this.swaggerProperties.getVersion())
                .build();
    }
}

定義好Starter

Starter是最後定義的目標項目,對所有的依賴進行總結,這邊的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>
    <groupId>com.mamba</groupId>
    <artifactId>swagger-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Spring Boot AutoConfiguration :: Swagger Starter</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.mamba</groupId>
            <artifactId>swagger-spring-boot-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.7.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

注意:可以將自動裝配的代碼全都寫到starter中,不是強制分成兩個項目的。

源碼

項目源碼參見傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章