前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >超详细!Spring Boot 项目与 Togglz 深度整合实现特性开关全攻略

超详细!Spring Boot 项目与 Togglz 深度整合实现特性开关全攻略

原创
作者头像
lyb-geek
发布于 2025-05-13 01:31:16
发布于 2025-05-13 01:31:16
810
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

在当今快速迭代的软件开发领域,如何高效地管理和控制应用程序的功能特性,成为了开发者们面临的重要挑战。想象一下,在不重启应用的情况下,就能灵活地启用或禁用某些功能,根据不同的用户群体、环境条件等因素,动态调整应用的功能展示和使用权限,这是不是非常酷?而 Togglz,这个强大的 Java 应用功能开关框架,就能帮助我们轻松实现这些功能。

一、认识 Togglz:功能开关框架的魅力

Togglz 是一款专为 Java 应用打造的功能开关框架,它就像是应用功能的 “智能管家”,允许开发者在不重启应用的情况下,便捷地启用或禁用功能。以下是 Togglz 的几大关键特性,让它在众多框架中脱颖而出

  • 功能开关:Togglz 通过定义功能开关,精准控制哪些功能对用户可见或可用,为功能管理提供了清晰的界限和规则。
  • 动态配置:支持在运行时更改功能开关的状态,无需重新部署应用程序,大大提高了开发和运维的效率,让功能调整更加灵活及时。
  • 多种存储方式:可以将功能开关的状态存储在不同的后端,如数据库文件系统或内存中,满足不同场景下的数据存储和管理需求。
  • 集成简单:与 Spring、CDI 等主流框架有良好的集成,易于在现有项目中引入,降低了技术升级和功能扩展的成本。
  • 用户界面:提供了一个直观的管理界面,方便管理员查看和修改功能开关的状态,操作便捷,一目了然。

二、Togglz 的常用应用场景

Togglz 的应用场景十分广泛,在软件开发的各个环节都能发挥重要作用:

  • 逐步发布新功能: 在大型项目中,新功能可能需要逐步发布给部分用户,以确保其稳定性和性能。Togglz 可以帮助你精准控制哪些用户可以看到新功能,实现平滑的功能上线。
  • A/B 测试: 通过功能开关,你可以轻松地为不同用户群体提供不同的功能版本,从而进行 A/B 测试,评估新功能的效果,为产品优化提供数据支持。
  • 紧急回滚: 如果新功能在生产环境中出现问题,可以快速关闭功能开关,而无需重新部署整个应用,从而减少停机时间,保障业务的连续性。
  • 内部测试: 在功能开发阶段,可以仅对内部测试人员开放新功能,确保功能在正式发布前经过充分测试,提高产品质量。
  • 环境隔离: 不同的环境(如开发、测试、生产)可以有不同的功能开关配置,确保每个环境中的功能状态符合需求,避免环境差异带来的问题。
  • 按需启用高级功能: 对于付费用户或特定客户,可以通过功能开关控制高级功能的访问权限,实现差异化服务,提升用户体验和商业价值。
  • 性能优化: 某些功能可能会影响应用性能,可以在生产环境中临时关闭这些功能,以监控和优化性能,保障应用的高效运行。

三、实战演示:Spring Boot 项目与 Togglz 的整合

接下来,我们通过一个实际的示例,详细演示如何在 Spring Boot 项目中与 Togglz 整合,实现特性开关的功能。我们以模拟在正式环境能获取某种资源,其他环境获取不到为例,逐步展开操作

1、引入 Togglz 相关依赖

在项目的 pom.xml 文件中引入 Togglz 的依赖:

代码语言:xml
AI代码解释
复制
   <dependency>
            <groupId>org.togglz</groupId>
            <artifactId>togglz-spring-boot-starter</artifactId>
            <version>${togglz.version}</version>
        </dependency>

2、定义功能开关

创建一个枚举类来定义功能开关:

代码语言:java
AI代码解释
复制
public enum EnvFeature implements Feature {

    @Label("生产环境")
    PROD,

    @Label("预发布环境")
    UAT,

    @Label("测试环境")
    TEST,


    @EnabledByDefault
    @Label("开发环境")
    DEV;

    public boolean isActive() {
        return FeatureContext.getFeatureManager().isActive(this);
    }

