關於Starter
Spring Boot秉承“約定大於配置”的開發方式,使得我們基於Spring Boot開發專案的效率變得十分高。相信使用過Spring Boot的小夥伴都會發現,當我們要用到某個Spring提供的元件時,只需要在
pom.xml
檔案中新增該元件的starter依賴就能整合到專案中。例如,在
pom.xml
檔案中新增spring-boot-starter-web
依賴,就能讓專案整合Spring MVC的功能。並且在最簡使用下幾乎不需要進行任何的配置,而以往想要整合Spring MVC,不僅要新增一堆類似於spring-web
、spring-webmvc
等相關依賴包,以及完成許多繁雜的配置才能夠實現整合。這是因為starter裡已經幫我們整合了各種依賴包,避免了依賴包缺失或依賴包之間出現版本衝突等問題。以及完成了許多基礎配置和自動裝配,讓我們可以在最簡使用下,跳過絕大部分的配置,從而達到開箱即用的效果。這也是Spring Boot實現“約定大於配置”的核心之一。
動手開發一個Starter
通過以上的描述,我們可以簡單地將starter看作是對一個元件功能粒度較大的模組化封裝,包括了所需依賴包的整合及基礎配置和自動裝配等。
這裡說下
artifactId
的命名問題,Spring 官方 Starter通常命名為spring-boot-starter-{name}
如spring-boot-starter-web
, Spring官方建議非官方Starter命名應遵循{name}-spring-boot-starter
的格式。
除了Spring官方提供的starter外,我們自己也可以根據業務開發一個starter。例如,當專案積累到一定程度時,我們可以將一些通用功能下沉為一個starter。而開發一個starter也很簡單,只需要以下步驟:
- 新建一個Maven專案,在pom.xml檔案中定義好所需依賴;
- 新建配置類,寫好配置項和預設值,使用
@ConfigurationProperties
指明配置項字首; - 新建自動裝配類,使用
@Configuration
和@Bean
來進行自動裝配; - 新建
spring.factories
檔案,用於指定自動裝配類的路徑; - 將starter安裝到maven倉庫,讓其他專案能夠引用;
接下來,以封裝一個用於操作redis的starter為例,一步步展示這些步驟的具體實現過程。首先是第一步,新建一個maven專案,完整的pom.xml內容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-starter-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<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>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
</dependencies>
</project>
第二步,新建一個屬性配置類,寫好配置項和預設值。並使用@ConfigurationProperties
指明配置項字首,用於載入配置檔案對應的字首配置項:
package com.example.starter.demo.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 屬性配置類,用於載入配置檔案對應的字首配置項
**/
@Data
@ConfigurationProperties("demo.redis")
public class RedisProperties {
private String host = "127.0.0.1";
private int port = 6379;
private int timeout = 2000;
private int maxIdle = 5;
private int maxTotal = 10;
private long maxWaitMillis = 10000;
private String password;
}
編寫一個簡單的redis操作工具,程式碼如下:
package com.example.starter.demo.component;
import com.google.gson.Gson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* redis 操作元件
**/
@Slf4j
@RequiredArgsConstructor
public class RedisComponent {
private final JedisPool jedisPool;
/**
* get value with key
*/
public <T> T get(String key, Class<T> clazz) {
try (Jedis resource = jedisPool.getResource()) {
String str = resource.get(key);
return stringToBean(str, clazz);
}
}
/**
* set value with key
*/
public <T> boolean set(String key, T value, int expireSeconds) {
try (Jedis resource = jedisPool.getResource()) {
String valueStr = beanToString(value);
if (valueStr == null || valueStr.length() == 0) {
return false;
}
if (expireSeconds <= 0) {
resource.set(key, valueStr);
} else {
resource.setex(key, expireSeconds, valueStr);
}
return true;
}
}
private <T> T stringToBean(String str, Class<T> clazz) {
Gson gson = new Gson();
return gson.fromJson(str, clazz);
}
private <T> String beanToString(T value) {
Gson gson = new Gson();
return gson.toJson(value);
}
}
第三步,新建自動裝配類,使用@Configuration
和@Bean
來實現對JedisPool
和RedisComponent
的自動裝配;
package com.example.starter.demo.configuration;
import com.example.starter.demo.component.RedisComponent;
import com.example.starter.demo.properties.RedisProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 自動裝配類
**/
@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfiguration {
private final RedisProperties properties;
@Bean
// 表示當Spring容器中沒有JedisPool類的物件時,才呼叫該方法
@ConditionalOnMissingBean(JedisPool.class)
public JedisPool jedisPool() {
log.info("redis connect string: {}:{}", properties.getHost(), properties.getPort());
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(properties.getMaxIdle());
jedisPoolConfig.setMaxTotal(properties.getMaxTotal());
jedisPoolConfig.setMaxWaitMillis(properties.getMaxWaitMillis());
String password = properties.getPassword();
if (password == null || password.length() == 0) {
return new JedisPool(jedisPoolConfig, properties.getHost(),
properties.getPort(), properties.getTimeout());
}
return new JedisPool(jedisPoolConfig, properties.getHost(),
properties.getPort(), properties.getTimeout(), properties.getPassword());
}
@Bean
@ConditionalOnMissingBean(RedisComponent.class)
public RedisComponent redisComponent(JedisPool jedisPool){
return new RedisComponent(jedisPool);
}
}
第四步,在專案的resources
目錄下新建一個META-INF
目錄,並在該目錄下新建spring.factories
檔案。如下圖所示:
在spring.factories
檔案裡指定自動裝配類的路徑:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.demo.configuration.RedisConfiguration
若需要指定多個自動裝配類的路徑,則使用逗號分隔。如下示例:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.demo.configuration.DemoConfiguration,\
com.example.starter.demo.configuration.RedisConfiguration
Tips:spring.factories
支援配置的key如下:
org.springframework.context.ApplicationContextInitializer
org.springframework.context.ApplicationListener
org.springframework.boot.autoconfigure.AutoConfigurationImportListener
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider
org.springframework.boot.env.EnvironmentPostProcessor
org.springframework.boot.SpringApplicationRunListener
org.springframework.boot.SpringBootExceptionReporter
org.springframework.beans.BeanInfoFactory
org.springframework.boot.env.PropertySourceLoader
org.springframework.data.web.config.SpringDataJacksonModules
org.springframework.data.repository.core.support.RepositoryFactorySupport
最後install這個maven專案,命令如下:
mvn clean install
如果使用的開發工具是IDEA的話就比較簡單,只需要雙擊一下install即可:
使用Starter
在任意一個Spring Boot專案的pom.xml
檔案中新增如下依賴:
<dependency>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在專案的application.yml
中新增如下配置項來覆蓋預設配置,若預設配置已符合需求則可以省略這一步:
demo:
redis:
host: 172.168.1.198
port: 6379
timeout: 3000
password:
max-total: 10
max-wait-millis: 10000
max-idle: 10
編寫一個單元測試類進行測試,程式碼如下:
package com.example.firstproject.starter;
import com.example.starter.demo.component.RedisComponent;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class StarterTests {
@Autowired
private RedisComponent redisComponent;
@Test
public void redisTest() {
String key = "redisTest";
String value = "success!!!!!";
boolean success = redisComponent.set(key, value, 3600);
log.info("set value to redis {}!", success ? "success" : "failed");
String result = redisComponent.get(key, String.class);
log.info("get value from redis: [{}]", result);
}
}