SpringBoot系列--自定義Starter

提到Spring Boot時,很多人想到的是它的自動化裝配特性。當我們項目需要Redis、MongoDB時,只需要引入相應的 spring-boot-starter-data-redis、spring-boot-starter-data-mongodb包,Spring 自然會把這些redisTemplate、mongoTemplate 放進IOC容器管理。我們只需要在application.properties文件中關注對應的 url、password等基礎配置信息。 當然。這些Starter的實現思想仍然還是約定大於配置。

在自定義Starter 之前,先了解下什麼是SPI,像在Dubbo、Spring中,好多拓展性接口都基於SPI實現。

SPI 即Service provider interface,服務提供發現接口,可能還是有點抽象。 現在我們有一個場景

先定義一個接口 Person

public interface Person {
	void eatFood();
	void doSport();

}

然後定義它的實現類

Doctor類

public class Doctor implements Person {
	[@Override](https://my.oschina.net/u/1162528)
	public void eatFood() {
		System.out.println("醫生————>多喫蔬菜");
	}

	[@Override](https://my.oschina.net/u/1162528)
	public void doSport() {
		System.out.println("醫生————>多多休息");
	}
}

Student 類

public class Student implements Person {
	[@Override](https://my.oschina.net/u/1162528)
	public void eatFood() {
		System.out.println("學生————>多喝牛奶");
	}

	[@Override](https://my.oschina.net/u/1162528)
	public void doSport() {
		System.out.println("學生————>多做運動");
	}
}

現在我們怎麼拿到實現Person 接口的所有 類,並選擇性調用呢,

就可以通過SPI

首先在resources 下創建 META-INF 文件包,然後在META-INF下創建services文件包,再創建文件com.tcwong.service.Person ,文件名即接口全路徑名稱,文件內容

com.tcwong.impl.Student
com.tcwong.impl.Doctor

通過ServiceLoader加載

public class SpiDemoApplication {

	public static void main(String[] args) {
		ServiceLoader<Person> serviceLoader = ServiceLoader.load(Person.class);
		serviceLoader.forEach(person -> {
			person.doSport();
			person.eatFood();
		});
	}
}

就可以拿到所有實現類了。我們不妨假想,ServiceLoader 通過掃描指定的META-INF下的services包,拿到裏面的配置信息,通過反射就可以實現。 實際上面的就是SPI機制,即約定大於配置。

實際Spring Boot 的Starter也是這種原理,只是Spring結合IOC特性,把實現類Bean統一管理,我們注入後直接使用即可。

現在自定義一個redis-starter

引入包

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
			<version>2.2.6.RELEASE</version>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>3.1.0</version>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.70</version>
		</dependency>

Redis操作類

public class MyRedis<V> {

	private String url;
	private String port;
	private String password;

	public MyRedis(String url, String port, String password) {
		// 配置參數校驗
		if (url == null){
			throw new RuntimeException("myRedis連接url不能爲空");
		}
		if (port == null){
			throw new RuntimeException("myRedis連接port不能爲空");
		}
		if (password == null){
			throw new RuntimeException("myRedis連接password不能爲空");
		}
		this.url = url;
		this.port = port;
		this.password = password;
		System.out.println(MessageFormat.format("myRedis已建立連接,地址:{0} 端口:{1} 密碼:{2}",url,port,password));
	}

	/**
	 * Description 模擬redis存儲
	 *
	 * [@param](https://my.oschina.net/u/2303379) key 鍵
	 * @param value 值
	 * @return 值 JSON
	 * @author tcwong
	 * @date 2021/3/19
	 */
	public String setValue(String key,V value){
		String valueString = JSONObject.toJSONString(value);
		System.out.println(MessageFormat.format("保存成功,key:{0} value:{1}",key,valueString));
		return valueString;
	}
}

Redis連接參數

@ConfigurationProperties(prefix = "myredis")
public class MyRedisProperty {

	private String url;
	private String port;
	private String password;

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getPort() {
		return port;
	}

	public void setPort(String port) {
		this.port = port;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

統一交給SpringIOC容器管理

@Configuration
@ConditionalOnClass(Jedis.class)
@EnableConfigurationProperties(MyRedisProperty.class)
public class MyRedisAutoConfiguration {

	@Resource private MyRedisProperty myRedisProperty;

	@Bean
	MyRedis myRedis(){
		MyRedis<Object> myRedis = new MyRedis<>(myRedisProperty.getUrl()
				, myRedisProperty.getPort()
				, myRedisProperty.getPassword());
		return myRedis;
	}
}

其中註解 @ConditionalOnClass(Jedis.class) 只有在引入包Jedis 時,當前配置纔會生效。

在resources 文件包下 創建 META-INF包,創建文件 spring.factories

文件內容

org.springframework.boot.autoconfigure.EnableAutoConfiguration =
com.tcwong.config.MyRedisAutoConfiguration

maven install 打jar包。

構建 Spring-demo 測試項目

引入包

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.tcwong</groupId>
			<artifactId>myredis-spring-boot-starter</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>

在 application.properties 配置 myredis 連接信息

server.port = 8080
myredis.url = 177.03.789.104
myredis.port = 9377
myredis.password = myredis*123456

定義redis 存儲實體 Student 類

public class Student {
	private String id;
	private String name;
	private Integer age;

	public Student() {
	}

	public Student(String id, String name, Integer age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student{" +
				"id='" + id + '\'' +
				", name='" + name + '\'' +
				", age=" + age +
				'}';
	}
}

RedisController 接口

@RestController
public class RedisController {

	@Resource private MyRedis myRedis;

	@PostMapping("/redis")
	public String redis(@RequestBody Student student){
		return myRedis.setValue(student.getId(),student);
	}
}

啓動項目

redis 已正常連接

接口 http://localhost:8080/redis

{
  "age": 1,
  "id": "123",
  "name": "蘇蘇"
}

myredis-starter 成功。

擴展:

如何在 application.properties 文件中,增加提示

只需要在自定義starter 時,

引入包

	  <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<version>2.2.6.RELEASE</version>
			<optional>true</optional>
		</dependency>

在META-INF 下創建文件 additional-spring-configuration-metadata.json

文件內容

{
  "properties": [
	{
	  "name": "myredis.url",
	  "type": "java.lang.String",
	  "defaultValue": "127.0.0.1",
	  "description": "連接地址."
	},
	{
	  "name": "myredis.port",
	  "type": "java.lang.String",
	  "defaultValue": "6379",
	  "description": "端口號."
	},
	{
	  "name": "myredis.password",
	  "type": "java.lang.String",
	  "defaultValue": "123456",
	  "description": "密碼."
	}
  ]
}

既可以自定義提示信息

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