Spring Boot和Togglz切面

2023/05/12

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上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章