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": "密码."
	}
  ]
}

既可以自定义提示信息

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