2018年8月

【翻译】Guide to Spring 5 WebFlux

原文链接:http://www.baeldung.com/spring-webflux

1. 概览

Spring WebFlux 框架是 Spring 5的一部分,他为 web 应用提供了响应式编程的支持。

在本文中,我们将使用响应式注解RestControllerWebClient创建一个小的 REST 应用,

我们还将研究如何使用Spring Security保护我们的响应式端点。

2. Spring WebFlux 框架

Spring WebFlux内部使用Project Reactor及其发布者实现 - Flux和Mono。

这个新框架支持下面两种编程模型:

  • 基于注释的响应式组件
  • 函数级别的路由和处理

在这里,我们将重点关注基于注释的响应式组件,正如我们已经研究过的functional style – routing and handling.

3. 依赖

让我们从spring-boot-starter-webflux依赖开始,它包含了所有其他必需的依赖:

  • 用于基本Spring Boot应用程序设置的spring-boot和spring-boot-starter
  • spring-webflux框架
  • 我们使用 reactive streams 和 reactor-netty需要的reactor-core

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <version>2.0.3.RELEASE</version>
    </dependency>

最新的spring-boot-starter-webflux 可以从Maven Central下载。

4. 响应式 REST 应用

我们将使用 Spring WebFlux 来创建一个非常简单的响应式 REST 员工管理应用:

  • 我们将使用一个简单的域模型 - 具有id和name字段的Employee
  • 我们将使用RestController和WebClient构建REST API,用于发布和检索Single以及Collection Employeeresources
  • 我们还将使用WebFlux和Spring Security创建安全的响应式端点

5. 响应式的 RestController

Spring WebFlux和Spring Web MVC框架一样,都支持基于注释的配置。

首先,在服务器端,我们创建一个带注释的控制器,用于发布我们的Employee反应流。

让我们创建带注释的EmployeeController:

@RestController
@RequestMapping("/employees")
public class EmployeeReactiveController {
 
    private final EmployeeRepository employeeRepository;
     
    // constructor...
}

EmployeeRepository可以是支持非阻塞响应流的任何数据存储库。

5.1. 单一资源

让我们在控制器中创建一个发布单个Employee资源的端点

@GetMapping("/{id}")
private Mono<Employee> getEmployeeById(@PathVariable String id) {
    return employeeRepository.findEmployeeById(id);
}

对于单个Employee资源,我们使用了Employee类型的Mono,因为它最多会发出1个元素。

5.2. 集合资源

我们还在我们的控制器中添加一个端点,用于发布所有Employees的集合资源:

@GetMapping
private Flux<Employee> getAllEmployees() {
    return employeeRepository.findAllEmployees();
}

对于集合资源,我们使用了Employee类型的Flux - 因为那是发布者专注于发出0..n元素。

6. 响应式 web 客户端

在Spring 5中引入的WebClient是一个支持Reactive Streams的非阻塞客户端。

在客户端,我们使用WebClient从EmployeeController中创建的端点检索数据。

让我们创建一个简单的EmployeeWebClient:

public class EmployeeWebClient {
 
    WebClient client = WebClient.create("http://localhost:8080");
 
    // ...
}

这里我们使用其工厂方法create创建了一个WebClient。 对于相对URL,它将指向localhost:8080。

6.1. 获取单一资源

To retrieve single resource of type Mono from endpoint /employee/{id}:

/employee/{id}中返回资源类型Mono获取单一资源:

Mono<Employee> employeeMono = client.get()
  .uri("/employees/{id}", "1")
  .retrieve()
  .bodyToMono(Employee.class);
 
employeeMono.subscribe(System.out::println);

6.2. 获取集合资源

同样的,通过/employees中返回资源类型Flux来获取集合资源:

Flux<Employee> employeeFlux = client.get()
  .uri("/employees")
  .retrieve()
  .bodyToFlux(Employee.class);
         
employeeFlux.subscribe(System.out::println);

详细的说明参考这篇文章 setting up and working with WebClient.

