一 Spring WebFlux Framework说明

Spring WebFlux 是 Spring Framework 5.0 中引入的新 reactive web framework。与 Spring MVC 不同,它不需要 Servlet API,完全异步和 non-blocking,并通过反应堆项目实现Reactive Streams规范。

Spring WebFlux 有两种版本:功能和 annotation-based。 annotation-based 一个非常接近 Spring MVC model,如下面的示例所示:

@RestController
@RequestMapping("/users")
public class MyRestController {

@GetMapping("/{user}")
public Mono<User> getUser(@PathVariable Long user) {
// ...
}

@GetMapping("/{user}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long user) {
// ...
}

@DeleteMapping("/{user}")
public Mono<User> deleteUser(@PathVariable Long user) {
// ...
}

}

函数变量“WebFlux.fn”将路由配置与请求的实际处理分开,如下面的示例所示:

@Configuration
public class RoutingConfiguration {

@Bean
public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
.andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
.andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
}

}
 
@Component
public class UserHandler {

public Mono<ServerResponse> getUser(ServerRequest request) {
// ...
}

public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
// ...
}

public Mono<ServerResponse> deleteUser(ServerRequest request) {
// ...
}
}
 

WebFlux 是 Spring Framework 的一部分,其详细信息可在reference 文档中找到。

您可以根据需要定义尽可能多的RouterFunction beans 来模块化 router 的定义。如果需要应用优先级,可以订购 Beans。

要开始,请将spring-boot-starter-webflux模块添加到 application。

在 application 中添加spring-boot-starter-webspring-boot-starter-webflux模块会导致 Spring Boot auto-configuring Spring MVC,而不是 WebFlux。选择此行为是因为许多 Spring 开发人员将spring-boot-starter-webflux添加到他们的 Spring MVC application 以使用 reactive WebClient。您仍然可以通过将所选的 application 类型设置为SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)来强制执行您的选择。

Spring WebFlux Auto-configuration

Spring Boot 为 Spring WebFlux 提供 auto-configuration,适用于大多数 applications。

auto-configuration 在 Spring 的默认值之上添加以下 features:

如果你想保留 Spring Boot WebFlux features 并且想要添加额外的WebFlux configuration,你可以添加自己的@Configuration class 类型为WebFluxConfigurer而不是 @EnableWebFlux

如果要完全控制 Spring WebFlux,可以添加自己的@Configuration注释@EnableWebFlux

带有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

Spring WebFlux 使用HttpMessageReaderHttpMessageWriter接口来转换 HTTP 请求和响应。通过查看 classpath 中可用的 libraries,它们配置为CodecConfigurer以具有合理的默认值。

Spring Boot 通过使用CodecCustomizer实例进一步自定义。例如,spring.jackson.* configuration 键应用于 Jackson 编解码器。

如果需要添加或自定义编解码器,可以创建自定义CodecCustomizer component,如下面的示例所示:

import org.springframework.boot.web.codec.CodecCustomizer;

@Configuration
public class MyConfiguration {

@Bean
public CodecCustomizer myCodecCustomizer() {
return codecConfigurer -> {
// ...
}
}

}

你也可以利用Boot 的自定义 JSON 序列化器和反序列化器

静态内容

默认情况下,Spring Boot 为 classpath 中名为/static(或/public/resources/META-INF/resources)的目录提供静态内容。它使用来自 Spring WebFlux 的ResourceWebHandler,以便您可以通过添加自己的WebFluxConfigurer并覆盖addResourceHandlers方法来修改该行为。

