WebFlux
前面我们讲了 WebFlux 基于的 Spring 框架和 Reactor 框架,现在我们来看看 WebFlux 的具体用法。
WebFlux REST Server
首先,我们来看看 WebFlux 的 REST 服务器。我们先创建一个 WebFlux 的项目,然后创建一个 REST 服务器。
首先,我们创建一个 Spring 项目,然后添加 WebFlux 的依赖,注意,如果你也喜欢用 Spring Doc,添加 WebFlux 的依赖而不是 WebMVC 的依赖。默认地址依然在/swagger-ui.html
。
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.6.0'
要创建一个 REST 服务器,我们需要创建一个 Controller,然后在 Controller 里定义 REST API。
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Hello, WebFlux!");
}
}
当然,如果你有 Service,它们也可以返回 Mono 或 Flux,这样就可以异步处理请求。
@Service
public class HelloService {
public Mono<String> hello() {
return Mono.just("Hello, WebFlux!");
}
}
@RestController
@RequiredArgsConstructor
public class HelloController {
private final HelloService helloService;
@GetMapping("/hello")
public Mono<String> hello() {
return helloService.hello();
}
}
是的,明面上和 Spring Web 的唯一区别是返回值是 Mono。
如果我们之后使用其它的库,也要使用 reactive 的版本。
Servlet 与 Netty
在本质上,Spring Web 基于 Servlet,而 WebFlux 基于 Netty。
现在从底层开始,我们来看看 Servlet 和 Netty 的区别。
Servlet
现在已经基本没人直接用 Servlet 了,但是要了解 Spring Web 的底层,我们还是要了解 Servlet。
Servlet 是 Java Web 开发的基础,它是一个 Java 类,用于处理 HTTP 请求和响应。Servlet 通过继承HttpServlet
类来实现。Servlet 是同步的,这意味着当一个请求到来时,Servlet 会阻塞线程,直到请求处理完成。这意味着 Servlet 不能处理大量的请求,因为每个请求都会占用一个线程。
要创建一个 Servlet,你需要继承HttpServlet
类,然后重写doGet
和doPost
方法。
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello, Servlet!");
}
}
要运行 Servlet,你需要一个 Servlet 容器,如 Tomcat 或 Jetty。如果是用 tomcat,你需要在web.xml
中配置 Servlet。
<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
然后,你需要将这个 war 包部署到 tomcat 中。具体而言,需要先编译 war 包,然后将 war 包放到 tomcat 的webapps
目录下。
而 Spring Web 本质上提供了一个 DispatcherServlet,它是一个 Servlet,用来把不同的请求分发给不同的 Controller。配置 DispatcherServlet 也是通过web.xml
。
<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Netty
和 Servlet 一样,Netty 也是一个 HTTP 服务器。但是 Netty 是异步的,这意味着它不会阻塞线程。Netty 是一个事件驱动的框架,它使用了 Reactor 模式,这意味着它使用了事件循环来处理请求。
Spring WebFlux 提供了两层抽象,一层是http
开头的,如HttpHandler
,一层是Web
开头的,如WebHandler
。后者更高级,前者更底层。
要创建一个 Netty 服务器,你需要创建一个HttpServer
,然后设置HttpHandler
。
public class HelloHandler implements HttpHandler {
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return response.writeWith(Mono.just(response.bufferFactory().wrap("Hello, Netty!".getBytes())));
}
}
public class NettyServer {
public static void main(String[] args) {
HttpHandler handler = new HelloHandler();
HttpServer server = HttpServer.create().host("localhost").port(8080).handle(handler);
server.bindNow();
}
}
可以看到,与 Servlet 很相似,不同点一是 Netty 是异步的,不同点二是 Netty 不再使用 xml 配置。
WebFlux Web Client
之前我们用过了RestTemplate
,而它有一个包装过的RestClient
,而WebClient
是 WebFlux 的客户端。
对于RestClient
,可以用流式 API 来构建请求。
String result = restClient.get()
.uri("https://example.com")
.retrieve()
.body(String.class);
System.out.println(result);
而WebClient
也是类似的,但是它是异步的。
WebClient client = WebClient.create("https://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
返回的是 Mono 或 Flux,这样就可以异步处理请求。
WebFlux Web Filter
WebFlux 也有WebFilter
,它是一个过滤器,可以用来处理请求和响应。
@Component
public class LoggingWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
System.out.println("Request: " + exchange.getRequest().getPath());
return chain.filter(exchange).doOnSuccess(aVoid -> {
System.out.println("Response: " + exchange.getResponse().getStatusCode());
});
}
}