1. 概述
本文将介绍 Flips 库,这是一个通过强大注解为 Spring Core、Spring MVC 和 Spring Boot 应用实现功能开关的解决方案。
功能开关(Feature Toggles)是一种快速安全交付新功能的模式。它允许我们无需修改或部署新代码就能改变应用行为。Martin Fowler 的博客有篇关于功能开关的深度解析。
2. Maven 依赖
首先需要在 pom.xml
中添加 Flips 库:
<dependency>
<groupId>com.github.feature-flip</groupId>
<artifactId>flips-core</artifactId>
<version>1.0.1</version>
</dependency>
Maven Central 提供最新版本,GitHub 项目在这里。
当然还需要添加 Spring 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.10.RELEASE</version>
</dependency>
⚠️ Flips 暂不支持 Spring 5.x,因此我们使用4.x 分支的最新 Spring Boot 版本。
3. 搭建基础 REST 服务
创建一个简单的 Spring Boot 项目来演示功能开关的添加和切换。
我们的 REST 应用将提供 Foo
资源访问:
public class Foo {
private String name;
private int id;
}
创建一个维护 Foo
列表的 Service
:
@Service
public class FlipService {
private List<Foo> foos;
public List<Foo> getAllFoos() {
return foos;
}
public Foo getNewFoo() {
return new Foo("New Foo!", 99);
}
}
后续会补充其他服务方法,但这段代码足以说明 FlipService
的作用。
最后创建 Controller:
@RestController
public class FlipController {
private FlipService flipService;
// constructors
@GetMapping("/foos")
public List<Foo> getAllFoos() {
return flipService.getAllFoos();
}
}
4. 基于配置的功能控制
Flips 最基础的用法是通过配置启用/禁用功能,提供多种注解实现。
4.1 环境属性控制
假设为 FlipService
添加了按 ID 获取 Foo
的新功能:
@GetMapping("/foos/{id}")
@FlipOnEnvironmentProperty(
property = "feature.foo.by.id",
expectedValue = "Y")
public Foo getFooById(@PathVariable int id) {
return flipService.getFooById(id)
.orElse(new Foo("Not Found", -1));
}
@FlipOnEnvironmentProperty
控制此接口的可用性。
简单来说,当 feature.foo.by.id
为 Y
时,可通过 ID 访问接口。若未设置或值不匹配,Flips 会禁用该接口。
功能未启用时,Flips 会抛出 FeatureNotEnabledException
,Spring 向 REST 客户端返回"未实现"响应。
当属性设置为 N
时调用接口,会看到:
Status = 501
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {
"errorMessage": "Feature not enabled, identified by method
public com.baeldung.flips.model.Foo
com.baeldung.flips.controller.FlipController.getFooById(int)",
"className":"com.baeldung.flips.controller.FlipController",
"featureName":"getFooById"
}
符合预期:Spring 捕获异常并向客户端返回 501 状态码。
4.2 活跃 Profile 控制
Spring 长期支持将 Bean 映射到不同环境(如 dev/test/prod)。将功能开关与活跃环境关联是自然延伸。
通过 @FlipOnProfiles
基于活跃的 Spring Profile 控制功能:
@RequestMapping(value = "/foos", method = RequestMethod.GET)
@FlipOnProfiles(activeProfiles = "dev")
public List getAllFoos() {
return flipService.getAllFoos();
}
@FlipOnProfiles
接受环境名称列表。当前活跃环境在列表中时,接口可访问。
4.3 Spring 表达式控制
Spring 表达式语言 (SpEL) 是操作运行时环境的强大机制。Flips 也支持通过 SpEL 切换功能。
@FlipOnSpringExpression
根据返回布尔值的 SpEL 表达式控制方法:
@FlipOnSpringExpression(expression = "(2 + 2) == 4")
@GetMapping("/foo/new")
public Foo getNewFoo() {
return flipService.getNewFoo();
}
4.4 禁用功能
使用 @FlipOff
完全禁用功能:
@GetMapping("/foo/first")
@FlipOff
public Foo getFirstFoo() {
return flipService.getLastFoo();
}
此例中 getFirstFoo()
完全不可访问。
如后文所示,可组合 Flips 注解,使 @FlipOff
能基于环境或其他条件禁用功能。
5. 基于日期/时间的功能控制
Flips 支持根据日期时间或星期几切换功能。将新功能与特定日期关联有明显优势。
5.1 日期时间控制
@FlipOnDateTime
接受 ISO 8601 格式的属性名。
设置属性表示新功能在 3 月 1 日激活:
first.active.after=2018-03-01T00:00:00Z
编写获取第一个 Foo 的接口:
@GetMapping("/foo/first")
@FlipOnDateTime(cutoffDateTimeProperty = "first.active.after")
public Foo getFirstFoo() {
return flipService.getLastFoo();
}
Flips 检查指定属性。若属性存在且当前时间超过设定时间,功能启用。
5.2 星期几控制
@FlipOnDaysOfWeek
注解适用于 A/B 测试等场景:
@GetMapping("/foo/{id}")
@FlipOnDaysOfWeek(daysOfWeek={DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY})
public Foo getFooByNewId(@PathVariable int id) {
return flipService.getFooById(id).orElse(new Foo("Not Found", -1));
}
getFooByNewId()
仅在周一和周三可用。
6. 替换 Bean
开关方法很有用,但有时需要通过新对象引入新行为。@FlipBean
指示 Flips 调用新 Bean 中的方法。
Flips 注解可用于任何 Spring @Component
。此前只修改了 @RestController
,现在尝试修改 Service
。
创建与 FlipService
行为不同的新服务:
@Service
public class NewFlipService {
public Foo getNewFoo() {
return new Foo("Shiny New Foo!", 100);
}
}
用新版本替换旧服务的 getNewFoo()
:
@FlipBean(with = NewFlipService.class)
public Foo getNewFoo() {
return new Foo("New Foo!", 99);
}
Flips 会将 getNewThing()
的调用重定向到 NewFlipService
。@FlipBean
最适合与其他开关组合使用。
7. 组合开关
通过指定多个注解组合开关。Flips 按顺序评估,采用隐式"AND"逻辑——所有条件必须满足才能启用功能。
组合之前的两个示例:
@FlipBean(
with = NewFlipService.class)
@FlipOnEnvironmentProperty(
property = "feature.foo.by.id",
expectedValue = "Y")
public Foo getNewFoo() {
return new Foo("New Foo!", 99);
}
实现了新服务的可配置化调用。
8. 总结
本指南创建了一个简单的 Spring Boot 服务,通过 Flips 注解控制接口开关。我们学习了如何通过配置信息和日期时间切换功能,以及如何在运行时通过替换 Bean 切换功能。