默认情况下,资源映射到/**,但您可以通过设置spring.webflux.static-path-pattern property 来调整它。例如,将所有资源重新定位到/resources/**可以实现如下:

spring.webflux.static-path-pattern=/resources/**

您还可以使用spring.resources.static-locations自定义静态资源位置。这样做会将默认值替换为目录位置列表。如果这样做,默认的欢迎页面检测将切换到您的自定义位置。因此,如果您在启动时的任何位置都有index.html,那么它就是 application 的主页。

除了前面列出的“标准”静态资源位置之外,还为Webjars 内容做了一个特例。如果 jar files 包含在 Webjars 格式中,则中包含路径的所有资源都将从 jar files 提供。

Spring WebFlux applications 并不严格依赖于 Servlet API,因此不能将它们部署为 war files 并且不要使用src/main/webapp目录。

模板引擎

与 REST web services 一样,您也可以使用 Spring WebFlux 来提供动态 HTML 内容。 Spring WebFlux 支持各种模板技术,包括 Thymeleaf,FreeMarker 和 Mustache。

Spring Boot 包括对以下模板引擎的 auto-configuration 支持:

当您使用其中一个模板引擎和默认的 configuration 时,您的模板将从src/main/resources/templates自动获取。

错误处理

Spring Boot 提供WebExceptionHandler,以合理的方式处理所有错误。它在处理 order 中的位置紧接在 WebFlux 提供的处理程序之前,这被认为是最后的。对于机器客户端,它会生成一个 JSON 响应,其中包含错误,HTTP 状态和 exception 消息的详细信息。对于浏览器客户端,有一个“whitelabel”错误处理程序,它以 HTML 格式呈现相同的数据。您还可以提供自己的 HTML 模板来显示错误(请参阅下一节)。

自定义此 feature 的第一个步骤通常涉及使用现有机制,但替换或扩充错误内容。为此,您可以添加ErrorAttributes类型的 bean。

要更改错误处理行为,可以实现ErrorWebExceptionHandler并注册该类型的 bean 定义。因为WebExceptionHandler非常 low-level,所以 Spring Boot 还提供了一个方便的AbstractErrorWebExceptionHandler来让你以 WebFlux 的方式处理错误,如下面的例子所示:

public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

// Define constructor here

@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {

return RouterFunctions
.route(aPredicate, aHandler)
.andRoute(anotherPredicate, anotherHandler);
}

}

要获得更完整的图片,您还可以直接子类化DefaultErrorWebExceptionHandler并覆盖特定方法。

自定义错误页面

如果要为给定状态 code 显示自定义 HTML 错误页面,可以将文件添加到/error文件夹。错误页面可以是静态 HTML(即,添加到任何静态资源文件夹下)或使用模板构建。文件的 name 应该是确切的状态 code 或系列掩码。

例如,要 map 404到静态 HTML 文件,您的文件夹结构如下:

src/
+- main/
    +- java/
    |   + <source code>
    +- resources/
        +- public/
            +- error/
            |   +- 404.html
            +- <other public assets>

要使用 Mustache 模板 map 所有5xx错误,您的文件夹结构如下:

src/
+- main/
    +- java/
    |   + <source code>
    +- resources/
        +- templates/
            +- error/
            |   +- 5xx.mustache
            +- <other templates>

Web 过滤器

Spring WebFlux 提供了一个WebFilter接口,可以实现过滤 HTTP request-response 交换。在 application context 中找到的WebFilter beans 将自动用于过滤每个交换。

如果过滤器的 order 很重要,则可以实现Ordered或使用@Order进行注释。 Spring Boot auto-configuration 可以为您配置 web 过滤器。执行此操作时,将使用以下 table 中显示的订单:

Web 过滤器 订购
MetricsWebFilter Ordered.HIGHEST_PRECEDENCE + 1
WebFilterChainProxy(Spring Security) -100
HttpTraceWebFilter Ordered.LOWEST_PRECEDENCE - 10

二 WebClient

Spring Boot 将 auto-detect 用于驱动WebClient,具体取决于 application classpath 上可用的 libraries。目前,支持 Reactor Netty 和 Jetty RS client。

spring-boot-starter-webflux starter 默认依赖于io.projectreactor.netty:reactor-netty,它带来了 server 和 client implementations。如果您选择使用 Jetty 作为 reactive 服务器,则应该在 Jetty Reactive HTTP client library,org.eclipse.jetty:jetty-reactive-httpclient上添加依赖项。对服务器和 client 使用相同的技术具有优势,因为它将自动在 client 和服务器之间共享 HTTP 资源。

开发人员可以通过提供自定义ReactorResourceFactoryJettyResourceFactory bean 来覆盖 Jetty 和 Reactor Netty 的资源 configuration - 这将应用于 clients 和服务器。

如果您希望覆盖 client 的该选项,您可以定义自己的ClientHttpConnector bean 并完全控制 client configuration。

您可以了解有关Spring Framework reference 文档中的 WebClient configuration 选项的更多信息。

WebClient 自定义

WebClient自定义有三种主要方法,具体取决于您希望自定义应用的广泛程度。

要使任何自定义的范围尽可能窄,请 inject auto-configured WebClient.Builder然后根据需要调用其方法。 WebClient.Builder实例是有状态的:构建器上的任何更改都会反映在随后使用它创建的所有 client 中。如果要使用相同的构建器创建多个 client,还可以考虑使用WebClient.Builder other = builder.clone();克隆构建器。

要对所有WebClient.Builder实例进行 application-wide 添加自定义,可以声明WebClientCustomizer beans 并在注入点本地更改WebClient.Builder

最后,您可以回退到原始 API 并使用WebClient.create()。在这种情况下,不应用 auto-configuration 或WebClientCustomizer

三 代码演示

  • pom.xml

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    注:webflux不能和web共存,webflux启动的是netty。

  • 实体类

    public class User {

    private String id; private String name; ​
    public String getId() {
    return id;
    }

    public void setId(String id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public User(String id, String name) {
    super();
    this.id = id;
    this.name = name;
    } }
  • Service层

    @Service
    public class UserService {
    ​ private static final Map<String, User> dataMap = new HashMap<>(); static{
    dataMap.put("1", new User("1", "小X老师"));
    dataMap.put("2", new User("2", "小D老师"));
    dataMap.put("3", new User("3", "小C老师"));
    dataMap.put("4", new User("4", "小L老师"));
    dataMap.put("5", new User("5", "小A老师"));
    dataMap.put("6", new User("6", "小S老师"));
    dataMap.put("7", new User("7", "小S老师"));
    } /**
    * 功能描述:返回用户列表
    * @return
    */
    public Flux<User> list(){
    Collection<User> list = UserService.dataMap.values(); return Flux.fromIterable(list);
    } /**
    * 功能描述:根据id查找用户
    * @param id
    * @return
    */
    public Mono<User> getById(final String id){
    return Mono.justOrEmpty(UserService.dataMap.get(id));
    } /**
    * 功能描述:根据id删除用户
    * @param id
    * @return
    */
    public Mono<User> del(final String id){
    return Mono.justOrEmpty(UserService.dataMap.remove(id));
    } }
  • web层

    @RestController
    @RequestMapping("/api/v1/user")
    public class UserController { ​
    //@Autowired
    //private UserService userService; private final UserService userService; public UserController(final UserService userService) {
    this.userService = userService;
    } @GetMapping("/test")
    public Mono<String> test(){
    return Mono.just("hello");
    }
    ​ /**
    * 功能描述:根据id找用户
    * @param id
    * @return
    */
    @GetMapping("find")
    public Mono<User> findByid(final String id){
    return userService.getById(id);
    } /**
    * 功能描述:删除用户
    * @param id
    * @return
    */
    @GetMapping("del")
    public Mono<User> del(final String id){
    return userService.del(id);
    } /**
    * 功能描述:列表
    * @return
    */
    @GetMapping(value="list",produces=MediaType.APPLICATION_STREAM_JSON_VALUE)
    public Flux<User> list(){
    return userService.list().delayElements(Duration.ofSeconds(2));
    } }
  • 测试类

    //@RunWith(SpringRunner.class)
    //@SpringBootTest
    public class WebfluxApplicationTests {


    @Test
    public void testBase(){ Mono<String> bodyMono = WebClient.create().get()
    .uri("http://localhost:8080/api/v1/user/find?id=1")
    .accept(MediaType.APPLICATION_JSON)
    .retrieve().bodyToMono(String.class); System.out.println(bodyMono.block()); } ​
    @Test
    public void testBase2(){ Mono<String> bodyMono = WebClient.create().get()
    .uri("http://localhost:8080/api/v1/user/find?id={id}",2)
    .accept(MediaType.APPLICATION_JSON)
    .retrieve().bodyToMono(String.class); System.out.println(bodyMono.block()); }

    }
    注:着重讲一下这个list方法,这个方法里面延迟2秒,会体现出是流一样,每隔2秒出现一条数据。 {"id":"1","name":"小X老师"}
    {"id":"2","name":"小D老师"}
    {"id":"3","name":"小C老师"}
    {"id":"4","name":"小L老师"}
    {"id":"5","name":"小A老师"}
    {"id":"6","name":"小S老师"}
    {"id":"7","name":"小S老师"}