    public static EnvFeature getEnvFeature(String env){
        EnvFeature[] envFeatures = EnvFeature.values();
        for (EnvFeature envFeature : envFeatures) {
            if(envFeature.name().equalsIgnoreCase(env)){
                return envFeature;
            }
        }
        throw new IllegalArgumentException("env参数不合法");
    }
}

@Label注解用于为功能开关提供一个友好的显示名称,方便在管理界面中识别;@EnabledByDefault注解用于标记某个功能开关在默认情况下是启用的

3、定义togglz功能管理配置

实现TogglzConfig接口,自定义功能开关的管理方式:

代码语言:java
AI代码解释
复制
/**
 * @see <a href="https://www.togglz.org/documentation/configuration">...</a>
 */

@Slf4j
@RequiredArgsConstructor
public class EnvTogglzConfig implements TogglzConfig , InitializingBean {

    private final ObjectProvider<List<StateRepositoryFactory>> listObjectProvider;
    private final EnvTogglzProperties envTogglzProperties;

    private Map<String,StateRepositoryFactory> stateRepositoryMap;
    @Override
    public Class<? extends Feature> getFeatureClass() {
        // 返回定义功能开关的枚举类
        return EnvFeature.class;
    }

    /***
     * @see <a href="https://www.togglz.org/documentation/repositories"></a>
     * @return
     */
    @Override
    public StateRepository getStateRepository() {
        //配置功能开关状态的存储方式
        if(stateRepositoryMap == null || stateRepositoryMap.isEmpty()){
            log.warn("stateRepositoryFactories is empty,select inMemoryStateRepository");
            return new InMemoryStateRepository();
        }

        return stateRepositoryMap.get(envTogglzProperties.getStateRepositoryType()).create();

    }

    /**
     * @see <a href="https://www.togglz.org/documentation/authentication"></a>
     * @return
     */
    @Override
    public UserProvider getUserProvider() {
        // 提供功能开关的提供者,返回当前登录的用户对象
      return new SingleUserProvider("lybgeek", true);
    }

    @Override
    public void afterPropertiesSet() {

        List<StateRepositoryFactory> stateRepositoryFactories = listObjectProvider.getIfAvailable();
        if(CollectionUtils.isEmpty(stateRepositoryFactories)){
            return;
        }
        stateRepositoryMap = new HashMap<>(stateRepositoryFactories.size());
        stateRepositoryFactories.forEach(stateRepositoryFactory -> stateRepositoryMap.put(stateRepositoryFactory.supportType(),stateRepositoryFactory));


    }
}

在这个配置类中,我们主要完成了以下几个配置点:

  • 功能开关类: 指定定义了所有功能开关的枚举类。
  • 用户解析: 提供一个方法来解析当前用户,这对于基于用户的功能开关策略非常重要。
  • 状态持久化: 配置功能开关状态的存储方式,例如内存、数据库、文件等。
  • 策略配置: 配置功能开关的激活策略,例如基于用户角色、请求参数、时间等

3、创建功能管理器

创建一个FeatureManager的 Bean:

代码语言:java
AI代码解释
复制
   @Bean
    @ConditionalOnMissingBean
    public FeatureManager featureManager(EnvTogglzConfig envTogglzConfig){
        return new FeatureManagerBuilder()
                .featureEnum(envTogglzConfig.getFeatureClass())
                .stateRepository(envTogglzConfig.getStateRepository())
                .userProvider(envTogglzConfig.getUserProvider())
                .build();
    }

FeatureManager提供了一系列的方法来查询和修改功能开关的状态,以及执行与功能开关相关的操作,包括查询功能开关状态、修改功能开关状态、获取功能开关元数据、执行功能开关策略等。

5、使用功能开关

这边炫技一下,我们使用bytebuddy来实现一个切面,以更灵活地使用功能开关。

a、 项目pom引入bytebuddy依赖

代码语言:java
AI代码解释
复制
 <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>${bytebuddy.version}</version>
        </dependency>

b、 自定义激活功能开关注解

代码语言:java
AI代码解释
复制
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface EnvTogglz {

    String activeEnv() default "${lybgeek.togglz.env:dev}";
}

c、 创建代理

实现InvocationHandler接口,创建一个代理类:

代码语言:java
AI代码解释
复制
@RequiredArgsConstructor
public class EnvTogglzInvocationHandler implements InvocationHandler {

    private final String activeEnv;
    private final Object target;
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(StringUtils.isEmpty(activeEnv)){
            return method.invoke(target, args);
        }

