1. WebFlux介绍
Spring WebFlux是SpringFramework5.0添加的新功能,WebFlux本身追随Reactive Programming而诞生的框架。
传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的,在Servlet3.1之后才有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上,因此它的运行环境的可选择行要比传统web框架多。
左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。
- Router Functions: 对标@Controller,@RequestMapping等标准的Spring MVC注解,提供一套函数式风格的API,用于创建Router,Handler和Filter。
- WebFlux: 核心组件,协调上下游各个组件提供响应式编程支持。
- Reactive Streams: 一种支持背压(Backpressure)的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor。
在Web容器的选择上,Spring WebFlux既支持像Tomcat,Jetty这样的的传统容器(前提是支持Servlet 3.1 Non-Blocking IO API),又支持像Netty,Undertow那样的异步容器。不管是何种容器,Spring WebFlux都会将其输入输出流适配成Flux格式,以便进行统一处理。
值得一提的是,除了新的Router Functions接口,Spring WebFlux同时支持使用老的Spring MVC注解声明Reactive Controller。和传统的MVC Controller不同,Reactive Controller操作的是非阻塞的ServerHttpRequest和ServerHttpResponse,而不再是Spring MVC里的HttpServletRequest和HttpServletResponse。
非阻塞:
Servlet3.1才开始支持异步处理,在此之前Servlet 线程需要一直阻塞,直到业务处理完毕再输出响应,然后结束 Servlet线程。异步处理的作用是在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,Servlet 线程继续做其他的事情。主要应用场景是针对业务处理较耗时的情况,这样就可以通过少量的线程处理更多的请求,实提现高吞吐量。
当然WebFlux提供了一种比其更完美的解决方案。使用非阻塞的方式可以利用较小的线程或硬件资源来处理并发进而提高其可伸缩性
2. 两种不同的编程模型
WebFlux服务端支持两种不同的编程模型:
- 第一种是 Spring MVC 中使用的基于 Java 注解的方式;
- 第二种是基于 Java 8 的 lambda 表达式的函数式编程模型。
这两种编程模型只是在代码编写方式上存在不同。它们运行在同样的反应式底层架构之上,因此在运行时是相同的
2.1 注解模式
与Spring MVC中的注解几乎一样,下面代码中中的 FirstFluxController 是 REST API 的控制器,通过@RestController 注解来声明。在 FirstFluxController 中声明了一个 URI 为/index 的映射。其对应的方法 index()的返回值是 Mono类型,其中包含的字符串”hello web-flux”会作为 HTTP 的响应内容。
WebFlux也支持相同的@Controller编程模型和Spring MVC中使用的相同注释。 主要区别在于底层核心,即HandlerMapping,HandlerAdapter,是非阻塞的,并且在响应的ServerHttpRequest和ServerHttpResponse上操作,而不是在HttpServletRequest和HttpServletResponse上操作。 以下是示例:
@RestController
public class FirstFluxController {
@GetMapping("/index")
public Mono<String> index() {
return Mono.just("hello web-flux!");
}
}
2.2 函数式路由模式
在函数式编程模型中,每个请求是由一个函数来处理的, 通过接口 org.springframework.web.reactive.function.server.HandlerFunction
来表示。HandlerFunction 是一个函数式接口,其中只有一个方法 Mono handle(ServerRequest request),因此可以用 labmda 表达式来实现该接口。IndexHandller中的index方法就可以通过lamda表达式包装为HandlerFunction的实现。
几个重要的概念:
- 接口 ServerRequest 表示的是一个 HTTP 请求。通过该接口可以获取到请求的相关信息,如请求路径、HTTP 头、查询参数和请求内容等。
- 方法 handle 的返回值是一个 Mono对象。
- 接口 ServerResponse 用来表示 HTTP 响应。ServerResponse 中包含了很多静态方法来创建不同 HTTP 状态码的响应对象。
@Component
public class IndexHandller {
public Mono<ServerResponse> index(ServerRequest req) {
return ok().body(Flux.just("functional rounter,base on netty"),String.class);
}
}
IndexHandller可以理解为webMVC中的Service类,它是业务逻辑的实现。在后面的路由方法中会调用IndexHandller中的方法。有了Handller之后,需要为Handller中的方法进行路由,也就是调用这些方法的条件。如/index /usercent之类的。
路由是通过函数式接口 org.springframework.web.reactive.function.server.RouterFunction
来完成的。接口RouterFunction
的方法Mono<HandlerFunction<T extends ServerResponse>> route(ServerRequest request)
对每个 ServerRequest,都返回对应的 0 个或 1 个 HandlerFunction
对象,以 Mono<HandlerFunction>
来表示。当找到对应的 HandlerFunction
时,该HandlerFunction
被调用来处理该ServerRequest
,并把得到的ServerResponse
返回。在使用 WebFlux 的 Spring Boot 应用中,只需要创建 RouterFunction 类型的 bean,就会被自动注册来处理请求并调用相应的 HandlerFunction。
@Configuration
public class webfluxconfig {
@Bean
@Autowired
public RouterFunction<ServerResponse> routes( IndexHandller indexHandller) {
return route(
GET("/homepage").and(accept(MediaType.APPLICATION_JSON)), indexHandller::index
);
}
}
相关的代码在git地址TheLudlows/webfluxDemo
通过SpringBoot启动,看起来我们没做什么事情,似乎和SpringMVC没啥区别啊!就是SpringMVC+Reactive Stream?其实不是的,只是SpringBoot的自动配置太强大了,下面就不使用Spring boot,手动的编写Server、Handler和Router。
3. without Spring Boot
Server
Spring WebFlux在Tomcat,Jetty,Servlet 3.1+容器以及非Servlet运行时(如Netty和Undertow)上受支持。 所有服务器都适用于低级通用API,因此可以跨服务器支持更高级别的编程模型。
Spring Boot有一个WebFlux启动器,可以自动执行这些步骤。默认情况下使用Netty作为服务器。
下面对两种Server的例子:
/**
* @author TheLudlows
*
*/
public class Server {
public static final String HOST = "localhost";
public static final int PORT = 8080;
private Routes routes = new Routes();
public static void main(String[] args) throws Exception {
Server server = new Server();
// server.startReactorServer();
server.startTomcatServer();
System.out.println("Press ENTER to exit.");
System.in.read();
}
public void startReactorServer() throws InterruptedException {
RouterFunction<ServerResponse> route = routes.rout();
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}
public void startTomcatServer() throws LifecycleException {
RouterFunction<?> route = routes.rout();
HttpHandler httpHandler = toHttpHandler(route);
Tomcat tomcatServer = new Tomcat();
tomcatServer.setHostname(HOST);
tomcatServer.setPort(PORT);
Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
rootContext.addServletMapping("/", "httpHandlerServlet");
tomcatServer.start();
}
}
重要的解释:
- HttpHandler - 用于HTTP请求处理的基本通用API,具有非阻塞I / O和(反应流)背压,以及每个支持的服务器的适配器。
- WebHandler API - 稍高级别,但仍然是服务器请求处理的通用API,它是更高级别的编程模型(如带注释的控制器和功能端点)的基础。
- ReactorHttpHandlerAdapter:使HttpHandler支持Reactor Netty通道处理功能。就是一个适配器
- ServletHttpHandlerAdapter:使HttpHandler支持Servlet 3.1异步非阻塞I / O。同上。
Router
/**
* @author TheLudlows
*
*/
public class Routes {
public RouterFunction<ServerResponse> rout() {
CustomerRepository repository = new CustomerRepositoryImpl();
CustomerHandler handler = new CustomerHandler(repository);
return nest(
path("/customer"),
//静态方法accept用来设定接收或者返回的请求类型
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getCustomer)
//连接方法:and(RouterFunction),andOther(RouterFunction),nest(),andNest(),andRoute()等,可以进行组合
.andRoute(method(HttpMethod.GET), handler::listCustomer))
.andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::saveCustomer)
).andNest(
path("/product"),
route(path("/"), serverRequest ->
ServerResponse.ok().contentType(APPLICATION_JSON)
.body(fromPublisher(Flux.just(new Product(1, "PC", 1000.00)), Product.class))
)
);
}
}
RouterFunction<ServerResponse>
创建是通过RouterFunctions
的静态方法route和nest两个方法创建的。
RouterFunctions.route(RequestPredicate predicate, HandlerFunction<T> handlerFunction)
其中 RequestPredicate 是一个函数式接口,接受一个’T’类型返回一个布尔类型。 大多数情况,我们都可以通过RequestPredicates类的静态方法来创建这个对象,比如:public static RequestPredicate GET(String pattern) { return method(HttpMethod.GET).and(path(pattern)); }
HandlerFunction<T>
也是一个函数式接口,前面函数路由模块也详细说到过这个接口。它接受一个ServerResponse的子类返回Mono,可以把这个对象当作实际处理逻辑的部分。public static <T extends ServerResponse> RouterFunction<T> nest(RequestPredicate predicate, RouterFunction<T> routerFunction)
nest 方法同样有一个 RequestPredicate 但是第二个参数变成了 RouterFunction routerFunction,这个方法的作用是为了分支uri。在RESTFUL风格的接口中,会有目录层级的概念,这里的nest方法就可以理解为层级的关系,比如上述的”/person”后面的”/{id}” 就是相当于 “/person/{id}”
关于Handller和Dao在此就不做介绍了,详细见代码:git地址,Handller的基本使用在前文已经介绍过了,至此WebFlux的两种编程风格以及手动创建WebFlexServer和Rounter说完了,但这仅仅算是webFlux的入门。后面会分享一些webFlux的原理和实践相关的文章^_^