SpringBoot之响应式编程的更多相关文章

  1. SpringBoot使用WebFlux响应式编程操作数据库

    这一篇文章介绍SpringBoot使用WebFlux响应式编程操作MongoDb数据库. 前言 在之前一篇简单介绍了WebFlux响应式编程的操作,我们在来看一下下图,可以看到,在目前的Spring ...

  2. 【SpringBoot】SpringBoot2.0响应式编程

    ========================15.高级篇幅之SpringBoot2.0响应式编程 ================================ 1.SprinBoot2.x响应 ...

  3. SpringBoot 2.x (14):WebFlux响应式编程

    响应式编程生活案例: 传统形式: 一群人去餐厅吃饭,顾客1找服务员点餐,服务员把订单交给后台厨师,然后服务员等待, 当后台厨师做好饭,交给服务员,经过服务员再交给顾客1,依此类推,该服务员再招待顾客2 ...

  4. springboot(二十三)Springboot2.X响应式编程

    序言 Spring WebFlux是Spring Framework 5.0中引入的新的反应式Web框架与Spring MVC不同,它不需要Servlet API,完全异步和非阻塞,并 通过React ...

  5. SpringBoot实战派读书笔记---响应式编程

    1.什么是WebFlux? WebFlux不需要Servlet API,在完全异步且无阻塞,并通过Reactor项目实现了Reactor Streams规范. WebFlux可以在资源有限的情况下提高 ...

  6. (转)Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门

    http://www.ityouknow.com/springboot/2019/02/12/spring-boot-webflux.html Spring 5.0 中发布了重量级组件 Webflux ...

  7. spring5响应式编程

    1.Spring5新特性    2.响应式编程响应式编程:非阻塞应用程序,借助异步和事件驱动还有少量的线程垂直伸缩,而非横向伸缩(分布式集群)当Http连接缓慢的时候,从数据库到Http数据响应中也会 ...

  8. Spring Boot (十四): 响应式编程以及 Spring Boot Webflux 快速入门

    1. 什么是响应式编程 在计算机中,响应式编程或反应式编程(英语:Reactive programming)是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流 ...

  9. 07-Spring5 WebFlux响应式编程

    SpringWebFlux介绍 简介 SpringWebFlux是Spring5添加的新模块,用于Web开发,功能和SpringMvc类似的,WebFlux使用当前一种比较流行的响应式编程框架 使用传 ...

