我有时会去写一些Spring Boot相关的组件,用来给web应用提供服务,这些是我在开发过程中积累的一些技巧和建议。

组件服务开关

组件服务开关一般分为两种方式,代码式配置式

代码式的一般是由组件提供一个EnableXXX注解,当web服务使用了这个注解后,组件即为开启状态。

例如:

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MockApiConfig.class})
public @interface EnableMockApi {
}

配置式的一般是添加了组件依赖后,组件即为开启状态,通过配置中的XXX.enabled=false的方式关闭组件服务。

例如:

1
2
3
4
5
@Configuration
@ConditionalOnProperty(prefix = "mockapi.data", value = "enabled", havingValue = "true", matchIfMissing = true)
public class YamlDataPool extends FieldDataPool {
......
}

两种方式各有利弊,但是我个人比较推荐配置式的,这种方式的侵入量会小很多,并且开关较为方便。

注入组件

对于三方项目来说,只是使用了基本的@SpringBootApplication注解或是@ComponentScan中未额外配置组件包名的,都没有办法对组件服务的工具进行注入,也就无法使用此组件。

要注入自己的组件当然可以使用@ComponentScan,在参数中增加组件包名,但是这样不优雅,我们还有更优雅的方式。

对于代码式开关的组件,可以在EnableXXX注解类代码中增加@Import注解,其中添加需要被Bean池管理的类,例如:

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MockApiConfig.class})
public @interface EnableMockApi {
}

这里就把MockApiConfig进行了注入。多个组件的话需要开发者对组件的关系进行管理,在不同的类上添加不同的Import参数,避免都配置在一个类上。

对于配置式开关的组件来说,更推荐使用配置文件的方式。开发者需要在resources文件夹下增加两层文件夹META-INF.spring,然后再spring文件夹下增加一个文件org.springframework.boot.autoconfigure.AutoConfiguration.imports,文件名就是这么长。最后在文件中写入要注入的全类名即可,效果类似于Import的参数:

1
2
3
idea.verlif.mockapi.config.MockApiConfig
idea.verlif.mockapi.config.MockApiBeanConfig
idea.verlif.mockapi.config.PathRecorder

像上面这样,我们就添加了三个类到Bean池中。

可替换组件

一般我们在开发一项给三方使用的服务或组件时,都会提供一些可更换的组件让三方开发的自定义性更高,通常我们建议使用Configuration类来管理这些可更换组件,并使用ConditionalOnMissingBean注解来让三方可以进行替换,例如:

1
2
3
4
5
6
7
8
9
@Configuration
@ConditionalOnMockEnabled
@Import({MockApiBuilder.class, MockApiRegister.class, YamlDataPool.class})
public class MockApiBeanConfig {

@Bean
@ConditionalOnMissingBean(MockLogger.class)
public MockLogger mockLogger() {...}
}

我们在组件里提供了默认的MockLogger,但它被ConditionalOnMissingBean标记,因此在实际项目中如果有自定义的继承或实现类存在于Bean池中,默认的MockLogger就会被替换成自定义的。这样开发者就可以通过自定义的MockLogger来自由替换默认的日志处理器,而不需要编写其他代码逻辑来手动切换。

这样做的好处是能使用Bean管理的方式切换实现类,充分利用Spring的AOC特性。

配置文件提示

很多组件都会使用配置文件的方式让组件的配置更方便,但是我们很容易发现,当组件打包变成依赖后,在实际项目中的配置文件中对组件的配置没有提示,甚至说找不到对应配置,即使配置没问出错并且可以生效。

因为配置文件的提示信息被放在了一个生成的文件中,需要我们使用spring-boot-configuration-processor这个依赖并开启:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

随后重新编译我们的组件,你就会发现在target/classes/META-INF下文件夹下多了一个文件spring-configuration-metadata.json,这个文件就是配置元信息,里面的内容就像这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"groups": [
{
"name": "mockapi",
"type": "idea.verlif.mockapi.config.MockApiConfig",
"sourceType": "idea.verlif.mockapi.config.MockApiConfig"
},
{
"name": "mockapi.data",
"type": "idea.verlif.mockapi.pool.YamlDataPool",
"sourceType": "idea.verlif.mockapi.pool.YamlDataPool"
}
],
"properties": [
{
"name": "mockapi.data.enabled",
"type": "java.lang.Boolean",
"sourceType": "idea.verlif.mockapi.pool.YamlDataPool",
"defaultValue": false
},
{
"name": "mockapi.data.pool",
"type": "java.util.List<idea.verlif.mockapi.pool.YamlDataPool$DataInfo>",
"sourceType": "idea.verlif.mockapi.pool.YamlDataPool"
},
{
"name": "mockapi.enabled",
"type": "java.lang.Boolean",
"description": "是否开启",
"sourceType": "idea.verlif.mockapi.config.MockApiConfig",
"defaultValue": false
},
{
"name": "mockapi.path-strategy",
"type": "idea.verlif.mockapi.config.MockApiConfig$PathStrategy",
"description": "地址重复策略",
"sourceType": "idea.verlif.mockapi.config.MockApiConfig"
}
],
"hints": []
}

此时我们再将组件放入实际项目中,再打开组件的配置时就会出现提示了,也可以使用配置跳转了。

开发建议

对于一个基本的组件来说,除了功能完善之外,还需要对其拓展性进行管理。通常拓展性包括两方面,一个是调用方式,一个是数据存储,例如下图:

代码层级

调用方式

这里的调用接口,就是到达核心功能的入口,而直调就是最基础的入口。通常为了维护方便,调用接口只会有直调作为唯一入口。因为直调是唯一的入口,那么其他方式的调用就需要转换成直调方式,这就有了调用转换器,将其他的调用方式转换成直调方式。

例如经典的Web后台三层结构中,service层既是功能核心,又是调用转换器。service通过聚合其他service并提供多种调用方法的方式,拓展出不同的业务入口,提供给controller和其他service调用。

后来为了将核心层和调用转换器从service中剥离开,又衍生出了bizapplication等层级。但是殊途同归,总的方向就是将核心功能与调用转换器功能解耦,编译开发和维护。

组件开发也是类似的,例如有一个求和的功能,除了提供基础方法add之外,还可以提供用于支持socket调用的socketHandler,用于支持通过启动参数直接求和的commandHandler,用于处理请求队列的requestLine等等。

数据解耦

数据解耦是个老生常谈的话题了,用过三方组件或应用的很多都会遇到“爱而不得”的情况(功能上完美符合需求,但是在存储上没法满足当前环境,比如只需要简单文件数据但只支持数据库存储、数据库选型不同)。这种情况常见于Web应用,为了应对这种情况,很多的框架就已经提供了仓储层了。通过接口或是注解的方式,对同一个存取需求做不同的实现。

这个话题就不必展开讲了,基本上开发者都是知道的,但是因为多一层结构的关系,导致很多项目或是服务没有做数据解耦的设计。

其他

本文使用到的代码由以下项目提供:

mockapi : 只需要一个注解即可为你的接口添加虚拟数据

参考资料:

如何让springboot工程中自定义配置项在idea中提示


本站总访问量