在当今快速迭代的软件开发领域,如何高效地管理和控制应用程序的功能特性,成为了开发者们面临的重要挑战。想象一下,在不重启应用的情况下,就能灵活地启用或禁用某些功能,根据不同的用户群体、环境条件等因素,动态调整应用的功能展示和使用权限,这是不是非常酷?而 Togglz,这个强大的 Java 应用功能开关框架,就能帮助我们轻松实现这些功能。
Togglz 是一款专为 Java 应用打造的功能开关框架,它就像是应用功能的 “智能管家”,允许开发者在不重启应用的情况下,便捷地启用或禁用功能。以下是 Togglz 的几大关键特性,让它在众多框架中脱颖而出
Togglz 的应用场景十分广泛,在软件开发的各个环节都能发挥重要作用:
接下来,我们通过一个实际的示例,详细演示如何在 Spring Boot 项目中与 Togglz 整合,实现特性开关的功能。我们以模拟在正式环境能获取某种资源,其他环境获取不到为例,逐步展开操作
1、引入 Togglz 相关依赖
在项目的 pom.xml 文件中引入 Togglz 的依赖:
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-spring-boot-starter</artifactId>
<version>${togglz.version}</version>
</dependency>
2、定义功能开关
创建一个枚举类来定义功能开关:
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接口,自定义功能开关的管理方式:
/**
* @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:
@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依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
b、 自定义激活功能开关注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface EnvTogglz {
String activeEnv() default "${lybgeek.togglz.env:dev}";
}
c、 创建代理
实现InvocationHandler接口,创建一个代理类:
@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 代理工厂
创建一个代理工厂类,用于生成代理对象:
@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 进行增强处理:
@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:
@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 文件中配置如下依赖:
<!-- Togglz 控制台依赖,用于管理特性开关 -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-console</artifactId>
<version>${togglz.version}</version>
</dependency>
然后在application.yml文件中激活控制台:
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://github.com/lyb-geek/springboot-learning/tree/master/springboot-togglz,欢迎大家下载学习和交流。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有