7. Spring WebFlux Security

我们可以使用 Spring Security 来对我们的响应式端点进行加密.

假设我们的EmployeeController中有一个新的端点。 此端点更新Employee详细信息并返回更新后的Employee

由于这允许用户更改现有员工,因此我们希望仅将此端点限制为ADMIN角色用户。

让我们为EmployeeController添加一个新方法:

@PostMapping("/update")
private Mono<Employee> updateEmployee(@RequestBody Employee employee) {
    return employeeRepository.updateEmployee(employee);
}

现在,为了限制对此方法的访问,让我们创建SecurityConfig并定义一些基于路径的规则来保证仅允许ADMIN用户:

@EnableWebFluxSecurity
public class EmployeeWebSecurityConfig {
 
    // ...
 
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(
      ServerHttpSecurity http) {
        http.csrf().disable()
          .authorizeExchange()
          .pathMatchers(HttpMethod.POST, "/employees/update").hasRole("ADMIN")
          .pathMatchers("/**").permitAll()
          .and()
          .httpBasic();
        return http.build();
    }
}

此配置将限制对端点/employees/update的访问。 因此,只有具有ADMIN角色的用户才能访问此端点并更新现有Employee

最后,@EnableWebFluxSecurity注解添加了一些默认配置的Spring Security WebFlux支持。

更详细的说明参见 configuring and working with Spring WebFlux security.

8. 总结

在本文中,我们通过创建一个小型的Reactive REST应用程序,探索了如何创建和使用Spring WebFlux框架支持的反应式Web组件。

我们学习了如何使用RestController和WebClient分别发布和使用反应流。

我们还研究了如何在Spring Security的帮助下创建安全的反应端点。

除了Reactive RestController和WebClient之外,WebFlux框架还支持反应式WebSocket和相应的WebSocketClient,用于Reactive Streams的套接字样式流。

更详细的资料请阅读 working with Reactive WebSocket with Spring 5.

最后,本文的所有的源码都可以在 Github找到。

【翻译】Handling Errors in Spring WebFlux

原文:http://www.baeldung.com/spring-webflux-errors

1. 概览

在本教程中,我们通过一个实际的例子来看一下可用于处理Spring WebFlux项目中的错误的各种策略

我们还将指出在哪种情况下使用一种策略会比另外一种好,在本文最后将提供所有源码的下载地址。

2. 配置实例

上一篇文章 previous article 中已经提到了maven的配置, 并对 Spring Webflux做了简单的介绍。

在这个例子中,我们为一个 RESTful 端点加上一个名为 username 的查询参数,并以“Hello username”作为结果返回。

First, let’s create a router function that routes the /hello request to a method named handleRequest in the passed-in handler:

首先,让我们创建一个路由器函数,将/hello请求路由名为handleRequest的方法中:

@Bean
public RouterFunction<ServerResponse> routeRequest(Handler handler) {
    return RouterFunctions.route(RequestPredicates.GET("/hello")
      .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), 
        handler::handleRequest);
    }

接下来,我们将定义handleRequest()方法,该方法调用sayHello()方法并在ServerResponse主体中包含/返回其结果的方法:

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return
      //...
        sayHello(request)
      //...
}

最后,sayHello()是一个简单的实用工具方法,它将“Hello”和 username 连接起来返回。

private Mono<String> sayHello(ServerRequest request) {
    //...
    return Mono.just("Hello, " + request.queryParam("name").get());
    //...
}

只要用 username 作为我们请求的一部分存在,例如使用“/hello?username=Tonni”访问,我们的端点就可以正确运行。

然而,如果我们调用"/hello"的时候没有使用 username 这个参数,它会抛出一个异常。

下面,我们将看看我们在何处如何重新组织我们的代码才能在WebFlux中处理此异常。

3. 在函数级别处理错误

Mono和Flux API内置了两个关键操作符,用于处理功能级别的错误。

让我们简要地探讨它们及其用法。

3.1. 使用 onErrorReturn

