定义 starter

首先需要定义我们自己的 ​​starter​​。

导入依赖和​​artifactId​​定义

这里说下 ​​artifactId​​​的命名问题,Spring 官方 Starter 通常命名为 spring-boot-starter-{name} 如 spring-boot-starter-web,Spring 官方建议非官方 Starter 命名应遵循 {name}-spring-boot-starter 的格式。举个例子:​​mybatis-spring-boot-starter​​​, ​​dubbo-spring-boot-starter​​.

<modelVersion>4.0.0</modelVersion>
<groupId>com.ssm</groupId>
<artifactId>example-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>

添加配置

在 ​​resources/META-INF​​​下创建 ​​spring.factories​​ 文件。参考如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ssm.xyz.config.ExampleAutoConfigure

​spring.factories​​ 在哪里读取的呢?大家可能有这个问题。

在我们的 Spring-Boot 的启动类上定义了​​@SpringBootApplication​​​注解包含了​​@EnableAutoConfiguration​​ 然后这个注解内部包含

​@Import({AutoConfigurationImportSelector.class})​​​ 通过这个​​AutoConfigurationImportSelector​​​读取了所有依赖 jar 的 ​​spring.factories​​文件配置的注解类。

关键点就是@Import这个注解。这个注解可以导入selector,也可以直接导入configruration类。

业务实现

说一实现,我们通自定义 ​​ExampleService​​​可以对字符串进行统一的 ​​wrap​​操作为字符串添加一个固定的前缀,其实本质就是替换一个 StringUtil 工具方法。

public class StringUtil {
public static String wrap(String str) {return "PrefixXXX" + str;}
}

下面我们开始组件定义方式的功能实现, ​​ExampleAutoConfigure​​​用来初始化组件内的 Bean ​​ExampleServiceProperties​​​和 ​​ExampleService​​:

package com.ssm.xyz.config;

@Configuration
@ConditionalOnClass(ExampleService.class)
@EnableConfigurationProperties(ExampleServiceProperties.class)
public class ExampleAutoConfigure {

@Autowired
private ExampleServiceProperties properties;

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "example.service",value = "enabled",havingValue = "true")
ExampleService exampleService (){
return new ExampleService(properties.getPrefix());
}

}

配置文件接受,这里使用的是 ​​@ConfigurationProperties​​​方法读取配置,会去读取 ​​example.service​​​前缀的配置如果通过属性文件设置的话,我们可以配置 ​​example.service.prefix = zhangsan hello​

@ConfigurationProperties("example.service")
public class ExampleServiceProperties {

private String prefix;
}

​ExampleService#wrap()​​ 本方法是核心代码,主要是实现组件的核心逻辑,如果后续其他业务,可以修改这个 wrap 方法

public class ExampleService {

private String prefix;

public ExampleService(String prefix {
this.prefix = prefix;
}

public String wrap(String word) {
return prefix + word + suffix;
}
}

使用 starter

上面我们完成了自定义 ​​starter​​​ 的开发,下面我们只需要导入我们的 ​​starter​​ 然后我们 Spring-Boot 在启动的时候就会自动会我们读取配置,然后进行初始化,注入到我们使用的地方。在业务开发的时候我们就可以直接使用。

导入组件依赖

使用 starter 之前需要导入依赖(导入组件之前需要提前对定义的组件进行 deploy 不然可能出现无法以依赖的问题,这个也是我们经常容易犯的一个错误):

<dependency>
<groupId>com.ssm</groupId>
<artifactId>example-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

测试类文件

为了方便和简单,我就把 Controller 和 Application 放在一起,我们对外暴露一个 /input 接口, 对传入的 word 进行 wrap 。代码操作如下:

@SpringBootApplication
@RestController
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Autowired
private ExampleService exampleService;

@GetMapping("/input")
public String input(@RequestParam("word") String word){
return exampleService.wrap(word);
}

}

访问地址:​​http://127.0.0.1:8080/input?word=word!​​ 输出结果

hello word!

Spring Boot 自定义组件总结

组件定义权衡的问题,如果我们是一个本地运行程序比如:通过 IP库逆向解析为地址信息、或者发邮件消息推送等组件。个人觉得可以把这些能力封装成一个服务,通过封装 client-sarter 方式提供给业务方使用。这样可以让服务划分指责更加清晰,而且保证提供组件不会增加原始服务的运行负担,保证​​微服务​​​的 ​​微​​。

还有一个问题就是组件粒度的问题,这个需要结合具体的业务场景和需求,感觉是一个仁者见仁智者见智问题。

参考文档