Spring Boot and Togglz Aspect – Spring Boot和Togglz Aspect

最后修改: 2017年 5月 2日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to take a look at how the Togglz library can be used with a Spring Boot application.

在本教程中,我们将看看如何将Togglz库用于Spring Boot应用程序。

2. Togglz

2.Togglz

The Togglz library provides an implementation of the Feature Toggles design pattern. This pattern refers to having a mechanism that allows determining during the runtime of an application whether a certain feature is enabled or not based on a toggle.

Togglz提供了Feature Toggles设计模式的一个实现。该模式指的是拥有一种机制,允许在应用程序的运行期间根据切换来确定是否启用某个功能。

Disabling a feature at runtime may be useful in a variety of situations such as working on a new feature which is not yet complete, wanting to allow access to a feature only to a subset of users or running A/B testing.

在运行时禁用一个功能可能在各种情况下都是有用的,如在一个尚未完成的新功能上工作,希望只允许一个子集的用户访问一个功能,或运行A/B测试。

In the following sections, we will create an aspect that intercepts methods with an annotation that provides a feature name, and determine whether to continue executing the methods depending on if the feature is enabled or not.

在下面的章节中,我们将创建一个方面,拦截带有注解的方法,该注解提供了一个特性名称,并根据该特性是否被启用来决定是否继续执行这些方法。

3. Maven Dependencies

3.Maven的依赖性

Along with the Spring Boot dependencies, the Togglz library provides a Spring Boot Starter jar:

除了Spring Boot的依赖,Togglz库还提供了一个Spring Boot Starter jar。

<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>

The latest versions of togglz-spring-boot-starter, togglz-spring-security, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-test, h2 can be downloaded from Maven Central.

togglz-spring-boot-startertogglz-spring-securityspring-boot-starter-webspring-boot-starter-data-jpaspring-boot-starter-testh2的最新版本可以从Maven中心下载。

4. Togglz Configuration

4.Togglz配置

The togglz-spring-boot-starter library contains auto-configuration for creating the necessary beans such as FeatureManager. The only bean we need to provide is the featureProvider bean.

togglz-spring-boot-starter库包含自动配置,用于创建必要的Bean,比如FeatureManager。我们唯一需要提供的Bean是featureProviderBean。

First, let’s create an enumeration that implements the Feature interface and contains a list of feature names:

首先,让我们创建一个枚举,该枚举实现了Feature接口并包含一个特征名称的列表。

public enum MyFeatures implements Feature {

    @Label("Employee Management Feature")
    EMPLOYEE_MANAGEMENT_FEATURE;

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

The enumeration also defines a method called isActive() that verifies whether a certain feature is enabled.

该枚举还定义了一个名为isActive()的方法,用于验证某个功能是否被启用。

Then we can define a bean of type EnumBasedFeatureProvider in a Spring Boot configuration class:

然后我们可以在Spring Boot配置类中定义一个EnumBasedFeatureProvider类型的bean。

@Configuration
public class ToggleConfiguration {

    @Bean
    public FeatureProvider featureProvider() {
        return new EnumBasedFeatureProvider(MyFeatures.class);
    }
}

5. Creating the Aspect

5.创建 “前景”

Next, we will create an aspect that intercepts a custom AssociatedFeature annotation and checks the feature provided in the annotation parameter to determine whether it is active or not:

接下来,我们将创建一个方面,拦截一个自定义的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;
        }
    }
}

Let’s also define the custom annotation called FeatureAssociation that will have a value() parameter of type MyFeatures enum:

让我们也定义名为FeatureAssociation的自定义注解,它将有一个value()类型为MyFeatures枚举的参数。

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface FeatureAssociation {
    MyFeatures value();
}

If the feature is active, the aspect will continue the execution of the method; if not, it will log a message without running the method code.

如果该功能是激活的,该方面将继续执行该方法;如果不是,它将记录一条信息而不运行该方法的代码。

6. Feature Activation

6.功能激活

A feature in Togglz can be either active or inactive. This behavior is controlled by an enabled flag and optionally an activation strategy.

Togglz中,一个功能可以是激活或不激活。这种行为由一个enabled标志和可选的激活策略控制。

To set the enabled flag to true, we can use the @EnabledByDefault annotation on the enum value definition.

为了将enabled标志设置为真,我们可以在枚举值定义上使用@EnabledByDefault注解。