随机推荐

  1. 基于操作系统原理的Red Hat Linux的基本操作

    一.实验目的 1.了解Linux操作系统的启动与登录方法. 2.掌握Red Hat Linux图形用户界面下的基本操作. 3.学会Red Hat Linux基本设置. 二.实验内容 1. 登录 2. ...

  2. MySQL在Linux系统环境的安装和无主机登录配置

           将mysql 安装在单个Linux系统主机,并配置本地或远程(此处可仅单指局域状态下的环境)的无主机登录.谨记的是:操作完mysql 设置时,需以flush privileges进行权限 ...

  3. go 学习笔记之10 分钟简要理解 go 语言闭包技术

    闭包是主流编程语言中的一种通用技术,常常和函数式编程进行强强联合,本文主要是介绍 Go 语言中什么是闭包以及怎么理解闭包. 如果读者对于 Go 语言的闭包还不是特别清楚的话,可以参考上一篇文章 go ...

  4. Python基础(十四)

    今日主要内容 装饰器扩展 有参装饰器 多个装饰器装饰一个函数 递归 一.装饰器扩展 (一)含有参数的装饰器 先来回顾一下装饰器的标准模式 def wrapper(fn): def inner(*arg ...

  5. 第三方日志库logrus使用

    日志是程序中必不可少的一个环节,由于Go语言内置的日志库功能比较简洁,我们在实际开发中通常会选择使用第三方的日志库来进行开发.本文介绍了logrus这个日志库的基本使用. logrus介绍 Logru ...

  6. 完美实现保存和加载easyui datagrid自定义调整列宽位置隐藏属性功能

    需求&场景 例表查询是业务系统中使用最多也是最基础功能,但也是调整最平凡,不同的用户对数据的要求也不一样,所以在系统正式使用后,做为开发恨不得坐在业务边上,根据他们的要求进行调整,需要调整最多 ...

  7. kotlin系列文章 --- 3.条件控制

    if表达式 一个if语句包含一个布尔表达式和一条或多条语句 // 基础用法 var max = a if (a<b) max = b // 加上else var max: Int if(a> ...

  8. Java 学习笔记之 Stop停止线程

    Stop停止线程: 使用stop()方法停止线程是非常暴力的,会抛出java.lang.ThreadDeath Error,但是我们无需显示捕捉, 以下捕捉只是为了看得更清晰. public clas ...

  9. Kafka 学习笔记之 Consumer API

    Kafka提供了两种Consumer API High Level Consumer API Low Level Consumer API(Kafka诡异的称之为Simple Consumer API ...

  10. CSS隐藏元素 display、visibility、opacity的区别

    关于使指定元素无法在视野内看到,有3个方法 display: none; opacity: 0; visibility: hidden; 1.display: none; 该方法会改变页面布局. 元素 ...