        if(EnvFeature.getEnvFeature(activeEnv).isActive()){
            return method.invoke(target, args);
        }

        throw new EnvTogglzException("非法访问", HttpStatus.FORBIDDEN.value());
    }
}

核心代码通过判断功能开关是否激活,来决定是否执行目标方法。

d、 创建 bytebuddy 代理工厂

创建一个代理工厂类,用于生成代理对象:

代码语言:java
AI代码解释
复制
@Slf4j
public final class EnvTogglzProxyFactory {
    private EnvTogglzProxyFactory(){}

    public static <T> T createProxy(T target,String activeEnv) {

        Class proxy = new ByteBuddy()
                .subclass(target.getClass())
                .method(any())
                .intercept(InvocationHandlerAdapter.of(new EnvTogglzInvocationHandler(activeEnv,target)))
                .attribute(MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER)
                .annotateType(target.getClass().getAnnotations())
                .make()
                .load(EnvTogglzProxyFactory.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

        try {
            return (T)proxy.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
            log.error("create proxy error",e);
        }

        return null;

    }

e、 通过bytebuddy增强

实现SmartInstantiationAwareBeanPostProcessor接口,对 Bean 进行增强处理:

代码语言:java
AI代码解释
复制
@Slf4j
public class EnvTogglzProxyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        EnvTogglz envTogglz = getEnvTogglz(bean);
        if(envTogglz != null){
            String activeEnv = applicationContext.getEnvironment().resolvePlaceholders(envTogglz.activeEnv());
            return EnvTogglzProxyFactory.createProxy(bean,activeEnv);
        }

        return bean;

    }



    private EnvTogglz getEnvTogglz(Object bean) {
        boolean aopProxy = AopUtils.isAopProxy(bean);
        Class targetClz = bean.getClass();
        if(aopProxy){
           targetClz = AopUtils.getTargetClass(bean);
        }

        for (Method declaredMethod : targetClz.getDeclaredMethods()) {
            EnvTogglz envTogglz = AnnotationHelper.getAnnotation(declaredMethod, EnvTogglz.class);
            if(envTogglz != null){
                return envTogglz;
            }
        }

        return null;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


}

6、测试功能开关

创建一个测试的 Controller:

代码语言:java
AI代码解释
复制
@RestController
@RequestMapping("test")
@RequiredArgsConstructor
public class TestController {

    @GetMapping("info")
    @EnvTogglz
    public ResponseEntity<String> info(){
        return ResponseEntity.ok("生产环境-资源");
    }
}

通过浏览器访问http://localhost:8080/test/info

发现访问不到正式的资源,因为我们默认激活的环境开关是DEV

要使用 Togglz 提供的控制面板,首先在项目的 pom.xml 文件中配置如下依赖:

代码语言:java
AI代码解释
复制
    <!-- Togglz 控制台依赖,用于管理特性开关 -->
        <dependency>
            <groupId>org.togglz</groupId>
            <artifactId>togglz-console</artifactId>
            <version>${togglz.version}</version>
        </dependency>

然后在application.yml文件中激活控制台:

代码语言:yaml
AI代码解释
复制
togglz:
  console:
    enabled: true
    path: /togglz
    secured: true

secured表示开启访问权限,权限取决于配置的UserProvider。浏览器访问http://localhost:8080/togglz

然后将生产环境改为激活状态

最后再次访问http://localhost:8080/test/info

,即可获取到资源。

四、总结

通过上述详细的步骤,我们成功地在 Spring Boot 项目中与 Togglz 进行了整合,实现了特性开关的功能。Togglz 为我们提供了一种灵活、高效的功能管理方式,能够帮助我们更好地应对软件开发过程中的各种挑战。本文只是对 Togglz 的一个入门介绍,如果大家对 Togglz 的源码感兴趣,还可以深入研究,其中的一些实现思路和技巧可以为我们的日常开发带来很多启发。如果想对 Togglz 有更深入的理解,可以查看官方网站:

https://www.togglz.org/

五、demo链接

本文的示例代码可以在以下链接中获取:

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-togglz,欢迎大家下载学习和交流。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、认识 Togglz:功能开关框架的魅力
  • 二、Togglz 的常用应用场景
  • 三、实战演示:Spring Boot 项目与 Togglz 的整合
  • 四、总结
  • 五、demo链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档