spring-flux

最近在整理springcloud,在整理springcloud-gateway的时候出了一点问题.spring-cloud-gateway是基于spring-flux实现,所以不会spring-flux的话会很闹心啊。

一、为什么

springmvc他不香了吗?那自然不能的。官方给出的原因是:

  1. 部分原因是需要使用非阻塞web堆栈来处理少量线程的并发性,并使用更少的硬件资源进行伸缩
  2. 另一个原因就是函数式编程

那么Reactive是什么,英译,反应的,其实我更喜欢响应式的。

spring-flux是非阻塞的,非阻塞是被动的,因为我们现在处于操作完成或数据可用时对通知做出反应的模式,而不是被阻塞的模式。谈到这个,那么应该联想到网络编程(socket),网络编程中服务端是需要监听(accept)端口,也就是只有在客户端发出请求的时候服务端才能做出响应,但是我们往往需要的不是这样的结果,所以会通过nio将其变成非阻塞的。他们都是类似的。

二、什么时候用?

首先看看他和springmvc有什么区别:

 

 

 从上图不能看出,他们各有功能并且还有通用的,那么什么时候用呢?

  1. 如果您有一个工作良好的Spring MVC应用程序,则无需更改。命令式编程是编写、理解和调试代码的最简单的方法。您可以最大限度地选择库,因为从历史上看,大多数库都是被阻塞的
  2. 如果你已经购买一个非阻塞的网络堆栈,Spring WebFlux提供了和其他人同样的执行模型的好处在这个空间,也提供了一个选择的服务器(Netty,Undertow,Tomcat、Jetty Servlet 3.1 +容器),选择编程模型(带注释的控制器和功能性web端点),和选择反应库(RxJava反应堆,或其他)。
  3. 如果您对与Java 8 lambdas或Kotlin一起使用的轻量级、功能性web框架感兴趣,您可以使用Spring WebFlux functional web endpoints。对于需求不那么复杂的小型应用程序或微服务,这也是一个不错的选择,因为它们可以从更大的透明性和控制中获益
  4. 在微服务体系结构中,您可以使用Spring MVC或Spring WebFlux控制器或Spring WebFlux功能端点的混合应用程序。在这两种框架中都支持相同的基于注释的编程模型,这使得在为正确的工作选择正确的工具时更容易重用知识。
  5. 评估应用程序的一种简单方法是检查其依赖关系。如果您有阻塞持久化api (JPA、JDBC)或网络api要使用,Spring MVC至少是通用架构的最佳选择。在单独的线程上使用Reactor和RxJava执行阻塞调用在技术上是可行的,但是您不能充分利用非阻塞web堆栈。
  6. 如果您有一个调用远程服务的Spring MVC应用程序,请尝试响应式WebClient。您可以直接从Spring MVC控制器方法返回反应类型(Reactor、RxJava或其他)。每次调用的延迟或调用之间的相互依赖性越大,好处就越明显。Spring MVC控制器也可以调用其他响应组件。
  7. 如果您有一个大型团队,请记住,在向非阻塞式、函数式和声明式编程的转变过程中,需要陡峭的学习曲线。一种不需要完全切换就可以开始的实用方法是使用响应式web客户机。除此之外,从小事开始,衡量好处。我们认为,对于广泛的应用程序,这种转变是不必要的。如果您不确定寻找哪些好处,那么首先了解一下非阻塞I/O是如何工作的(例如,单线程Node.js上的并发性)及其影响。

三、怎么用

现在市场上流行的框架是springboot,而springboot原生支持spring-flux,所以这里就用springboot演示了

