1. 概述
在本教程中,我们介绍如何将Togglz库与Spring Boot应用程序一起使用。
2. Togglz
Togglz库提供了功能切换设计模式的实现,此模式指的是拥有一种机制,该机制允许在应用程序运行时根据切换确定是否启用某个功能。
在运行时禁用功能可能在各种情况下都很有用,例如处理尚未完成的新功能、希望仅允许部分用户访问功能或运行A/B测试。
在接下来的部分中,我们将创建一个切面来拦截带有提供功能名称的注解的方法,并根据该功能是否启用来确定是否继续执行这些方法。
3. Maven依赖
除了Spring Boot依赖项之外,Togglz库还提供了一个Spring Boot Starter:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-spring-boot-starter</artifactId>
<version>2.4.1</version>
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-spring-security</artifactId>
<version>2.4.1</version>
</dependency>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.194</version>
</dependency>
最新版本的[togglz-spring-boot-starter](https://search.maven.org/classic/#search | ga | 1 | togglz spring boot starter),[togglz-spring-security](https://search.maven.org/classic/#search | ga | 1 | togglz spring security),[spring-boot-starter-web](https://search.maven.org/classic/#search | ga | 1 | a%3A”spring-boot-starter-web”),[spring-boot-starter-data-jpa](https://search.maven.org/classic/#search | ga | 1 | a%3A”spring-boot-starter-data-jpa”),[spring-boot-starter-test](https://search.maven.org/classic/#search | ga | 1 | a%3A”spring-boot-starter-test”),[h2](https://search.maven.org/classic/#search | ga | 1 | a%3A”h2”)可以从Maven Central下载。 |
4. Togglz配置
togglz-spring-boot-starter库包含用于创建必要bean的自动配置,例如FeatureManager。我们唯一需要提供的bean是featureProvider bean。
首先,让我们创建一个实现Feature接口并包含功能名称列表的枚举:
public enum MyFeatures implements Feature {
@Label("Employee Management Feature")
EMPLOYEE_MANAGEMENT_FEATURE;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
枚举还定义了一个名为isActive()的方法,该方法用于验证是否启用了某个功能。
然后我们可以在Spring Boot配置类中定义一个EnumBasedFeatureProvider类型的bean:
@Configuration
public class ToggleConfiguration {
@Bean
public FeatureProvider featureProvider() {
return new EnumBasedFeatureProvider(MyFeatures.class);
}
}
5. 创建切面
接下来,我们将创建一个切面用于拦截自定义的AssociatedFeature注解并检查注解参数中提供的功能以确定它是否处于活动状态:
@Aspect
@Component
public class FeaturesAspect {
private static final Logger LOG = Logger.getLogger(FeaturesAspect.class);
@Around("@within(featureAssociation) || @annotation(featureAssociation)")
public Object checkAspect(ProceedingJoinPoint joinPoint, FeatureAssociation featureAssociation) throws Throwable {
if (featureAssociation.value().isActive()) {
return joinPoint.proceed();
} else {
LOG.info("Feature " + featureAssociation.value().name() + " is not enabled!");
return null;
}
}
}
我们还定义一个名为@FeatureAssociation的自定义注解,该注解将具有MyFeatures枚举类型的value()参数:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface FeatureAssociation {
MyFeatures value();
}
如果该功能处于活动状态,则切面将继续执行该方法;如果没有,它将在不运行方法代码的情况下记录一条消息。
6. 功能激活
Togglz中的功能可以是活动的也可以是非活动的,此行为由启用标志和可选的激活策略控制。
要将启用标志设置为true,我们可以在枚举值定义上使用@EnabledByDefault注解。
Togglz库还提供了多种激活策略,可用于根据特定条件确定是否启用某个功能。
在我们的示例中,让我们为EMPLOYEE_MANAGEMENT_FEATURE使用SystemPropertyActivationStrategy,该策略根据系统属性的值评估功能的状态,可以使用@ActivationParameter注解指定所需的属性名称和值:
public enum MyFeatures implements Feature {
@Label("Employee Management Feature")
@EnabledByDefault
@DefaultActivationStrategy(id = SystemPropertyActivationStrategy.ID,
parameters = {
@ActivationParameter(
name = SystemPropertyActivationStrategy.PARAM_PROPERTY_NAME,
value = "employee.feature"),
@ActivationParameter(
name = SystemPropertyActivationStrategy.PARAM_PROPERTY_VALUE,
value = "true") })
EMPLOYEE_MANAGEMENT_FEATURE
// ...
}
我们已经将我们的功能设置为仅当employee.feature属性的值为true时才启用。
Togglz库提供的其他类型的激活策略包括:
- UsernameActivationStrategy:允许该功能对指定的用户列表处于活动状态
- UserRoleActivationStrategy:当前用户的角色用于确定功能的状态
- ReleaseDateActivationStrategy:在特定日期和时间自动激活功能
- GradualActivationStrategy:为指定百分比的用户启用功能
- ScriptEngineActivationStrategy:允许使用以JVM的ScriptEngine支持的语言编写的自定义脚本来确定功能是否处于活动状态
- ServerIpActivationStrategy:基于服务器IP地址启用的功能
7. 测试切面
7.1 示例应用程序
为了查看我们的切面的实际应用,让我们创建一个简单示例,其中包含用于管理组织员工的功能。
随着此功能的开发,我们可以添加使用值为EMPLOYEE_MANAGEMENT_FEATURE的@AssociatedFeature注解进行标注的方法和类,这确保只有在该功能处于活动状态时才能访问它们。
首先让我们定义一个基于Spring Data的Employee实体类和repository:
@Entity
public class Employee {
@Id
private long id;
private double salary;
// standard constructor, getters, setters
}
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
}
接下来,让我们添加一个EmployeeService,其中包含一个增加员工工资的方法,我们将使用EMPLOYEE_MANAGEMENT_FEATURE参数将@AssociatedFeature注解添加到方法上:
@Service
public class SalaryService {
@Autowired
EmployeeRepository employeeRepository;
@FeatureAssociation(value = MyFeatures.EMPLOYEE_MANAGEMENT_FEATURE)
public void increaseSalary(long id) {
Employee employee = employeeRepository.findById(id).orElse(null);
employee.setSalary(employee.getSalary() + employee.getSalary() * 0.1);
employeeRepository.save(employee);
}
}
该方法将从我们将调用以进行测试的/increaseSalary端点调用:
@Controller
public class SalaryController {
@Autowired
SalaryService salaryService;
@PostMapping("/increaseSalary")
@ResponseBody
public void increaseSalary(@RequestParam long id) {
salaryService.increaseSalary(id);
}
}
7.2 JUnit测试
首先,让我们添加一个测试,在将employee.feature属性设置为false后调用我们的POST端点。在这种情况下,该功能不应处于活动状态,并且员工薪水的值不应发生变化:
@Test
void givenFeaturePropertyFalse_whenIncreaseSalary_thenNoIncrease()throws Exception {
Employee emp = new Employee(1, 2000);
employeeRepository.save(emp);
System.setProperty("employee.feature", "false");
mockMvc.perform(post("/increaseSalary")
.param("id", emp.getId() + ""))
.andExpect(status().is(200));
emp = employeeRepository.findOne(1L);
assertEquals("salary incorrect", 2000, emp.getSalary(), 0.5);
}
接下来,我们添加一个测试,在将属性设置为true后执行调用。在这种情况下,工资的值应该增加:
@Test
void givenFeaturePropertyTrue_whenIncreaseSalary_thenIncrease()throws Exception {
Employee emp = new Employee(1, 2000);
employeeRepository.save(emp);
System.setProperty("employee.feature", "true");
mockMvc.perform(post("/increaseSalary")
.param("id", emp.getId() + ""))
.andExpect(status().is(200));
emp = employeeRepository.findById(1L).orElse(null);
assertEquals("salary incorrect", 2200, emp.getSalary(), 0.5);
}
8. 总结
在本教程中,我们演示了如何使用切面将Togglz库与Spring Boot集成。
与往常一样,本教程的完整源代码可在GitHub上获得。