WebFlux knowledge structure
Written in the beginning
Due to my busy curriculum, project development, postgraduate entrance examination and other matters, it is slow to update the article. If you think my article is right for you, please send me a message and I will update it in time.
Index
- In the meaning of @ “Validated”, data range and type verification shall be carried out when Bean equipment is carried out;
Specifically, some Bean definitions define not only the data type but also the data scope; To construct such a Bean, you need to evaluate the incoming data; And Spring integrates this judgment process, the @Validated annotation
Need basis
Spring Framework
JDK 8.0+ functional programming
Netty
Reactor responsive programming
Spring WebFlux
Reactive Core
HttpHandler
A slightly lower-level protocol for HTTP requests; Tomcat, Jetty, Netty, Undertow, Servlet3.1 and other servers are standardized. It aims to provide a more abstract and unified interface for different Web servers.
Netty
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
Copy the code
Undertow
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
Copy the code
Tomcat
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);
Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/"."main");
server.setHost(host);
server.setPort(port);
server.start();
Copy the code
Jetty
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);
Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();
ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
Copy the code
Servlet3.1
WebHandler API
Based on HttpHandler, and provide a slightly more advanced and more commonly used Web API to make code writing easier; And build a more concrete programming model. A “chain of responsibilities” process consisting of several parts (the same idea as Netty’s chain process).
Special Bean Types
1. WebExceptionHandler
Provides the handling of Filter exceptions and WebHandler exceptions in the Filter chain, the number of 0-n.
2. WebFilter
Provides “intercepting” logic processing, quantity 0-N. Intercepts before or after the rest of the Filter chain and WebHandler (for Filter).
3. WebHandler
The processor, which is also the main business processing part (the Controller in Spring MVC).
4. WebSessionManager
A manager for WebSession.
5. ServerCodecConfigurer
Used to access HttpMessageReader instances to parse form data and file data; The data is then passed through the ServerWebExchange method.
6. LocaleContextResolver
LocaleContext parser.
7. ForwardedHeaderTransformer
Processing HTTP headers, including extracting and removing some header key-value pairs, or removing only.
Form Data
ServerWebExchange provides the Mono<MultiValueMap<String, String>> getFormData() method; This method is used to access form data.
Multipart Data
ServerWebExchange provides the Mono<MultiValueMap<String, Part>> getMultipartData() method; This method is used to access Multipart data.
DefaultServerWebExchange parses the contents of “multiPart /form-data” into MultiValueMap using the configured HttpMessageReader<MultiValueMap<String, Part>>.
Currently, Synchronoss NIO Multipart is the only supported third-party library for non-blocking resolution of Multipart requests.
To parse multipart data into streams, Flux can be used as the return value of HttpMessageReader instead of HttpMessageReader<MultiValueMap<String, Part>>.
For example, in an annotation Controller, using @requestPart means that the entire Multipart data needs to be parsed for map-like access to each individual part by name.
In contrast, you can use @requestbody to parse the RequestBody into Flux without having to integrate it into MultiValueMap.
Forwarded Headers
If the request goes through a proxy server (such as Nginx), the host address, port, and request method may be changed. At this point, it can be a challenge for the client to correctly find information such as the host address.
ForwardedHeaderTransformer is a component class that provides based on the request to modify the host, port, and the method of function; These request headers are then removed, and the class can be declared in bean form.
Can be done by setting ForwardedHeaderTransformer removeOnly = true only remove unsafe head without access to some effect.
Filters
In the WebHandler API, you can use WebFilter to implement ‘intercepting’ logic; A WebFilter can intercept data flows into or from other Webfilters or WebHandlers.
When using WebFlux Config, you will find that registering a WebFilter is as easy as declaring it as a Spring Bean; Optionally, you can use the @Order annotation or implement the Ordered interface to sort webfilters.
CORS(Cross-domain Access)
Spring WebFlux provides fine-grained support for CORS through annotations of controller components. However, when you use Spring Security, we recommend (not me, Spring officials) using the built-in CorsFilter class, which must precede all Spring Security filters.
Exceptions (abnormal)
In the WebHandler API, you can use WebExceptionHandler to handle exceptions from WebFilter or WebHandler.
When using WebFlux Config, registering a WebExceptionHandler is as easy as declaring a Spring Bean.
The Order can also be set using @Order or implementing the Ordered interface.
There are two available WebExceptionHandler implementation, respectively: ResponseStatusExceptionHandler: By setting the HTTP response Code to handle ResponseStatusExceptionHandler type of exception. WebFluxResponseStatusExceptionHandler: ResponseStatusExceptionHandler derived classes, can be in any exception decided @ ResponseStatus annotation HTTP status code.
Codecs(Codecs)
The Spring-Web and Spring-Core modules provide support for deserialization from byte data to high-level objects and high-level object to byte serialization through non-blocking IO with back-pressure reactive flow.
The above support is described below:
Encoder and Decoder are low-level protocols used to encode/decode detached HTTP content.
HttpMessageReader and HttpMessageWriter are codecs used to encode/decode the content of HTTP messages.
An encoder can be encapsulated by EncoderHttpMessageWriter to make it suitable for Web applications; Similarly, decoders can be wrapped by DecoderHttpMessageReader.
DataBuffer abstracts and encapsulates byte buffers for different servers (such as Netty’s ByteBuf, java.nio.byteBuffer); This is also the data dependency (or data source) on which all codecs work.
The Spring-core module provides encoder and decoder implementations for Byte [], ByteBuffer, DataBuffer, Resource, and String.
For form data, binary/file content, events sent by the server, etc. The Spring-Web module provides codecs including Jackson JSON, Jackson Smile, JAXB2, Protocol Buffers and a Web-only HTTP message reader/writer implementation.
ClientCodecConfigurer and ServerCodecConfigurer are commonly used to configure and customize codecs within Web applications.
Jackson JSON(Jackson JSON)
Jackson2Decoder working principle:
- Jackson is an asynchronous, non-blocking parser that aggregates a stream of byte blocks into each block of TokenBuffer, where each block represents a JSON object
- Each TokenBuffer is passed to Jackson’s ObjectMapper to create a higher-level object
- When decoding a single-valued Publisher (such as Mono), a TokenBuffer is generated
- When decoding a multi-valued publisher(such as Flux), once a complete object receives enough bytes, each TokenBuffer is passed to ObjectMapper, and the input objects can be JSON arrays, If the content-type is “application/stream+ JSON “, it can also be a row split JSON
Jackson2Decoder Purpose: To decode byte streams to JSON and convert them to Object using Jackson 2.9.
Jackson2Encoder Working principle:
- For single-value Publisher, simply serialize through ObjectMapper
- For multi-value publishers decorated with “application/json”, the default is to use flux.collecttolist () to aggregate the values and serialize the collection
- For multi-value Publisher modified by streaming media types (e.g., Application /stream+ JSON, Application/Stream + X-jackson-smile), encode, write, and refresh each value using a JSON format based on line separators
- For SSE, Jackson2Encoder is called for each event, and the result of execution is actively flushed to ensure that there is no delay in transmission
Jackson2Encoder and Jackson2Decoder do not support String and String encoding. If you need to get JSON arrays from Flux, you should use flux.collecttolist () and encode Mono<List>
Form Data
FormHttpMessageReader and FormHttpMessageWriter support coding/decoding of “Application/X-www-form-urlencoded” content
On the server side, where you often need to access form data from multiple locations, ServerWebExchange provides a dedicated getFormData() method that parses the content through FormHttpMessageReader and then caches the results for multiple accesses
Once getFormData() is called, the native data in the request body cannot be accessed again. For this reason, the application should use ServerWebExchange to access cached data instead of the native data P.S. In fact, I think this design is probably due to Netty’s reference counting
Multipart(file data/binary data)
MultipartHttpMessageReader and MultipartHttpMessageWriter provides for “multipart/form – data” type of data encoding/decoding
In fact, another HttpMessageReader MultipartHttpMessageReader through agent to do the actual data parsing (parsed into the data Flux), then simply put the result set into a MultiValueMap
Currently, Synchronoss NIO Multipart is used to do the actual parsing
On a server that often needs to access multipart data from multiple locations, ServerWebExchange provides special getMultipartData () method by MultipartHttpMessageReader to parse the contents and results are cached for multiple access (similar to the form data)
Once getMultipartData() is called, the native data in the request body cannot be accessed again. Therefore, applications should consistently use getMultipartData() to perform multiple, map-based accesses to parts. Or use SynchronossPartHttpMessageReader for only once for Flux access data will be destroyed after p.. The reason for this design is the same as form data access
Limits (limit)
Decoder and HttpMessageReader implementations that aggregate some or all of the input stream data into buffers can set the maximum size of bytes a memory can contain to avoid memory bursting
Buffer processing occurs in some cases, such as aggregating incoming data into an object (HTTP requests are sent segment by segment, so aggregating); Buffering can also occur when an input stream is split
To set the maximum buffer size, you can check whether the provided Decoder or HttpMessageReader already has the maxInMemorySize attribute. In WebFlux, there is a separate place to set this property for all codecs; On the client side, you can set this property via webClient. Builder
For Multipart data, this restriction applies only to non-file parts; For the file part, it determines the threshold at which the file can be written to the disk; For portions of a file written to the hard disk, there is a maxDiskUsagePerPart property that limits the amount of hard disk space for each portion of the file
Also, there is a maxParts attribute that limits the total number of file parts in a Multipart request. To configure these three properties in WebFlux, Need to provide a pre-configured to ServerCodecConfigurer MultipartHttpMessageReader instance
Streaming (flow)
When streaming HTTP responses, heartbeat detection mechanisms should be added and the connection should be disconnected in a timely manner
DataBuffer(Data buffering component)
Byte buffers for various servers are encapsulated
For Netty reference counting, WebFlux generally does not care about this unless it directly consumes or produces data rather than relying on codecs to convert it to high-level objects. Or build your own codec (which can also cause memory leaks)
Logging (log)
DispatcherHandler
A central Handler, the DispatcherHandler, is responsible for forwarding requests. The actual request processing is done by configured proxy components. This mode of working is flexible and supports a wide variety of workflows
The DispatcherHandler automatically discovers the proxy component it needs through the Spring configuration. It is also designed as a Spring Bean and provides access to its context by implementing the ApplicationContextAware interface
A Spring configuration for WebFlux typically contains:
- DispatcherHandler with Bean name “webHandler”
- WebFilter and WebExceptionHandler Beans
- A special Bean for the DispatcherHandler
- other
The configuration is usually passed to WebHttpHandlerBuilder to build the processing chain
Such as:
ApplicationContext context = … HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
Special Bean Types
The DispatcherHandler delegates the request to a special Bean to complete the request processing and return the appropriate response.
HandlerMapping
To map a request to a Handler, the mapping is based on some specification, the details of which are determined by the implementation of HandlerMapping – annotation-based controller, simple URL schema mapping, and others
The main HandlerMapping implementation class is used to @ RequestMapping RequestMappingHandlerMapping annotation methods, for RouterFunctionMapping endpoint routing function, And explicit registration of URI path patterns and SimpleUrlHandlerMapping for WebHandler instances
HandlerAdapter
Help DispatcherHandler call a mapped Handler for a request without knowing how the Handler is actually called
For example, calling an annotated controller requires parsing the annotations first, which saves the DispatcherHandler from the tedious details
HandlerResultHandler
Process the result of the call from the processor and finally determine the reply response.
WebFlux Config(WebFlux configuration)
WebFlux Config is a good way to do this
Processing (Processing)
The DispatcherHandler process the request workflow:
- Each HandlerMapping is woken up to find a matching handler, and the first one that matches will be the handler for the request
- Once a handler is found, it is executed by an appropriate HandlerAdapter, which encapsulates the execution result (return value) as a HandlerResult
- The HandlerResult will be passed to an appropriate HandlerResultHandler to render the view or written directly to the Response to complete the execution
Result Handling
The result of the Handler call passes through the HandlerAdapter and is encapsulated into a HandlerResult with some text attached, which is then passed to the first HandlerResultHandler that can handle it for final processing
Here are some default implementations of HandlerResultHandler: ResponseEntityResultHandler ServerResponseResultHandler ResponseBodyResultHandler ViewResolutionResultHandler
ResponseEntityResultHandler
ResponseEntity, usually from the @Controller instance.
ServerResponseResultHandler
ServerResponse, usually from a functional endpoint
ResponseBodyResultHandler
Return value from the @responseBody method or the @RestController class
ViewResolutionResultHandler
CharSequence, View, Model, Map, Rendering, and any other Object are considered Model attributes
Exceptions (abnormal)
Specific error functions are triggered when a handler call fails or when a handler return value fails to be processed through the HandlerResultHandler
Whenever an error occurs, the error function can change the response state
You can use @ControllerAdvice to handle exceptions before selecting a handler to handle them
View Resolution
Annotated Controllers
@Controller
Either a Controller Bean can be defined using the standard SpringBean method or it can be built using the @controller annotation
The @Controller annotation provides automatic inference and path scanning methods for assembling Controller components, allowing Controller classes to be recognized as Web components
@restController is a combined annotation of @Controller and @responseBody, meaning that the return value is written directly to the Response body rather than being rendered to the view or written to the HTML template
Request Mapping
The @requestMapping annotation is often used to map controller methods; It has a variety of attributes for matching, such as by URL, by HTTP request method, request parameters, headers, or media types
This annotation can be used either at the class level to indicate that all methods of the class include the path map, or at the method level for specific request processing
@requestMapping has some sub-annotations to qualify the request: @getMapping @postMapping @putMapping @deletemapping @PatchMapping
URI Patterns(URI Matching)
The use of wildcards for URI maps? : Matches one character *: matches 0 or more characters in a path fragment **: matches 0 or more path fragments
Also, you can get the value of the URI by setting a variable in the path. For example: @GetMapping(“/owners/{ownerId}/pets/{petId}”) public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { // … } Path variable parameters can also be used for class-level mapping
URI variables can be automatically converted to the appropriate type, otherwise a TypeMismatchException will be thrown, and simple basic types (such as int, double, Long, String, Date, etc.) can be retrieved directly
URI variables can also be explicitly named (e.g. @pathVariable (“customId”))
The syntax {*varName} matches 0 or more remaining path fragments, such as (“/resources/{*path}”)
The syntax {varName: regular expression} can be used to match regular expressions
Uris can also use the embedded placeholder ${… }, to be injected as a property at system startup
WebFlux urIs differ from SpringMVC in that they cannot use postfix expressions
Pattern Comparison
When multiple maps match a URL, they must be compared to find the best match; This is done by pathPattern.specificity_Comparator, which is responsible for finding the best match
For each matchable item, a value is computed based on the number of URI variables and placeholders. A URI variable with a value smaller than the placeholders matches with a fraction smaller, and if the fraction is the same, a long match
Consumable Media Types
Request matching can be fine-grained with the content-Type attribute of the request, for example: @PostMapping(path = “/pets”, consumes = “application/json”) public void addPet(@RequestBody Pet pet) { // … }
The Consumes attribute also supports the negative form, for example:! Text /plain means to match all non-text /plain requests
As Consumes at the class level, when the method uses Consumes, it means that it overwrites the Consumes attribute for the class
MediaType encapsulates several types
Producible Media Types
The request mapping can be fine-grained based on the Accept attribute of the request, for example: @GetMapping(path = “/pets/{petId}”, produces = “application/json”) @ResponseBody public Pet getPet(@PathVariable String petId) { // … } indicates that in addition to matching the URI, the request that accepts the return type of “application/json” should be matched
The media type can also specify a character set, and also supports reverse operations (matching only accepts requests other than this type).
Method-level Settings can also override class-level Settings
Parameters and Headers
The matching can be fine-grained according to the request parameters: 1. Match according to the existence of a parameter 2. Matches a parameter based on whether it does not exist. 3. Matches a parameter based on whether it is equal to a certain value. @GetMapping(path = “/pets/{petId}”, params = “myParam=myValue”) public void findPet(@PathVariable String petId) { // … } check if myParam is equal to myValue
The same method can be used to check if the request header matches. For example: @GetMapping(path = “/pets”, headers = “myHeader=myValue”) public void findPet(@PathVariable String petId) { // … }
HTTP HEAD, OPTIONS
Annotations Custom Annotations
Spring allows you to combine annotations to implement request mapping. The goal is to get a more granular child annotation of @requestMapping.
If you want to get a more granular logical match. Inherit the RequestMappingHandlerMapping and overwrite getCustomMethodCondition () method. This allows you to check custom attributes and return your own RequestCondition
Explicit Registrations
You can programmatically register handler methods, which can be used for dynamic registration or advanced cases. Like different instances of the same processor at different urls.
For example:
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler)
throws NoSuchMethodException { // Inject the target processor and processor mapping into the controller
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build(); // Prepare request mapping metadata
Method method = UserHandler.class.getMethod("getUser", Long.class); // Get the handler method
mapping.registerMapping(info, handler, method); // Add registration}}Copy the code
Handler Methods
RequestMapping has flexible processor method signatures; You can choose from a list of supported method parameters and return values.
Method Arguments
Reactive types can be supported for parameters that need to handle blocking IO (e.g., Reactor, RxJava, or others). Reactive types are not recommended for parameters that do not require blocking
JDK8’s Optional annotation is used with an annotation with the required attribute, which is equivalent to adding required=false to the annotation
1. ServerWebExchange
Full access to ServerWebExchange. ServerWebExchange is a container containing: HTTP Request and HTTP Response; Request and session properties; CheckNotModified method and others
2. ServerHttpRequest, ServerHttpResponse
Add HTTP Request and Response
3. WebSession
To access the Session; No new session is forced to start unless attributes are added; Reactive types are supported.
4. java.security.Principal
The currently authenticated user – may be the specific Principal implementation class (if known). Reactive types are supported.
5. org.springframework.http.HttpMethod
Requested method
6. java.util.Locale
The current request locale is determined by the most specific available LocaleResolver; In fact is to configure the LocaleResolver/LocaleContextResolver.
7. java.util.TimeZone + java.time.ZoneId
The time zone associated with the current request is determined by LocaleContextResolver.
8. @PathVariable
Used to access URI template variables
9. @MatrixVariable
Used to access key/value pairs in URI path fragments
10. @RequestParam
Used to access Servlet request parameters; The value of the parameter has been converted to the parameter type of the declared method.
Note: use of @requestParam is optional
11. @RequestHeader
Used to access the request header; The value of the request header has been converted to the parameter type of the declared method.
12. @CookieValue
Used to access cookies; The value of the Cookie has been converted to the parameter type of the declared method.
13. @RequestBody
Used to access the HTTP request body; With the HttpMessageReader instance, the request body content has been converted to the parameter type of the declared method. Reactive types are supported.
14. HttpEntity<B>
Access the header and body of the request; The request body is converted to the parameter type of the declared method by the HttpMessageReader instance. Reactive types are supported.
15. @RequestPart
A part used to access a request of type “multipart/form-data”. Reactive types are supported.
16. java.util.Map, org.springframework.ui.Model, and org.springframework.ui.ModelMap.
Use to access the model used in the HTML controller and present it to the template as part of the view rendering.
17. @ModelAttribute
Used to access existing properties (instantiated if none exist) in the model to which data binding and validation are applied.
The use of @modelAttribute is optional.
18. Errors, BindingResult
Errors in validation and data binding used to access command objects.
The Errors or BindingResult arguments must be declared immediately after the validated method arguments.
19. SessionStatus + class-level @SessionAttributes
Used to mark completion of form processing; Clears all Session attributes declared through the class-level @sessionAttributes annotation.
20. UriComponentsBuilder
Used to prepare the URL of Host, Port, Scheme, and Path relative to the current request.
21. @SessionAttribute
Use to access any Session property
22. @RequestAttribute
Used to access request properties.
23. Any other argument
By default, if it is a simple type, it resolves to @requestParam.
Return Values
All return values support reactive types.
1. @ResponseBody
The return value is encoded by HttpMessageWriter and written to the response.
2. HttpEntity<B>, ResponseEntity<B>
The return value specifies the complete response, including the HTTP header and body; And then write to the HTTP Response via HttpMessageWriter.
3. HttpHeaders
Return a response with an HTTP header but no body.
4. String
Returns the template name, which is parsed by the view parser to get the template name.
5. View
Returns a view.
6. java.util.Map, org.springframework.ui.Model
Implicitly determine the view name based on the request path; Attributes are implicitly added to model data.
7. @ModelAttribute
Ready to add to model data; The view name is implicitly inferred from the request path.
8. Rendering
Apis for model and view rendering schemes.
9. void
A method that returns a null value, possibly asynchronous; If the parameter has a ServerHttpResponse, ServerWebExchange, or @responseStatus annotation; That means the method handles the response completely, without returning the value and letting Spring write it out.
Another possibility is, instead of having a ResponseBody, we have a ResponseHeader.
10. Flux<ServerSentEvent>, Observable<ServerSentEvent>, or other reactive type
Send server events; You can omit the ServerSentEvent wrapper when you only need to write data. If you want to use “text/event-stream”; “Text /event-stream” must be requested or declared in the map via the Produces attribute.
11. Any other return value
Type Conversion
For String input requests, if the declared type is not String; Then a cast will occur.
In addition to casting primitives, you can cast them via WebDataBinder; Or registration by FormattingConversionService Formatters to implement custom transformation.
Matrix Variables
Matrix variables, which appear in urIs, with ‘between each variable; ‘space; Each value is separated by a ‘,’; For example: “/ cars; color=red,green; year=2012”; There are three variables: [cars], [color], [year]; [color] corresponds to two values: red and green. Multiple values can be separated to correspond to a variable; For example: “color = red; color=green; Color =blue” means color has three values.
Unlike Spring MVC, the default of matrix variables does not affect the request mapping; If you want to access matrix variables, you can add variables to the URI request path to receive matrix variables, such as:
// GET /pets/42; q=11; r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
Copy the code
For possible ambiguity, the only variable can be indicated:
// GET /owners/42; q=11/pets/21; q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
Copy the code
Optional parameters can also be set to default values:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
Copy the code
You can also use MultiValueMap to get all the values of a matrix variable:
// GET /owners/42; q=11; r=12/pets/21; q=22; s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
/ / matrixVars: [" q ": [11, 22]," r ": 12," s ": 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}
Copy the code
@RequestParam
Unlike Spring MVC, WebFlux’s @RequestParam annotation does not bind query parameters, form data, and file/binary data together; By default, WebFlux’s @RequestParam only has query parameters, so if you want to implement binding, you can use data binding to convert query parameters, form data, and binary/file data into command objects.
Method arguments using @RequestParam annotations are mandatory by default, but you can specify method arguments optionally by setting @RequestParam’s required flag to false or by declaring arguments using the java.util.Optional wrapper.
When the @RequestParam annotation is declared on a Map <String, String> or MultiValueMap <String, String> parameter, the Map is populated with all query parameters.
This annotation is optional, meaning that any primitive type that is not parsed by other parameters is treated as decorated with the @requestParam annotation.
@RequestHeader
You can use this annotation to bind the request header to a method parameter, for example:
Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml; Q = 0.9 Accept - Language fr, en - gb; Q = 0.7, en. Q =0.3 Accept-encoding gzip, Deflate Accept-Charset ISO-8859-1, UTF-8; Q = 0.7 *; Q = 0.7 Keep Alive - 300Copy the code
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding")String encoding, // Gets the accepted encoding type@RequestHeader("Keep-Alive") long keepAlive) { // Gets whether to keep the connection
/ /...
}
Copy the code
Similarly, if a Map or MultiValueMap is used as an argument to the annotation, the Map is automatically populated.
@CookieValue
You can use the @Cookievalue annotation to bind the value of the HTTP cookie to the method parameters in the controller.
@ModelAttribute
Access to model data can be achieved by adding @ModelAttribute annotations in front of method parameters; If this data does not exist, a new instance is created. The values of the model data will override variables of the same name in the query parameters and form data. This is because of data binding, which saves you the hassle of manually parsing individual data.
Adding BindingResult to @ModelAttribute handles data binding exceptions.
Automatic validation can be implemented by adding an @valid annotation in front of @modelAttribute.
@modelAttribute is optional.
@SessionAttributes
This annotation is used to store webSessions in the Model between requests.
This is a type-level annotation that declares the Session property used by a particular controller.
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...} The above code stores pet in the Session. Until another controller uses the SessionStatus method to clear the Session. The following method clears the Session@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors()) {
// ...
}
status.setComplete();
// ...}}}Copy the code
@SessionAttribute
If you need access to preexisting session attributes that are managed globally (for example, outside the controller (for example, through filters) and may or may not exist, you can use the @sessionAttribute annotation on method parameters, as shown in the following example:
@GetMapping("/")
public String handle(@SessionAttribute User user) { // Use Session data named user
// ...
}
Copy the code
Consider injecting webSessions into controller methods to add or remove sessions.
@RequestAttribute
Easier access to previous request attributes using @sessionAttribute; Consider using @requestAttribute, as follows:
@GetMapping("/")
public String handle(@RequestAttribute Client client) { // Access the previous attributes
// ...
}
Copy the code
Multipart Content
The best way to handle file upload forms is to bind them to command objects via data binding. Such as:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...}}Copy the code
For a commit:
POST /someUrl
Content-Type: multipart/mixed
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...
Copy the code
Separate parts can be accessed via @requestPart:
@PostMapping("/")
public String handle(@RequestPart("meta-data")Part metadata, // Access metadata@RequestPart("file-data") FilePart file) { // Access file data
// ...
}
Copy the code
To deserialize native data, receive data with concrete objects instead of parts:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { // Deserialize to MetaData
// ...
}
Copy the code
You can also combine @requestPart and @validated to implement verification; Then use Mono to prepare to handle the exception
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// use one of the onError* operators...
}
Copy the code
To wrap all the data in MultiValueMap form, use the @RequestBody annotation
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) {
// ...
}
Copy the code
In streaming data, for continuous access to the data, Flux can be used for processing
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {
// ...
}
Copy the code
@RequestBody
You can use this annotation to read the HTTP request body or deserialize it into a class; Such as:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
Copy the code
Unlike Spring MVC, in Web Flux, @RequestBody is a responsive and completely non-blocking design; It can also be read using streams, such as:
@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
// ...
}
Copy the code
Standard Spring Bean validation exceptions contain BindingResult with error details, using @Validated; You can declare parameters in controller methods by using asynchronous wrappers; The exception is then handled using the error-associated operator.
HttpEntity
More or less like @requestbody, but it has both a request header and a RequestBody. Such as:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
Copy the code
@ResponseBody
This annotation serializes the return value into the Response Body via HttpMessageWriter. Such as:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle(a) {
// ...
}
Copy the code
The @responseBody at the class level is inherited by the method.
@responseBody supports asynchronous, responsive operations; This means that the result can be written to the Response Body using a response type.
ResponseEntity
ResponseEntity is like @responseBody but with a status code and a return header; Such as:
@GetMapping("/something")
public ResponseEntity<String> handle(a) {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}
Copy the code
WebFlux supports asynchronous generation of ResponseEntity or ResponseEntity using a single-valued response type; Single and multi-value response types to generate the body section.
Jackson JSON
1. JSON Views
Model(Model data)
The front and rear ends are separated, temporarily omitted. Basically the same as Spring MVC.
DataBinder(DataBinder)
This applies when Spring cannot automatically transform data.
Managing Exceptions
REST API exceptions
Controller Advice
Functional Points(Functional endpoints)
In addition to annotation methods, functional declarations are also used for business processing.
HTTP requests are processed by handler. A ServerRequest is passed in, which the handler accepts and returns a Mono.
The handler is similar to the @requestMapping method body.
Note that the received and returned data types are fixed, so do not change them arbitrarily.
Requests are routed by the routing function to the corresponding handler method and back to a Mono, so obviously the routing function is like @RequestMapping, but with data and (forward) behavior.
Routerfunctions.route () provides a series of utility methods to build routing functions, Then using RouterFunctions. ToHttpHandler (RouterFunction) transform routing functions into an HttpHandler then install into the built-in ServerAdapter.
HandlerFunction(processor function)
Request to include elements in the form of Flux or Mono; Responses are presented as reactive Publisher, including Flux and Mono.
ServerRequest
ServerRequest provides access to HTTP methods, URIs, headers, and query parameters, as well as the body() method for direct access to the request body.
Extract the request body to Mono:
Mono<String> string = request.bodyToMono(String.class);
Copy the code
Extract the request body to the POJO:
Flux<Person> people = request.bodyToFlux(Person.class);
Copy the code
Use the more generic ServerRequest.body(BodyExtractor) to extract data:
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
Copy the code
Accessing form data:
Mono<MultiValueMap<String, String> map = request.formData();
Copy the code
Accessing multipart data:
Mono<MultiValueMap<String, Part> map = request.multipartData();
Copy the code
Accessing multiple parts of multipart data at once:
Flux<Part> parts = request.body(BodyExtractors.toParts());
Copy the code
ServerResponse(ServerResponse)
ServerResponse provides access to the response body, which can be created using the build() method. You can use the builder to set the response status code, add response headers, provide response bodies, and so on.
An example using a 200 status code:
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
Copy the code
Depending on codec support, you can provide implicit arguments to indicate how the response body should be serialized and deserialized. Such as:
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...) ;Copy the code
Handler Classes
Request processing can be implemented by writing handlers, such as:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
Copy the code
If you need to write more than one handler, consider organizing them into a handler class, like the @Controller class. Such as:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
returnrepository.getPerson(personId) .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person)) .switchIfEmpty(ServerResponse.notFound().build()); }}Copy the code
The Validation (verification)
You can use the Spring Validation tool to validate the request body.
HandlerFunction(router)
Routing functions are used to route requests to the corresponding handler; It is usually not necessary to write your own routing functions; the RouterFunctions utility class provides a convenient method. Routerfunctions.route () allows you to quickly build a routing function, and RouterFunctions.Route (RequestPredicate, HandlerFunction) provides a more straightforward way to do this.
The route() method is recommended because it provides a more ‘shortcut’ approach. In addition to routing based on HTTP methods, additional assertions are provided to refine the mapping. For each HTTP method, there is an overloaded variant that provides a RequestPredicate argument for additional constraints to make the mapping match more accurately.
Predicates (claim)
The RequestPredicates utility class provides common implementations based on request paths, HTTP methods, content-type, and so on. For example, the following partition is based on Accept:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
Copy the code
Also combine your own assertions:
RequestPredicate.and(RequestPredicate) // Simultaneously match.
RequestPredicate.or(RequestPredicate) // As long as there is a match.
Copy the code
Routes (routing)
Router routing rules: Match in order, so you should write the more specific ones first and the more general ones later. Note: This is different from the annotation method, where the annotation method matching principle is best match, so pay attention to the order.
In general, defined routes should be grouped together using RouterFunction. There are several ways to group routes together:
A) Add (RouterFunction) on routerfunctions.route ()
B) Use RouterFunction. And (RouterFunction)
C) RouterFunction.and() and RouterFunctions.route() -RouterFunction.andRoute(RequestPredicate, HandlerFunction)
For example:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.add(otherRoute)
.build();
Copy the code
1. The Nested Routes were led by…
It is common for multiple routing functions to share the same assertion, for example, the request path. Take the example of a shared path:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
Copy the code
Note that PATH is a consumer consuming the Route Builder. The following example shows nesting if the same Accept attribute is still shared:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.build();
Copy the code
Running a Server
One way to run a routing function is to convert the routing function to an HttpHandler and then use a Server Adapter for adaptation.
A more typical approach, and one used by Spring Boot, is to use WebFlux Config to set up a DispatcherHandler-based configuration class. It uses Spring configuration to declare the components needed to process the request. Java Configuration for WebFlux declares the following basic components to support functional endpoints.
RouterFunctionMapping: Infer multiple RouterFunctions <? > bean and group them together via RouterFunction.andOther, and route requests to the combined RouterFunction.
HandlerFunctionAdapter: A simple adapter that lets the DispatcherHandler call a HandlerFunction that matches the request.
ServerResponseResultHandler: call ServerResponse writeTo method to treat calls HandlerFunction results in writing to the response.
The processing component makes functional endpoints more life-cycle friendly for handling DispatcherHandler requests and can also run in parallel with annotation declared controller classes. This is also how the Spring WebFlux initiator enables functional endpoints.
The following example shows how to use the WebFlux Java configuration:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
publicRouterFunction<? > routerFunctionA() {// ...
}
@Bean
publicRouterFunction<? > routerFunctionB() {// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...}}Copy the code
Filtering Handler Functions
Before applies to all routers; After applies to all routers; If there is nesting, it only works on the body of the nesting.