欢迎你来读这篇博客,这篇博客主要是关于 Togglz 的深入解析与工程化实战。
Togglz 是 Java 生态里的 Feature Flag / Feature Toggle 工具。它可以让我们把“代码上线”和“功能开放”拆开:代码可以先发布到生产环境,但功能是否真正对用户生效,可以通过开关动态控制。
序言 在传统开发模式里,我们经常把“发布代码”和“发布功能”绑在一起。
比如你写了一个新的结算逻辑,发版之后,这个逻辑立刻对所有用户生效。如果线上出问题,常见处理方式就是:
回滚版本;
临时改配置;
紧急修代码;
半夜重新发版;
程序员在工位上默默修仙。
但在成熟的持续交付体系里,更推荐的方式是:
代码可以先上线,功能可以后打开;功能可以灰度打开,也可以快速关闭。
这就是 Feature Flag,也叫 Feature Toggle,中文一般叫功能开关 、特性开关 、灰度开关 。
Togglz 正是 Java 平台上对 Feature Toggles 模式的一种实现。它可以在运行时判断某个功能是否启用,并且可以通过配置、数据库、文件、控制台、激活策略等方式动态管理功能状态。
本文会从以下几个角度深入展开:
Togglz 是什么;
Feature Flag 解决什么问题;
Togglz 的核心组件;
Spring Boot 3.x 如何集成 Togglz;
如何基于 JDBC 持久化开关状态;
如何接入 Admin Console 和 Actuator;
如何做租户级灰度;
如何在业务代码里优雅使用;
如何测试;
生产环境如何治理 Feature Flag;
Togglz 和 Nacos、配置中心、权限系统、XXL-JOB 等工具的边界。
本文示例以 Spring Boot 3.x + Java 17+ + Togglz 4.6.2 为基础。
说明:Togglz 4.x 已经面向 Spring Boot 3、Spring 6、Jakarta 10 和 Java 17 生态。旧项目如果还在 Spring Boot 2.x,需要特别关注版本兼容问题。
正文 一、Togglz 是什么? 1.1 一句话理解 Togglz 是一个 Java Feature Flag 工具,用来在应用运行时动态控制某个功能是否启用。
你可以把它理解成:
1 2 普通代码发布:代码一上线,功能就生效 Togglz 模式:代码先上线,功能是否生效由开关决定
例如:
1 2 3 4 5 6 7 if (BizFeatures.NEW_SETTLEMENT_ENGINE.isActive()) { return newSettlementCalculator.calculate(command); } else { return oldSettlementCalculator.calculate(command); }
这段代码的核心含义是:
开关打开:走新逻辑;
开关关闭:走老逻辑;
不需要重新发布代码;
线上出问题可以快速关闭。
这在复杂业务系统里非常实用,尤其是订单、支付、财务、结算、会员、营销、库存这类高风险模块。
1.2 Feature Flag 到底解决什么问题? Feature Flag 解决的不是“能不能写 if-else”的问题,而是发布风险治理 问题。
它主要解决以下场景。
1.2.1 灰度发布 新功能不直接对所有用户开放,而是先对部分用户、部分租户、部分环境开放。
例如:
1 2 3 4 第一阶段:只给内部测试账号打开 第二阶段:只给 3 个试点租户打开 第三阶段:给 10% 用户打开 第四阶段:全量打开
1.2.2 快速止血 上线新逻辑后发现有问题,传统方式可能要重新发版或回滚。
使用 Togglz 后,可以直接关掉开关:
1 NEW_SETTLEMENT_ENGINE = false
功能立刻回到老逻辑。
这叫“止血开关”,在生产事故里很有价值。
1.2.3 新旧逻辑双轨运行 重构一个核心模块时,最怕一刀切。
比如你要重构结算逻辑,可以让新旧逻辑同时存在:
如果新逻辑出问题,关开关即可。
1.2.4 A/B 测试 同一个功能可以让不同用户看到不同版本。
例如:
然后结合埋点分析转化率、点击率、错误率等指标。
1.2.5 未完成代码提前合并 有些功能开发周期较长,如果一直不合主干,分支会越来越难合。
Feature Flag 可以让未完成代码提前合入主干,但默认关闭。
1 2 3 代码已经进入 master 功能默认关闭 等开发完成后再打开
这样可以降低长期分支带来的合并地狱。
二、Togglz 不是什么? 在深入实战前,先把边界说清楚。
2.1 Togglz 不是配置中心 Togglz 和 Nacos、Apollo、Spring Cloud Config 不是同一类东西。
工具
核心职责
示例
Nacos / Apollo
管理配置值
超时时间、线程池大小、短信供应商
Togglz
管理功能是否启用
新结算逻辑是否开启、新导出是否开启
Spring Security
管理认证授权
用户是否有权限访问某接口
XXL-JOB / Quartz
管理任务调度
定时任务何时执行
配置中心当然也可以用一个 boolean 配置实现功能开关,但 Togglz 的优势在于它围绕 Feature Flag 做了更完整的抽象:
Feature;
FeatureManager;
StateRepository;
UserProvider;
ActivationStrategy;
Admin Console;
Actuator Endpoint;
Testing Support。
配置中心是“给你一堆配置”,Togglz 是“围绕功能发布做治理”。
2.2 Togglz 不是权限系统 不要把功能开关当成权限系统。
比如:
1 2 用户是否能访问应付导出:权限系统决定 新版应付导出逻辑是否启用:Togglz 决定
权限是“这个用户有没有资格用这个功能”。
功能开关是“这个功能当前是否处于开放状态”。
二者可以组合,但不要混成一坨。不然最后你会得到一个既不像权限系统、也不像灰度系统的神秘 if 森林。
2.3 Togglz 不是完整实验平台 Togglz 可以支持用户级、角色级、比例级、时间级等激活策略,但它不是完整的 A/B 实验平台。
完整 A/B 平台通常还需要:
分桶稳定性;
实验互斥;
指标采集;
统计显著性分析;
实验看板;
实验生命周期管理。
Togglz 更适合做 Java 应用内嵌式 Feature Flag,而不是替代专业实验平台。
三、Togglz 核心架构 Togglz 的核心架构并不复杂,可以理解为 6 个组件。
flowchart TB
A[业务代码] --> B[FeatureManager]
B --> C[FeatureProvider]
B --> D[StateRepository]
B --> E[UserProvider]
B --> F[ActivationStrategy]
C --> C1[Feature 枚举]
D --> D1[application.yml]
D --> D2[JDBC]
D --> D3[File]
D --> D4[Redis / MongoDB 等]
E --> E1[当前用户]
E --> E2[角色]
E --> E3[租户ID]
F --> F1[用户名策略]
F --> F2[角色策略]
F --> F3[比例灰度]
F --> F4[自定义租户策略]
G[Admin Console / Actuator] --> B
3.1 Feature Feature 表示一个功能开关。
在 Java 中通常使用 enum 定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.cybermario.togglz.feature;import org.togglz.core.Feature;import org.togglz.core.annotation.Label;public enum BizFeatures implements Feature { @Label("新版结算引擎") NEW_SETTLEMENT_ENGINE, @Label("新版应付导出") NEW_PAYABLE_EXPORT, @Label("租户灰度结算") TENANT_GRAY_SETTLEMENT, @Label("成本月结增强校验") COST_MONTH_END_STRICT_CHECK }
推荐使用枚举定义 Feature,因为:
类型安全;
IDE 可提示;
不容易写错字符串;
后续清理方便;
和代码审查更友好。
不推荐在业务代码里到处写字符串:
1 new NamedFeature ("NEW_SETTLEMENT_ENGINE" )
这种方式灵活,但也更容易写出错别字。功能开关可不是许愿池,拼错了它不会报梦提醒你。
3.2 FeatureManager FeatureManager 是 Togglz 的核心入口,用于判断某个 Feature 是否激活。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;import org.togglz.core.manager.FeatureManager;@Service @RequiredArgsConstructor public class SettlementFeatureService { private final FeatureManager featureManager; public boolean useNewSettlementEngine () { return featureManager.isActive(BizFeatures.NEW_SETTLEMENT_ENGINE); } }
也可以在枚举里封装一个 isActive() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.togglz.core.Feature;import org.togglz.core.context.FeatureContext;import org.togglz.core.annotation.Label;public enum BizFeatures implements Feature { @Label("新版结算引擎") NEW_SETTLEMENT_ENGINE; public boolean isActive () { return FeatureContext.getFeatureManager().isActive(this ); } }
这样业务代码可以写成:
1 2 3 if (BizFeatures.NEW_SETTLEMENT_ENGINE.isActive()) { }
不过在严肃业务代码中,我更建议注入 FeatureManager,因为它更容易测试,也更符合依赖注入风格。
3.3 StateRepository StateRepository 用来存储功能开关状态。
常见存储方式:
存储方式
适合场景
是否适合生产
application.yml
本地开发、简单测试
不推荐
FileBasedStateRepository
单机应用、小型项目
视情况
JDBCStateRepository
后端服务、企业系统
推荐
Redis / MongoDB 等
特定基础设施场景
视团队能力
自定义 StateRepository
接入自研配置中心
可选
重点注意:
如果只在 application.yml 里配置 feature state,Spring Boot Starter 默认使用的是内存实现,运行时修改不会持久化。生产环境如果需要动态修改并持久化,建议显式配置 JDBCStateRepository 或其他持久化 StateRepository。
3.4 UserProvider UserProvider 用来告诉 Togglz 当前用户是谁。
它主要用于两类场景:
判断某个功能是否只对特定用户启用;
判断当前用户是否可以访问 Admin Console。
例如:
1 2 3 4 @Bean public UserProvider userProvider () { return () -> new SimpleFeatureUser ("admin" , true ); }
实际项目里通常要从 Spring Security 里获取当前用户。
3.5 ActivationStrategy ActivationStrategy 是激活策略。
功能开关不是只有 true/false,还可以有策略:
1 2 3 4 5 6 只对指定用户开启 只对指定角色开启 只对指定 IP 开启 只对指定时间后开启 只对指定比例用户开启 只对指定租户开启
比如:
1 2 3 4 5 6 7 togglz: features: TENANT_GRAY_SETTLEMENT: enabled: true strategy: tenant param: tenants: tenant_1001,tenant_1002
含义是:
这个 Feature 总开关是打开的;
但只有租户 tenant_1001 和 tenant_1002 会真正激活;
其他租户仍然走老逻辑。
3.6 Admin Console 与 Actuator Togglz 可以通过两种方式管理开关:
Admin Console:一个内嵌 Web 控制台;
Actuator Endpoint:通过接口查看和修改开关状态。
生产环境建议:
Admin Console 必须加权限;
不要暴露到公网;
最好只允许内网访问;
开关变更要打审计日志;
重要开关变更最好走审批。
功能开关很强,但别让它变成生产环境的“任意门”。
四、版本与依赖选择 4.1 版本建议 本文示例使用:
1 2 3 Java: 17+ Spring Boot: 3.x Togglz: 4.6.2
Maven 依赖:
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 42 <properties > <togglz.version > 4.6.2</togglz.version > </properties > <dependencies > <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-spring-boot-starter</artifactId > <version > ${togglz.version}</version > </dependency > <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-console</artifactId > <version > ${togglz.version}</version > </dependency > <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-spring-security</artifactId > <version > ${togglz.version}</version > </dependency > <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-testing</artifactId > <version > ${togglz.version}</version > <scope > test</scope > </dependency > <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-junit</artifactId > <version > ${togglz.version}</version > <scope > test</scope > </dependency > </dependencies >
如果你只需要最基础能力,togglz-spring-boot-starter 就够了。
如果要可视化管理开关,加入 togglz-console。
如果要测试 Feature 开关状态,加入 togglz-testing 和 togglz-junit。
4.2 Spring Boot 3 项目结构 示例项目结构:
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 togglz-demo ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com.cybermario.togglz │ │ │ ├── TogglzDemoApplication.java │ │ │ ├── config │ │ │ │ ├── TogglzConfig.java │ │ │ │ └── SecurityConfig.java │ │ │ ├── context │ │ │ │ └── TenantContext.java │ │ │ ├── feature │ │ │ │ ├── BizFeatures.java │ │ │ │ └── TenantActivationStrategy.java │ │ │ ├── settlement │ │ │ │ ├── SettlementCalculationService.java │ │ │ │ ├── OldSettlementCalculator.java │ │ │ │ ├── NewSettlementCalculator.java │ │ │ │ ├── SettlementCommand.java │ │ │ │ └── SettlementResult.java │ │ │ └── web │ │ │ └── SettlementController.java │ │ └── resources │ │ ├── application.yml │ │ └── db │ │ └── migration │ │ └── V1__create_togglz_table.sql │ └── test │ └── java │ └── com.cybermario.togglz │ └── SettlementCalculationServiceTest.java
五、第一版实战:基于 application.yml 的最小接入 先做一个最小可运行版本。
5.1 定义 Feature 枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.cybermario.togglz.feature;import org.togglz.core.Feature;import org.togglz.core.annotation.Label;public enum BizFeatures implements Feature { @Label("新版结算引擎") NEW_SETTLEMENT_ENGINE, @Label("新版应付导出") NEW_PAYABLE_EXPORT, @Label("成本月结增强校验") COST_MONTH_END_STRICT_CHECK }
5.2 配置 application.yml 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 server: port: 8080 togglz: enabled: true feature-enums: - com.cybermario.togglz.feature.BizFeatures features: NEW_SETTLEMENT_ENGINE: enabled: false NEW_PAYABLE_EXPORT: enabled: false COST_MONTH_END_STRICT_CHECK: enabled: true console: enabled: true path: /togglz-console secured: false management: endpoints: web: exposure: include: health,info,togglz
注意:
1 togglz.console.secured: false
只适合本地演示。生产环境不要这么干。
生产环境如果控制台裸奔,那不是灰度发布,是灰度自爆。
5.3 编写业务代码 以财务结算场景为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.cybermario.togglz.settlement;import com.cybermario.togglz.feature.BizFeatures;import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;import org.togglz.core.manager.FeatureManager;@Service @RequiredArgsConstructor public class SettlementCalculationService { private final FeatureManager featureManager; private final OldSettlementCalculator oldSettlementCalculator; private final NewSettlementCalculator newSettlementCalculator; public SettlementResult calculate (SettlementCommand command) { if (featureManager.isActive(BizFeatures.NEW_SETTLEMENT_ENGINE)) { return newSettlementCalculator.calculate(command); } return oldSettlementCalculator.calculate(command); } }
老逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.cybermario.togglz.settlement;import org.springframework.stereotype.Component;import java.math.BigDecimal;@Component public class OldSettlementCalculator { public SettlementResult calculate (SettlementCommand command) { BigDecimal amount = command.storeAmount() .subtract(command.feeAmount()) .add(command.adjustAmount()); return new SettlementResult ("OLD" , amount); } }
新逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.cybermario.togglz.settlement;import org.springframework.stereotype.Component;import java.math.BigDecimal;@Component public class NewSettlementCalculator { public SettlementResult calculate (SettlementCommand command) { BigDecimal amount = command.storeAmount() .subtract(command.feeAmount()) .subtract(command.buyoutPendingAmount()) .add(command.adjustAmount()); return new SettlementResult ("NEW" , amount); } }
请求模型:
1 2 3 4 5 6 7 8 9 10 11 package com.cybermario.togglz.settlement;import java.math.BigDecimal;public record SettlementCommand ( BigDecimal storeAmount, BigDecimal feeAmount, BigDecimal buyoutPendingAmount, BigDecimal adjustAmount ) { }
返回模型:
1 2 3 4 5 6 7 8 9 package com.cybermario.togglz.settlement;import java.math.BigDecimal;public record SettlementResult ( String engine, BigDecimal amount ) { }
Controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.cybermario.togglz.web;import com.cybermario.togglz.settlement.SettlementCalculationService;import com.cybermario.togglz.settlement.SettlementCommand;import com.cybermario.togglz.settlement.SettlementResult;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/api/settlements") @RequiredArgsConstructor public class SettlementController { private final SettlementCalculationService settlementCalculationService; @PostMapping("/calculate") public SettlementResult calculate (@RequestBody SettlementCommand command) { return settlementCalculationService.calculate(command); } }
5.4 启动后验证 请求:
1 2 3 4 5 6 7 8 curl -X POST http://localhost:8080/api/settlements/calculate \ -H "Content-Type: application/json" \ -d '{ "storeAmount": 1000, "feeAmount": 100, "buyoutPendingAmount": 50, "adjustAmount": 20 }'
如果 NEW_SETTLEMENT_ENGINE.enabled=false,返回:
1 2 3 4 { "engine" : "OLD" , "amount" : 920 }
如果打开新逻辑:
1 2 3 4 togglz: features: NEW_SETTLEMENT_ENGINE: enabled: true
返回:
1 2 3 4 { "engine" : "NEW" , "amount" : 870 }
六、第二版实战:接入 Admin Console 如果引入了:
1 2 3 4 5 <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-console</artifactId > <version > ${togglz.version}</version > </dependency >
并配置:
1 2 3 4 5 togglz: console: enabled: true path: /togglz-console secured: false
启动应用后访问:
1 http://localhost:8080/togglz-console
你可以在控制台看到所有 Feature,并手动开启或关闭。
6.1 生产环境必须加权限 本地可以:
1 2 3 togglz: console: secured: false
生产环境必须:
1 2 3 4 togglz: console: secured: true feature-admin-authority: ROLE_FEATURE_ADMIN
配合 Spring Security:
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 package com.cybermario.togglz.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.Customizer;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.web.SecurityFilterChain;@Configuration public class SecurityConfig { @Bean SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .authorizeHttpRequests(registry -> registry .requestMatchers("/togglz-console/**" ).hasRole("FEATURE_ADMIN" ) .requestMatchers("/actuator/togglz/**" ).hasRole("FEATURE_ADMIN" ) .anyRequest().authenticated() ) .httpBasic(Customizer.withDefaults()) .csrf(csrf -> csrf.ignoringRequestMatchers("/actuator/togglz/**" )); return http.build(); } }
注意:
是否关闭 CSRF 要看你的安全策略;
如果是管理后台,建议前端通过统一网关访问;
不建议直接暴露 Togglz Console;
开关变更最好落审计日志。
七、第三版实战:基于 JDBC 持久化开关状态 前面的 application.yml 版本有一个核心问题:
运行时通过控制台改了开关,重启后状态可能丢失。
生产环境一般要把开关状态持久化到数据库。
7.1 创建表结构 Togglz 的 JDBCStateRepository 默认使用一张表保存状态,核心字段如下:
1 2 3 4 5 6 CREATE TABLE feature_toggle ( feature_name VARCHAR (100 ) PRIMARY KEY , feature_enabled INTEGER NOT NULL , strategy_id VARCHAR (200 ), strategy_params TEXT );
如果你使用 Flyway:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CREATE TABLE IF NOT EXISTS feature_toggle ( feature_name VARCHAR (100 ) PRIMARY KEY , feature_enabled INTEGER NOT NULL , strategy_id VARCHAR (200 ), strategy_params TEXT ); COMMENT ON TABLE feature_toggle IS 'Togglz 功能开关状态表' ; COMMENT ON COLUMN feature_toggle.feature_name IS '功能开关名称' ; COMMENT ON COLUMN feature_toggle.feature_enabled IS '是否启用:1=启用,0=禁用' ; COMMENT ON COLUMN feature_toggle.strategy_id IS '激活策略ID' ; COMMENT ON COLUMN feature_toggle.strategy_params IS '激活策略参数' ;
PostgreSQL 中,未加双引号的字段名会转为小写,但 SQL 访问通常大小写不敏感,不影响使用。
7.2 配置 JDBCStateRepository 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.cybermario.togglz.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.togglz.core.repository.StateRepository;import org.togglz.core.repository.jdbc.JDBCStateRepository;import javax.sql.DataSource;@Configuration public class TogglzConfig { @Bean public StateRepository stateRepository (DataSource dataSource) { return JDBCStateRepository.newBuilder(dataSource) .tableName("feature_toggle" ) .createTable(false ) .usePostgresTextColumns(true ) .build(); } }
解释一下:
1 .tableName("feature_toggle" )
指定表名。
生产环境建议关闭自动建表,由 Flyway/Liquibase 管理表结构。
1 .usePostgresTextColumns(true )
PostgreSQL 场景下,策略参数可以使用 TEXT 类型。
7.3 初始化开关数据 可以初始化几条数据:
1 2 3 4 5 6 INSERT INTO feature_toggle (feature_name, feature_enabled, strategy_id, strategy_params)VALUES ('NEW_SETTLEMENT_ENGINE' , 0 , NULL , NULL ), ('NEW_PAYABLE_EXPORT' , 0 , NULL , NULL ), ('COST_MONTH_END_STRICT_CHECK' , 1 , NULL , NULL )ON CONFLICT (feature_name) DO NOTHING;
然后通过控制台动态修改状态。
7.4 JDBC 模式下的执行流程 sequenceDiagram
participant User as 用户请求
participant API as 业务接口
participant FM as FeatureManager
participant DB as feature_toggle
participant Old as 老逻辑
participant New as 新逻辑
User->>API: 请求结算计算
API->>FM: 判断 NEW_SETTLEMENT_ENGINE 是否激活
FM->>DB: 查询 feature_toggle
DB-->>FM: 返回开关状态
alt 开关开启
FM-->>API: true
API->>New: 执行新结算逻辑
else 开关关闭
FM-->>API: false
API->>Old: 执行老结算逻辑
end
八、第四版实战:租户级灰度 在 SaaS 系统中,最常见的灰度单位不是“用户”,而是“租户”。
例如:
1 2 3 tenant_1001:试点租户,打开新结算逻辑 tenant_1002:试点租户,打开新结算逻辑 tenant_2001:普通租户,仍然走老逻辑
这就需要自定义 ActivationStrategy。
8.1 定义 TenantContext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.cybermario.togglz.context;public final class TenantContext { private static final ThreadLocal<String> TENANT_ID = new ThreadLocal <>(); private TenantContext () { } public static void setTenantId (String tenantId) { TENANT_ID.set(tenantId); } public static String getTenantId () { return TENANT_ID.get(); } public static void clear () { TENANT_ID.remove(); } }
8.2 从请求头中提取租户 ID 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 package com.cybermario.togglz.context;import jakarta.servlet.*;import jakarta.servlet.http.HttpServletRequest;import org.springframework.stereotype.Component;import java.io.IOException;@Component public class TenantContextFilter implements Filter { private static final String TENANT_HEADER = "X-Tenant-Id" ; @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; try { String tenantId = httpRequest.getHeader(TENANT_HEADER); TenantContext.setTenantId(tenantId); chain.doFilter(request, response); } finally { TenantContext.clear(); } } }
生产环境里,租户 ID 一般来自:
JWT;
登录态;
网关解析;
租户域名;
请求头;
RPC 上下文。
不要完全信任前端直接传来的 X-Tenant-Id,否则租户隔离会很危险。
8.3 自定义 UserProvider 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 package com.cybermario.togglz.config;import com.cybermario.togglz.context.TenantContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.togglz.core.user.SimpleFeatureUser;import org.togglz.core.user.UserProvider;@Configuration public class TogglzUserConfig { @Bean public UserProvider userProvider () { return () -> { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String username = "anonymous" ; boolean featureAdmin = false ; if (authentication != null && authentication.isAuthenticated()) { username = authentication.getName(); featureAdmin = authentication.getAuthorities().stream() .anyMatch(authority -> "ROLE_FEATURE_ADMIN" .equals(authority.getAuthority())); } SimpleFeatureUser user = new SimpleFeatureUser (username, featureAdmin); user.setAttribute("tenantId" , TenantContext.getTenantId()); return user; }; } }
这里把当前租户 ID 放到了 FeatureUser 的 attribute 中。
后面的激活策略可以从用户属性里读取它。
8.4 自定义 TenantActivationStrategy 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.cybermario.togglz.feature;import org.springframework.stereotype.Component;import org.togglz.core.activation.Parameter;import org.togglz.core.activation.ParameterBuilder;import org.togglz.core.repository.FeatureState;import org.togglz.core.spi.ActivationStrategy;import org.togglz.core.user.FeatureUser;import java.util.Arrays;import java.util.Objects;@Component public class TenantActivationStrategy implements ActivationStrategy { public static final String ID = "tenant" ; public static final String PARAM_TENANTS = "tenants" ; @Override public String getId () { return ID; } @Override public String getName () { return "租户灰度策略" ; } @Override public boolean isActive (FeatureState featureState, FeatureUser user) { if (user == null ) { return false ; } Object tenantIdValue = user.getAttribute("tenantId" ); if (tenantIdValue == null ) { return false ; } String tenantId = tenantIdValue.toString(); String tenants = featureState.getParameter(PARAM_TENANTS); if (tenants == null || tenants.isBlank()) { return false ; } return Arrays.stream(tenants.split("," )) .map(String::trim) .filter(value -> !value.isBlank()) .anyMatch(value -> Objects.equals(value, tenantId)); } @Override public Parameter[] getParameters() { return new Parameter []{ ParameterBuilder.create(PARAM_TENANTS) .label("租户ID列表" ) .description("多个租户ID使用英文逗号分隔,例如 tenant_1001,tenant_1002" ) }; } }
Spring Boot Starter 会自动把 Spring 容器中的 ActivationStrategy Bean 加入到 FeatureManager。
8.5 配置租户灰度开关 1 2 3 4 5 6 7 8 9 10 togglz: feature-enums: - com.cybermario.togglz.feature.BizFeatures features: TENANT_GRAY_SETTLEMENT: enabled: true strategy: tenant param: tenants: tenant_1001,tenant_1002
业务代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.cybermario.togglz.settlement;import com.cybermario.togglz.feature.BizFeatures;import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;import org.togglz.core.manager.FeatureManager;@Service @RequiredArgsConstructor public class SettlementCalculationService { private final FeatureManager featureManager; private final OldSettlementCalculator oldSettlementCalculator; private final NewSettlementCalculator newSettlementCalculator; public SettlementResult calculate (SettlementCommand command) { if (featureManager.isActive(BizFeatures.TENANT_GRAY_SETTLEMENT)) { return newSettlementCalculator.calculate(command); } return oldSettlementCalculator.calculate(command); } }
验证:
1 2 3 4 5 6 7 8 9 curl -X POST http://localhost:8080/api/settlements/calculate \ -H "Content-Type: application/json" \ -H "X-Tenant-Id: tenant_1001" \ -d '{ "storeAmount": 1000, "feeAmount": 100, "buyoutPendingAmount": 50, "adjustAmount": 20 }'
返回新逻辑:
1 2 3 4 { "engine" : "NEW" , "amount" : 870 }
换成普通租户:
1 2 3 4 5 6 7 8 9 curl -X POST http://localhost:8080/api/settlements/calculate \ -H "Content-Type: application/json" \ -H "X-Tenant-Id: tenant_2001" \ -d '{ "storeAmount": 1000, "feeAmount": 100, "buyoutPendingAmount": 50, "adjustAmount": 20 }'
返回老逻辑:
1 2 3 4 { "engine" : "OLD" , "amount" : 920 }
九、第五版实战:用 AOP 做方法级功能保护 有些功能不是“新旧逻辑切换”,而是“功能整体关闭”。
例如:
可以用 AOP 封装一个注解。
9.1 定义注解 1 2 3 4 5 6 7 8 9 10 11 12 13 package com.cybermario.togglz.feature;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeatureGuard { BizFeatures value () ; String message () default "当前功能暂未开放" ; }
9.2 定义异常 1 2 3 4 5 6 7 8 package com.cybermario.togglz.feature;public class FeatureDisabledException extends RuntimeException { public FeatureDisabledException (String message) { super (message); } }
9.3 编写切面 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 package com.cybermario.togglz.feature;import lombok.RequiredArgsConstructor;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;import org.togglz.core.manager.FeatureManager;@Aspect @Component @RequiredArgsConstructor public class FeatureGuardAspect { private final FeatureManager featureManager; @Around("@annotation(featureGuard)") public Object around (ProceedingJoinPoint joinPoint, FeatureGuard featureGuard) throws Throwable { if (featureManager.isActive(featureGuard.value())) { return joinPoint.proceed(); } throw new FeatureDisabledException (featureGuard.message()); } }
9.4 使用注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.cybermario.togglz.web;import com.cybermario.togglz.feature.BizFeatures;import com.cybermario.togglz.feature.FeatureGuard;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class PayableExportController { @GetMapping("/api/payables/export") @FeatureGuard(value = BizFeatures.NEW_PAYABLE_EXPORT, message = "新版应付导出功能暂未开放") public String export () { return "export success" ; } }
这样业务方法里就不用到处写:
1 2 3 if (!featureManager.isActive(...)) { throw new RuntimeException (...); }
十、Actuator Endpoint 使用 如果配置了:
1 2 3 4 5 management: endpoints: web: exposure: include: health,info,togglz
可以查看开关状态:
1 curl http://localhost:8080/actuator/togglz
返回类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [ { "name" : "NEW_SETTLEMENT_ENGINE" , "enabled" : false , "strategy" : null , "params" : { } } , { "name" : "TENANT_GRAY_SETTLEMENT" , "enabled" : true , "strategy" : "tenant" , "params" : { "tenants" : "tenant_1001,tenant_1002" } } ]
也可以通过 Actuator 修改状态。不同版本的 endpoint 细节可能略有差异,建议以当前项目暴露的 /actuator/togglz 返回为准。
生产建议:
1 2 3 4 只允许内网访问 只允许管理员角色访问 所有修改必须记录审计日志 不要让普通业务用户接触该端点
十一、开启缓存:减少数据库查询 如果每次判断开关都查数据库,高频接口会有额外压力。
可以开启 Togglz cache:
1 2 3 4 5 togglz: cache: enabled: true time-to-live: 5000 time-unit: milliseconds
含义:
1 2 开关状态缓存 5 秒 5 秒内重复判断不再查数据库
生产建议:
场景
TTL 建议
风险开关,需要快速止血
1 秒 ~ 5 秒
普通灰度开关
5 秒 ~ 30 秒
很少变化的开关
30 秒 ~ 5 分钟
不要为了省几次数据库查询,把 TTL 设置得过长。
如果线上出事故,你希望开关能尽快生效,而不是等缓存慢悠悠地醒来。
十二、测试 Feature Flag Feature Flag 最大的坑是:
开关打开测了,开关关闭没测;或者反过来。
只要业务代码里有开关,就应该测试两条路径。
12.1 使用 Togglz 测试支持 依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-testing</artifactId > <version > ${togglz.version}</version > <scope > test</scope > </dependency > <dependency > <groupId > org.togglz</groupId > <artifactId > togglz-junit</artifactId > <version > ${togglz.version}</version > <scope > test</scope > </dependency >
测试示例:
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 42 43 44 45 46 47 48 49 50 package com.cybermario.togglz;import com.cybermario.togglz.feature.BizFeatures;import com.cybermario.togglz.settlement.SettlementCalculationService;import com.cybermario.togglz.settlement.SettlementCommand;import com.cybermario.togglz.settlement.SettlementResult;import org.junit.jupiter.api.Test;import org.togglz.junit5.AllDisabled;import org.togglz.junit5.AllEnabled;import org.togglz.testing.TestFeatureManager;import java.math.BigDecimal;import static org.assertj.core.api.Assertions.assertThat;class SettlementCalculationServiceTest { private final SettlementCalculationService service = TestSettlementFactory.createService(); @Test @AllDisabled(BizFeatures.class) void shouldUseOldSettlementEngineWhenFeatureDisabled (TestFeatureManager featureManager) { featureManager.disable(BizFeatures.NEW_SETTLEMENT_ENGINE); SettlementResult result = service.calculate(command()); assertThat(result.engine()).isEqualTo("OLD" ); assertThat(result.amount()).isEqualByComparingTo("920" ); } @Test @AllEnabled(BizFeatures.class) void shouldUseNewSettlementEngineWhenFeatureEnabled (TestFeatureManager featureManager) { featureManager.enable(BizFeatures.NEW_SETTLEMENT_ENGINE); SettlementResult result = service.calculate(command()); assertThat(result.engine()).isEqualTo("NEW" ); assertThat(result.amount()).isEqualByComparingTo("870" ); } private SettlementCommand command () { return new SettlementCommand ( new BigDecimal ("1000" ), new BigDecimal ("100" ), new BigDecimal ("50" ), new BigDecimal ("20" ) ); } }
上面这个示例里 TestSettlementFactory 可以自己构造 service 和依赖对象,也可以用 Spring Boot Test 注入。
核心是:开关开和关都要测 。
12.2 测试策略建议 推荐至少覆盖:
测试类型
要测什么
Unit Test
开关打开/关闭时分支是否正确
Integration Test
FeatureManager、StateRepository 是否正常
Web Test
接口在开关关闭时是否返回预期
Regression Test
删除开关前确认老逻辑已经不再需要
十三、生产环境最佳实践 13.1 命名规范 Feature 名称建议使用业务域前缀:
1 2 3 4 5 SETTLEMENT_NEW_ENGINE PAYABLE_NEW_EXPORT RECEIVABLE_RECONCILIATION_V2 COST_MONTH_END_STRICT_CHECK ORDER_NEW_SPLIT_RULE
不要使用:
1 2 3 4 5 NEW_FEATURE TEST FLAG_1 TEMP AAA
开关名必须一眼看懂,否则三个月后没人敢删。
13.2 开关分类 建议把开关分为几类。
类型
说明
示例
Release Toggle
发布型开关
新结算逻辑
Experiment Toggle
实验型开关
A/B 页面
Ops Toggle
运维型开关
关闭高风险导出
Permission Toggle
功能开放型开关
指定租户开放
Kill Switch
熔断止血开关
关闭第三方调用
不同类型的生命周期不同。
13.3 每个开关必须有 Owner 每个 Feature Flag 都应该有负责人。
建议维护元信息:
1 2 3 4 5 6 7 8 开关名称:SETTLEMENT_NEW_ENGINE 负责人:财务后端组 / 张三 创建时间:2026-06-12 计划删除时间:2026-07-30 开关类型:Release Toggle 默认状态:关闭 灰度范围:指定租户 回滚方案:关闭开关走老逻辑
你可以在文档里维护,也可以扩展数据库字段,也可以用代码注解约定。
13.4 开关不要永久存在 Feature Flag 最大的问题是容易变成技术债。
上线初期:
1 2 3 4 5 if (featureEnabled) { newLogic(); } else { oldLogic(); }
三个月后:
1 2 3 4 5 6 7 if (featureEnabled && tenantEnabled && configEnabled && userEnabled) { } else if (...) { } else { }
最后就变成开关博物馆。
建议:
1 2 3 4 发布型开关:功能全量稳定后 1~2 个迭代内删除 实验型开关:实验结束后删除 止血开关:可长期保留,但必须明确场景 运维型开关:保留,但必须有监控和审计
13.5 核心链路必须保持幂等 Feature Flag 只能控制入口,不能替代业务幂等。
例如结算单生成:
1 2 3 开关打开后走新结算逻辑 如果请求重试、任务重跑、消息重复消费 仍然必须保证不会生成重复结算单
所以还需要:
唯一索引;
幂等键;
状态机;
乐观锁;
业务流水号;
重复提交保护。
别指望一个 Feature Flag 拯救所有设计问题,它是安全带,不是自动驾驶。
13.6 开关变更要有审计 建议记录:
1 2 3 4 5 6 7 8 开关名称 修改前状态 修改后状态 修改人 修改时间 修改原因 影响范围 关联工单
生产问题排查时,这类记录非常关键。
很多事故不是因为代码变了,而是因为某个开关被人“随手一点”。
随手一点,生产升天。别问,问就是血泪经验。
13.7 监控指标 建议对关键 Feature Flag 增加监控:
1 2 3 4 5 6 7 新逻辑调用次数 老逻辑调用次数 新逻辑异常率 老逻辑异常率 新逻辑耗时 老逻辑耗时 不同租户命中情况
示例埋点:
1 2 3 4 5 6 7 if (featureManager.isActive(BizFeatures.NEW_SETTLEMENT_ENGINE)) { meterRegistry.counter("settlement.engine" , "type" , "new" ).increment(); return newSettlementCalculator.calculate(command); } meterRegistry.counter("settlement.engine" , "type" , "old" ).increment();return oldSettlementCalculator.calculate(command);
灰度发布不是“开了就完事”,而是要看数据是否正常。
十四、常见坑 14.1 只用 application.yml,误以为控制台修改会持久化 本地测试没问题,生产重启后状态丢失。
解决:
1 生产环境使用 JDBCStateRepository / FileBasedStateRepository / 其他持久化 StateRepository
14.2 控制台没加权限 本地为了方便设置:
1 2 3 togglz: console: secured: false
结果带到生产。
解决:
1 2 3 4 生产环境 secured=true 配合 Spring Security 网关限制来源 加审计
14.3 开关判断散落在业务代码里 到处都是:
1 2 if (featureManager.isActive(...)) { }
解决:
对新旧逻辑切换,可以封装策略类;
对接口级保护,可以用注解 + AOP;
对大模块,可以用 Facade 隔离;
不要让 Feature Flag 污染核心领域模型。
14.4 开关长期不删除 Release Toggle 全量后还不删,代码越来越复杂。
解决:
1 2 3 每个开关建立计划删除时间 每个迭代清理过期开关 代码审查时关注 Feature Flag 生命周期
14.5 把 Feature Flag 当业务规则引擎 Togglz 可以控制功能是否启用,但不适合承载复杂业务规则。
不要写成:
1 如果 A 租户 + B 角色 + C 时间 + D 金额 + E 门店 + F 商品类目,则启用功能
这种场景更适合规则引擎、策略模式、配置化规则表,而不是把 Togglz 用成万能 if 中央处理器。
十五、Togglz 与常见方案对比 15.1 Togglz vs Nacos
对比项
Togglz
Nacos
核心定位
Feature Flag
配置中心/注册中心
是否有 Feature 抽象
有
无
是否有激活策略
有
需要自己实现
是否适合配置普通参数
不适合
适合
是否适合功能灰度
适合
可以做,但要封装
是否有控制台
有
有
如果你已经有 Nacos,也可以直接用 Nacos 配置做开关。
但如果你的需求是:
1 2 3 4 5 6 按用户灰度 按租户灰度 按角色灰度 需要 FeatureManager 需要测试支持 需要内嵌控制台
Togglz 会更贴近 Feature Flag 场景。
15.2 Togglz vs Unleash
对比项
Togglz
Unleash
部署方式
Java 应用内嵌
独立 Feature Flag 平台
控制台
内嵌控制台
独立控制台
多语言支持
偏 Java
多语言 SDK
企业治理
轻量
更完整
适用场景
Java 项目内嵌式开关
多系统统一 Feature Flag 平台
如果只是单个 Java/Spring Boot 系统,Togglz 很轻。
如果公司有多个语言、多套系统,需要统一 Feature Flag 平台,可以考虑 Unleash、OpenFeature + Provider 等方案。
15.3 Togglz vs 自己写 if + 配置 自己写当然可以:
1 2 @Value("${features.new-settlement:false}") private boolean newSettlement;
但问题是:
没有统一 Feature 抽象;
没有激活策略;
没有控制台;
没有测试支持;
没有用户上下文;
没有状态仓库抽象;
后期会变成散落配置。
小项目可以自己写,大项目建议别手搓轮子。
手搓轮子本身没错,但轮子多了之后,车可能就不认路了。
十六、完整配置参考 16.1 application.yml 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 42 43 44 45 46 server: port: 8080 spring: application: name: togglz-demo togglz: enabled: true feature-enums: - com.cybermario.togglz.feature.BizFeatures features: NEW_SETTLEMENT_ENGINE: enabled: false NEW_PAYABLE_EXPORT: enabled: false COST_MONTH_END_STRICT_CHECK: enabled: true TENANT_GRAY_SETTLEMENT: enabled: true strategy: tenant param: tenants: tenant_1001,tenant_1002 cache: enabled: true time-to-live: 5000 time-unit: milliseconds console: enabled: true path: /togglz-console secured: true feature-admin-authority: ROLE_FEATURE_ADMIN use-management-port: false management: endpoints: web: exposure: include: health,info,togglz
16.2 Feature 枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.cybermario.togglz.feature;import org.togglz.core.Feature;import org.togglz.core.annotation.Label;public enum BizFeatures implements Feature { @Label("新版结算引擎") NEW_SETTLEMENT_ENGINE, @Label("新版应付导出") NEW_PAYABLE_EXPORT, @Label("成本月结增强校验") COST_MONTH_END_STRICT_CHECK, @Label("租户灰度结算") TENANT_GRAY_SETTLEMENT }
16.3 TogglzConfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.cybermario.togglz.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.togglz.core.repository.StateRepository;import org.togglz.core.repository.jdbc.JDBCStateRepository;import javax.sql.DataSource;@Configuration public class TogglzConfig { @Bean public StateRepository stateRepository (DataSource dataSource) { return JDBCStateRepository.newBuilder(dataSource) .tableName("feature_toggle" ) .createTable(false ) .usePostgresTextColumns(true ) .build(); } }
16.4 租户策略配置 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.cybermario.togglz.feature;import org.springframework.stereotype.Component;import org.togglz.core.activation.Parameter;import org.togglz.core.activation.ParameterBuilder;import org.togglz.core.repository.FeatureState;import org.togglz.core.spi.ActivationStrategy;import org.togglz.core.user.FeatureUser;import java.util.Arrays;import java.util.Objects;@Component public class TenantActivationStrategy implements ActivationStrategy { public static final String ID = "tenant" ; public static final String PARAM_TENANTS = "tenants" ; @Override public String getId () { return ID; } @Override public String getName () { return "租户灰度策略" ; } @Override public boolean isActive (FeatureState featureState, FeatureUser user) { if (user == null || user.getAttribute("tenantId" ) == null ) { return false ; } String currentTenantId = user.getAttribute("tenantId" ).toString(); String enabledTenants = featureState.getParameter(PARAM_TENANTS); if (enabledTenants == null || enabledTenants.isBlank()) { return false ; } return Arrays.stream(enabledTenants.split("," )) .map(String::trim) .filter(value -> !value.isBlank()) .anyMatch(value -> Objects.equals(value, currentTenantId)); } @Override public Parameter[] getParameters() { return new Parameter []{ ParameterBuilder.create(PARAM_TENANTS) .label("租户ID列表" ) .description("多个租户ID用英文逗号分隔" ) }; } }
十七、落地到财务系统的建议 如果你在财务 SaaS 里使用 Togglz,我建议优先用于这些场景:
17.1 结算逻辑重构
用途:
1 2 3 新旧结算逻辑切换 灰度租户试点 异常时快速回退老逻辑
17.2 应付导出重构
用途:
1 2 3 新版导出性能优化 大数据量导出灰度 出问题时快速关闭新版导出
17.3 成本月结增强校验 1 COST_MONTH_END_STRICT_CHECK
用途:
1 2 3 校验规则逐步打开 先观察影响范围 避免一次性拦截大量历史脏数据
17.4 第三方接口新版适配
用途:
1 2 3 外部接口切换 新老供应商切换 按租户/客户维度灰度
17.5 大客户预处理性能优化 1 SETTLEMENT_PREPROCESS_OPTIMIZATION
用途:
1 2 3 新预处理逻辑只给大客户打开 观察耗时、错误率、结果一致性 稳定后全量
十八、推荐落地流程 flowchart TD
A[定义新功能] --> B[创建 Feature Flag]
B --> C[默认关闭]
C --> D[代码合并到主干]
D --> E[测试环境打开]
E --> F[预发环境验证]
F --> G[生产指定租户灰度]
G --> H{指标是否正常}
H -- 否 --> I[关闭开关回退老逻辑]
H -- 是 --> J[扩大灰度范围]
J --> K[全量打开]
K --> L[观察一个迭代]
L --> M[删除旧逻辑和开关]
十九、总结 Togglz 的核心价值不是让你少写几行配置,而是让功能发布变得更可控。
它适合这些场景:
1 2 3 4 5 6 7 新旧逻辑切换 灰度发布 租户级开放 用户级开放 快速止血 渐进式重构 持续交付
它不适合这些场景:
1 2 3 4 5 替代权限系统 替代配置中心 替代规则引擎 替代完整 A/B 实验平台 长期堆积大量历史开关
在 Spring Boot 项目中,Togglz 的最佳实践可以总结为:
1 2 3 4 5 6 7 Feature 用枚举定义 状态用 JDBC 持久化 控制台必须加权限 关键开关必须有审计 开关开关两条路径都要测试 灰度发布必须配合监控 全量稳定后及时删除 Release Toggle
一句话:
Togglz 让功能上线从“发版即梭哈”变成“开关可控、灰度可退、风险可管”。
这就是它最有价值的地方。
参考资料
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。