定义 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 方式提供给业务方使用。这样可以让服务划分指责更加清晰,而且保证提供组件不会增加原始服务的运行负担,保证微服务
的 微
。
还有一个问题就是组件粒度的问题,这个需要结合具体的业务场景和需求,感觉是一个仁者见仁智者见智问题。