使用方法有两种,一种是基于Annotated Controllers,另一种就是Functional Endpoints

  • Annotated Controllers 

    这一种是基于注解实现,他和springmvc的注解是一样的,所以会springmvc的话这种方式是很容易上手的。想@Controller,@RestController等注解都是通用的

 

 可以看到,实现方式和springmvc没什么两样。不同的是springmvc是基于Servlet 阻塞式实现,而spring-flux是非阻塞的,所以request、response等实现是不同的(就是说不能再用httpservletrequest了)

  @GetMapping("test/{id}")
    public String test(ServerWebExchange exchange, @PathVariable long id, @MatrixVariable int age) {
        Object name = exchange.getAttribute("name");
        System.out.println("name = " + name);
        System.out.println("id = " + id);
        System.out.println("age = " + age);
        return "请求成功";
    }

以下是spring-flux控制层方法所有支持的入参:

 

Controller method argumentDescription

ServerWebExchange

访问完整的ServerWebExchange——HTTP请求和响应、请求和会话属性、checkNotModified方法等容器。

ServerHttpRequestServerHttpResponse

访问HTTP请求或响应

WebSession

访问会话。除非添加了属性,否则这不会强制开始新会话。支持反应类型

java.security.Principal

当前经过身份验证的用户——如果已知,可能是特定的主体实现类。支持反应类型。

org.springframework.http.HttpMethod

请求的HTTP方法。

java.util.Locale

当前请求区域设置,由可用的最特定的LocaleResolver确定—实际上,即配置的LocaleResolver/LocaleContextResolver。

java.util.TimeZone + java.time.ZoneId

与当前请求关联的时区,由LocaleContextResolver确定。

@PathVariable

用于访问URI模板变量. See URI Patterns.

@MatrixVariable

用于访问URI路径段中的名称-值对. See Matrix Variables.

@RequestParam

参数值被转换为声明的方法参数类型. See @RequestParam.

@RequestHeader

头值被转换为声明的方法参数类型. See @RequestHeader.

@CookieValue

Cookie值被转换为声明的方法参数类型. See @CookieValue.

@RequestBody

请求体内容通过使用HttpMessageReader实例转换为声明的方法参数类型。支持反应类型。See @RequestBody.

HttpEntity<B>

主体使用HttpMessageReader实例进行转换。支持反应类型. See HttpEntity.

@RequestPart

用于访问多部件/表单数据请求中的部件。支持反应类型。See Multipart Content and Multipart Data.

java.util.Maporg.springframework.ui.Model, and org.springframework.ui.ModelMap.

用于访问在HTML控制器中使用并作为视图呈现的一部分公开给模板的模型。

@ModelAttribute

 用于访问应用了数据绑定和验证的模型中已存在的属性(如果不存在则实例化)。See @ModelAttribute as well as Model and DataBinder.

ErrorsBindingResult

错误或BindingResult参数必须在验证方法参数之后立即声明。

SessionStatus + class-level @SessionAttributes

用于标记表单处理完成,这将触发清除通过类级别的@SessionAttributes注释声明的会话属性。更多细节请参见@SessionAttributes。

UriComponentsBuilder

用于准备相对于当前请求的主机、端口、方案和路径的URL. See URI Links.

@SessionAttribute

用于访问任何会话属性——与作为类级别@SessionAttributes声明的结果存储在会话中的模型属性相反。 See @SessionAttribute for more details.

@RequestAttribute

用于访问请求属性。 See @RequestAttribute for more details.

Any other argument

如果一个方法参数没有与上面的任何一个匹配,默认情况下,如果它是简单类型,就解析为@RequestParam,如BeanUtils#isSimpleProperty所确定的那样,否则解析为@ModelAttribute。

 

下表为返回支持的对象:

Controller method return valueDescription

@ResponseBody

返回值通过HttpMessageWriter实例进行编码并写入响应。 See @ResponseBody.

HttpEntity<B>ResponseEntity<B>

返回值指定完整的响应,包括HTTP头,并且主体通过HttpMessageWriter实例进行编码并写入响应。 See ResponseEntity.

HttpHeaders

用于返回带有标题而没有正文的响应.

String

用ViewResolver实例解析并与隐式模型一起使用的视图名称——通过命令对象和@ModelAttribute方法确定。处理程序方法还可以通过声明模型参数(前面已经描述过)以编程方式丰富模型。

