前言
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中,不是強制分成兩個項目的。
源碼
項目源碼參見傳送門