Togglz library also provides a variety of activation strategies that can be used to determine whether a feature is enabled based on a certain condition.

Togglz库还提供了各种激活策略,可用于根据某个条件来确定是否启用某个功能。

In our example, let’s use the SystemPropertyActivationStrategy for our EMPLOYEE_MANAGEMENT_FEATURE which evaluates the feature’s state based on the value of a System property. The required property name and value can be specified using the @ActivationParameter annotation:

在我们的示例中,让我们为我们的 EMPLOYEE_MANAGEMENT_FEATURE 使用 SystemPropertyActivationStrategy ,它根据一个 System 属性的值来评估该功能的状态。可以使用@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;
    //...
}

We have set our feature to be enabled only if the employee.feature property has the value true.

我们将我们的功能设置为只有在employee.feature属性的值为true时才会被启用。

Other types of activation strategies provided by the Togglz library are:

Togglz库提供的其他类型的激活策略是。

  • UsernameActivationStrategy – allows the feature to be active for a specified list of users
  • UserRoleActivationStrategy – the current user’s role is used to determine the state of a feature
  • ReleaseDateActivationStrategy – automatically activates a feature at a certain date and time
  • GradualActivationStrategy – enables a feature for a specified percentage of users
  • ScriptEngineActivationStrategy – allows the use of a custom script written in a language supported by the ScriptEngine of the JVM to determine whether a feature is active or not
  • ServerIpActivationStrategy – a feature is enabled based on IP addresses of the server

7. Testing the Aspect

7.测试方面的问题

7.1. Example Application

7.1.应用实例

To see our aspect in action, let’s create a simple example that contains a feature for managing the employees of an organization.

为了看到我们的行动,让我们创建一个简单的例子,其中包含管理一个组织的雇员的功能。

As this feature will be developed, we can add methods and classes annotated with our @AssociatedFeature annotation with a value of EMPLOYEE_MANAGEMENT_FEATURE. This ensures that they will only be accessible if the feature is active.

由于这个功能将被开发,我们可以添加方法和类,用我们的@AssociatedFeature注解,值为EMPLOYEE_MANAGEMENT_FEATURE。这可以确保它们只有在该功能处于活动状态时才能被访问。

First, let’s define an Employee entity class and repository based on Spring Data:

首先,让我们定义一个Employee实体类和基于Spring Data的资源库。

@Entity
public class Employee {

    @Id
    private long id;
    private double salary;
    
    // standard constructor, getters, setters
}
public interface EmployeeRepository
  extends CrudRepository<Employee, Long>{ }

Next, let’s add an EmployeeService with a method to increase an employee’s salary. We will add the @AssociatedFeature annotation to the method with a parameter of EMPLOYEE_MANAGEMENT_FEATURE:

接下来,让我们添加一个EmployeeService,其中有一个方法可以增加雇员的薪水。我们将为该方法添加@AssociatedFeature注解,参数为EMPLOYEE_MANAGEMENT_FEATURE

@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);
    }
}

The method will be called from an /increaseSalary endpoint that we will call for testing:

该方法将从一个/increaseSalary端点调用,我们将调用该端点进行测试。

@Controller
public class SalaryController {

    @Autowired
    SalaryService salaryService;

    @PostMapping("/increaseSalary")
    @ResponseBody
    public void increaseSalary(@RequestParam long id) {
        salaryService.increaseSalary(id);
    }
}

7.2. JUnit Test

7.2.JUnit测试

First, let’s add a test in which we call our POST mapping after setting the employee.feature property to false. In this case, the feature should not be active and the value of the employee’s salary should not change:

首先,让我们添加一个测试,在将employee.feature属性设置为false后调用我们的POST映射。在这种情况下,该功能不应该被激活,雇员的工资值不应该改变。

@Test
public 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);
}

Next, let’s add a test where we perform the call after setting the property to true. In this case, the value of the salary should be increased:

接下来,让我们添加一个测试,在设置属性为true后执行调用。在这种情况下,工资的值应该被增加。

@Test
public 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. Conclusions

8.结论

In this tutorial, we’ve shown how we can integrate Togglz library with Spring Boot by using an aspect.

在本教程中,我们展示了如何通过使用一个方面将Togglz库与Spring Boot集成。

The full source code of the example can be found over on GitHub.

该示例的完整源代码可以在GitHub上找到over