当出现错误时,我们可以使用 onErrorReturn()来返回一个静态的默认值。

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return sayHello(request)
      .onErrorReturn("Hello Stranger")
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s));
}

sayHello()抛出异常时,函数就会默认返回"Hello Stranger"。

3.2. 使用onErrorResume

使用onErrorResume处理错误有三种方式:

  • 计算动态返回值
  • 使用fallback方法 跳转到备份路径
  • 捕获,包装和重新抛出错误,例如 作为自定义业务异常

让我们看看怎么杨计算一个值:

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
          .syncBody(s))
        .onErrorResume(e -> Mono.just("Error " + e.getMessage())
          .flatMap(s -> ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .syncBody(s)));
}

在这里,每当sayHello()抛出异常时,我们将返回一个字符串,该字符串由附加到字符串“Error”的动态获取的错误消息组成。

接下来,当错误发生时我们调用 fallback 方法:

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s))
      .onErrorResume(e -> sayHelloFallback()
      .flatMap(s ->; ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s)));
}

在这里,只要sayHello()抛出异常,我们就会调用替代方法sayHelloFallback()。

使用onErrorResume()的最后一个选项是捕获,包装和重新抛出错误,例如 作为NameRequiredException

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return ServerResponse.ok()
      .body(sayHello(request)
      .onErrorResume(e -> Mono.error(new NameRequiredException(
        HttpStatus.BAD_REQUEST, 
        "username is required", e))), String.class);
}

在这里,只要sayHello()抛出异常,我们就会抛出一个自定义异常,并带有消息:"username is required"。

4. 全局级别的错误处理

到目前为止,我们提供的所有示例都在函数级别上处理了错误处理。

但是,我们可以选择在全局范围内处理我们的WebFlux错误。 要做到这一点,我们只需要采取两个步骤:

  • 自定义全局错误响应属性
  • 实现全局错误处理程序

我们的处理程序抛出的异常将被自动转换为HTTP状态和JSON错误正文。 要自定义这些,我们可以简单地扩展DefaultErrorAttributes类并覆盖其getErrorAttributes()方法:

public class GlobalErrorAttributes extends DefaultErrorAttributes{
     
    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, 
      boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(
          request, includeStackTrace);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }
 
}

在这里,我们希望状态:BAD_REQUEST和消息:"username is required"在发生异常时作为错误属性的一部分返回。

接下来,让我们实现全局错误处理程序。 为此,Spring提供了一个方便的AbstractErrorWebExceptionHandler类,供我们在处理全局错误时进行扩展和实现:

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
    AbstractErrorWebExceptionHandler {
 
    // constructors
 
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(
      ErrorAttributes errorAttributes) {
 
        return RouterFunctions.route(
          RequestPredicates.all(), this::renderErrorResponse);
    }
 
    private Mono<ServerResponse> renderErrorResponse(
       ServerRequest request) {
 
       Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
 
       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON_UTF8)
         .body(BodyInserters.fromObject(errorPropertiesMap));
    }
}

在这个例子中,我们将全局错误处理程序的顺序设置为-2。 这是为了给它一个比在@Order(-1)注册的DefaultErrorWebExceptionHandler更高的优先级。

errorAttributes对象将是我们在Web异常处理程序的构造函数中传递的副本的精确副本。 理想情况下,这应该是我们自定义的Error Attributes类。

然后,我们清楚地说明我们想要将所有错误处理请求路由到renderErrorResponse()方法。

最后,我们获取错误属性并将它们插入服务器响应主体中。

然后,它会生成一个JSON响应,其中包含错误,HTTP状态和计算机客户端的异常消息的详细信息。 对于浏览器客户端,它有一个“whitelabel”错误处理程序,它以HTML格式呈现相同的数据。 当然,这可以是定制的。

5. 结尾

在本文中,我们研究了可用于处理Spring WebFlux项目中的错误的各种策略,并指出了使用一种策略而不是另一种策略的优势。

正如所承诺的那样,本文附带的完整源代码可以在 GitHub获得。