View

一个视图实例,用于与隐式模型一起呈现——通过命令对象和@ModelAttribute方法确定。处理程序方法还可以通过声明模型参数(前面已经描述过)以编程方式丰富模型。

java.util.Maporg.springframework.ui.Model

属性要添加到隐式模型中,视图名称根据请求路径隐式确定。

@ModelAttribute

要添加到模型中的属性,视图名根据请求路径隐式确定。注意@ModelAttribute是可选的。参见该表后面的“任何其他返回值”。

Rendering

用于模型和视图呈现场景的API。

void

如果一个方法有一个void,可能是异步的(例如mono&void&gt),返回类型(或者一个null返回值),如果它也有一个ServerHttpResponse,一个ServerWebExchange参数,或者一个@ResponseStatus注释,那么它被认为已经完全处理了响应。如果控制器对ETag或lastModified时间戳进行了正确的检查,也同样如此。// TODO: See Controllers for details.

如果以上都不为真,则void返回类型还可以指示REST控制器“无响应体”或HTML控制器的默认视图名称选择。

Flux<ServerSentEvent>Observable<ServerSentEvent>, or other reactive type

释放服务器发送的事件。当只需要写入数据时,可以省略ServerSentEvent包装器(但是,必须通过produces属性在映射中请求或声明文本/事件流)。

Any other return value

如果返回值不匹配任何上述情况,,默认情况下,视为一个视图名称,如果是字符串或无效(默认视图名称选择适用),或作为一个模型属性被添加到模型中,除非它是一个简单类型,由BeanUtils # isSimpleProperty,在这种情况下,它仍未得到解决。

 

  • Functional Endpoints

 Spring WebFlux包括WebFlux.fn,一种轻量级函数编程模型,其中函数用于路由和处理请求,契约被设计为不变性。它是基于注释的编程模型的替代方案,但在其他方面运行在相同的反应性核心基础上。

那么到底怎么用呢?

这里需要两个,一个路由(route),一个处理器(handler)

处理器:

port org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;

import static org.springframework.web.reactive.function.server.ServerResponse.*;

import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

/**
* <p></p>
*
* @author lhf
* @since 2020/10/13 9:39
*/
@Component
public class SysUserHandler {


@Resource
private SysUserService sysUserService;

public Mono<ServerResponse> selectOne(ServerRequest request) {
String id = request.queryParam("id").get().trim();

return ok().body(BodyInserters.fromValue(this.sysUserService.queryById(Long.valueOf(id))));
}


}

然后是路由:

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import javax.annotation.Resource;

import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;

/**
* <p></p>
*
* @author lhf
* @since 2020/10/13 8:44
*/
@Configuration
public class SysUserRouter {

@Resource
private SysUserHandler handler;

@Bean
public RouterFunction<ServerResponse> test() {
return route(GET("/test"), response -> ok().body(fromValue("成功了瓜皮")));
}


@Bean
public RouterFunction<ServerResponse> selectOne() {
return route().GET("/sysUser/selectOne", contentType(MediaType.ALL), handler::selectOne).build();
}
}

第一个路由是一个测试路由,直接返回,第二个将会调用处理器,去处理逻辑

初步使用就是这样了!!!!

 

菜鸟一个,欢迎大佬吐槽

 

 

     

 

/**
* <p></p>
*
* @author lhf
* @since 2020/10/13 9:39
*/
public class SysUserHandler {
private SysUserRouter router;

public SysUserHandler(SysUserRouter router) {
this.router = router;
}

@Resource
private SysUserService sysUserService;

public Mono<ServerResponse> selectOne(ServerRequest request) {
String id = request.queryParam("id").get().trim();
return ok().body(BodyInserters.fromValue(this.sysUserService.queryById(Long.valueOf(id))));
}



}
posted @ 2020-10-13 17:24  葬月!  阅读(715)  评论(0编辑  收藏  举报