Spring Web MVC is designed based on the Servlet API and Servlet container. Spring WebFlux is definitely not based on the first two. It is based on the Reactive Streams API and the Servlet 3.1+ container design.
What is the Reactive Streams API?
What is a Stream? A flow is a sequence of elements produced by a producer and consumed by one or more consumers. This specific design pattern is called the publish and subscribe pattern. A common flow processing mechanism is the pull/push pattern. Back pressure is a common strategy that allows publishers to have unlimited buffers for items to ensure that publishers do not suppress subscribers if they publish items too quickly. Reactive Streams is a standard that provides the ability to handle non-blocking backpressure asynchronous Streams. The main scenarios are runtime environments (including JVM and JS) and networks. Also, the JDK 9 java.util.Concurrent package provides two main apis for handling response flows: -flow-submissionPublisher
Why can only run in Servlet 3.1+ containers?
As you know, one of the new features of the 3.1 specification is asynchronous processing support. Asynchronous processing support: The Servlet thread does not need to block all the time, that is, it does not need to output a response until the business is finished and then terminate the Servlet thread. The purpose of asynchronous processing is that after receiving the request, the Servlet thread can delegate the time-consuming operation to another thread, returning it to the container without generating a response. It is applicable to time-consuming service processing scenarios, reducing server resource usage and increasing concurrent processing speed. Therefore, WebFlux supports Tomcat, Jetty (non-blocking IO API), and also supports asynchronous containers like Netty and Undertow. In the container, Spring WebFlux ADAPTS the input stream to Mono or Flux format for unified processing.
What is Spring WebFlux
Let’s start with this diagram, where we know about containers, response flows. What is Spring WebFlux? Spring WebFlux is a new module of Spring 5 that includes responsive HTTP and WebSocket support, plus support for two different programming models on the upper server side: – Based on Spring MVC annotation @Controller etc. – Based on Functional routing
Here are two implementation examples, starting with adding the corresponding dependency to POM.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Copy the code
Based on Spring MVC annotation RESTful API
The official case is simple, as follows:
@RestController public class PersonController { private final PersonRepository repository; public PersonController(PersonRepository repository) { this.repository = repository; } @PostMapping("/person") Mono<Void> create(@RequestBody Publisher<Person> personStream) { return this.repository.save(personStream).then(); } @GetMapping("/person") Flux<Person> list() { return this.repository.findAll(); } @GetMapping("/person/{id}") Mono<Person> findById(@PathVariable String id) { return this.repository.findOne(id); }}Copy the code
PersonRepository Spring Data Reactive Repositories does not support MySQL, nor does it support MySQL transactions. So the original Spring transaction management with Reactivey doesn’t work. JDBC JPA transactions are based on the blocking IO model, and if Spring Data Reactive does not upgrade the IO model to support JDBC, production applications will have to use less dependent transactions. You can also use transparent transaction management, where a database connection is passed as a callback on each operation.
Spring Data Reactive Repositories currently supports Mongo, Cassandra, Redis, Couchbase.
Can you elaborate on the “old Spring transaction management with Reactivey”? In addition, the application has strong dependent transactions, is there a corresponding solution?
Let’s look at this picture first. Spring Web MVC -> Spring Data Spring WebFlux -> Spring Data Reactive
If you use Spring Data Reactive, the original Spring transaction management for Spring Data (JDBC, etc.) will not work. Because the original Spring Data JPA is based on ThreadLocal transaction delivery, its nature is based on the blocking IO model, not asynchronous. Reactive is asynchronous, so ThreadLocal can’t get a value from a different thread. Naturally, we need to think about how to do transactions in Reactive programming. One way to do this is to call back conn: newTransaction(conn ->{})
Because every operation to the database is asynchronous, connections cannot be passed by ThreadLocal in Reactive programming and must be passed by parameters. Although there will be some lines of code intrusion. Further, it is possible to kotlin coroutines for transparent transaction management by putting conn in local variables of the coroutine. Spring Data Reactive Repositories does not support MySQL transactions.
The answer is, this question is actually related to the first question. MySQL is not supported. JDBC is not supported. You can see that JDBC belongs to Spring Data. Wait for Spring Data Reactive Repositories to upgrade the IO model to support MySQL. You can also use Reactive programming to support transactions, as discussed above.
If your application can only use non-strongly dependent data transactions and still uses MySQL, you can use the following implementation:
@RestController
@RequestMapping(value = "/city")
public class CityRestController {
@Autowired
private CityService cityService;
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Mono<City> findOneCity(@PathVariable("id") Long id) {
return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.findCityById(id)));
}
@RequestMapping(method = RequestMethod.GET)
public Flux<City> findAllCity() {
return Flux.create(cityFluxSink -> {
cityService.findAllCity().forEach(city -> {
cityFluxSink.next(city);
});
cityFluxSink.complete();
});
}
@RequestMapping(method = RequestMethod.POST)
public Mono<Long> createCity(@RequestBody City city) {
return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.saveCity(city)));
}
@RequestMapping(method = RequestMethod.PUT)
public Mono<Long> modifyCity(@RequestBody City city) {
return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.updateCity(city)));
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public Mono<Long> modifyCity(@PathVariable("id") Long id) {
return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.deleteCity(id)));
}
}
Copy the code
In the findAllCity method, the flux.create method is used to create and encapsulate the response into Flux data. And using lambda to write the data stream’s handlers is very convenient. The Service layer is still the same logic, the business Service layer interface is as follows:
Public interface CityService {/** * return */ List<City> findAllCity(); ** @param ID * @return */ City findCityById(Long ID); ** @param city * @return */ Long saveCity(city city); /** * updateCity info ** @param city * @return */ Long updateCity(city city); ** @param ID * @return */ Long deleteCity(Long ID); }Copy the code
Specific case on my Github: github.com/JeffLi1993/…
Realize RESTful API based on Functional routing
Create a Route class to define RESTful HTTP routes
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class Routes {
private CityService cityService;
public Routes(CityService cityService) {
this.cityService = cityService;
}
@Bean
public RouterFunction<?> routerFunction() {
return route(
GET("/api/city").and(accept(MediaType.APPLICATION_JSON)), cityService:: findAllCity).and(route(
GET("/api/user/{id}").and(accept(MediaType.APPLICATION_JSON)), cityService:: findCityById)
);
}
}
Copy the code
RoouterFunction is similar to @RequestMapping in Spring Web MVC. It defines routing information. Each route is mapped to a processing method that is invoked when an HTTP request is received.
Create a Netty HttpServer:
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import reactor.ipc.netty.http.server.HttpServer;
@Configuration
public class HttpServerConfig {
@Autowired
private Environment environment;
@Bean
public HttpServer httpServer(RouterFunction<?> routerFunction) {
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create("localhost", Integer.valueOf(environment.getProperty("server.port")));
server.newHandler(adapter);
return server;
}
}
Copy the code
Netty is naturally recommended to run Reactive applications because Netty is asynchronous and event-driven.
The original:Talk about WebFlux for Spring Boot 2.0