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.idY 时,可通过 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 切换功能。


原始标题:A Guide to Flips for Spring | Baeldung

» 下一篇: Java Weekly, 第221期