From 9d5a25e7378f5cb2d20eedbf3a6de1dcca3d1bfb Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 13 Sep 2017 21:02:28 -0400 Subject: [PATCH] Proper WebFlux reference and MVC reference updates Pending -- WebSocket, WebTestClient, more details around annotation processing, exception handling, and view resolution. Issue: SPR-15149, SPR-16009 --- src/docs/asciidoc/images/webflux-overview.png | Bin 71929 -> 0 bytes src/docs/asciidoc/index.adoc | 6 +- src/docs/asciidoc/integration.adoc | 64 +- src/docs/asciidoc/kotlin.adoc | 4 +- src/docs/asciidoc/reactive-web.adoc | 62 - src/docs/asciidoc/web-reactive.adoc | 15 + src/docs/asciidoc/web.adoc | 2 +- src/docs/asciidoc/web/webflux-functional.adoc | 71 +- src/docs/asciidoc/web/webflux.adoc | 1962 ++++++++++++++--- src/docs/asciidoc/web/webmvc.adoc | 1621 ++++++-------- 10 files changed, 2341 insertions(+), 1466 deletions(-) delete mode 100644 src/docs/asciidoc/images/webflux-overview.png delete mode 100644 src/docs/asciidoc/reactive-web.adoc create mode 100644 src/docs/asciidoc/web-reactive.adoc diff --git a/src/docs/asciidoc/images/webflux-overview.png b/src/docs/asciidoc/images/webflux-overview.png deleted file mode 100644 index 62774d0c64ed177e79b9456a9d5a28c42f167685..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71929 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. The Spring Framework supports declarative transaction management, remote access to your logic through RMI or web services, and various options for persisting your data. -It offers full-featured web frameworks such as <> -and <>; and it enables you to +It offers full-featured web frameworks such as <> +and <>; and it enables you to integrate <> transparently into your software. This document is a reference guide to Spring Framework features. Questions on the @@ -29,7 +29,7 @@ This reference document provides the following sections: * <> -* The Web on <> or <> stacks +* The Web on <> or <> stacks * <> diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index 87d12e10da..0c430af97c 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -1369,68 +1369,8 @@ and writes the media type supported by the Java I/O API. [[rest-async-resttemplate]] ==== Async RestTemplate -Web applications often need to query external REST services those days. The very nature of -HTTP and synchronous calls can lead up to challenges when scaling applications for those -needs: multiple threads may be blocked, waiting for remote HTTP responses. - -`AsyncRestTemplate` and <>'s APIs are very similar; see -<>. The main difference between those APIs is -that `AsyncRestTemplate` returns -{api-spring-framework}/util/concurrent/ListenableFuture.html[`ListenableFuture`] -wrappers as opposed to concrete results. - -The previous `RestTemplate` example translates to: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - // async call - Future> futureEntity = template.getForEntity( - "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21"); - - // get the concrete result - synchronous call - ResponseEntity entity = futureEntity.get(); ----- - -{api-spring-framework}/util/concurrent/ListenableFuture.html[`ListenableFuture`] -accepts completion callbacks: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - ListenableFuture> futureEntity = template.getForEntity( - "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21"); - - // register a callback - futureEntity.addCallback(new ListenableFutureCallback>() { - @Override - public void onSuccess(ResponseEntity entity) { - //... - } - - @Override - public void onFailure(Throwable t) { - //... - } - }); ----- - -[NOTE] -==== -The default `AsyncRestTemplate` constructor registers a -{api-spring-framework}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor` -] for executing HTTP requests. -When dealing with a large number of short-lived requests, a thread-pooling TaskExecutor -implementation like -{api-spring-framework}/scheduling/concurrent/ThreadPoolTaskExecutor.html[`ThreadPoolTaskExecutor`] -may be a good choice. -==== - -See the -{api-spring-framework}/util/concurrent/ListenableFuture.html[`ListenableFuture` javadocs] -and -{api-spring-framework}/web/client/AsyncRestTemplate.html[`AsyncRestTemplate` javadocs] -for more details. +The `AsyncRestTemplate` is deprecated. +Please use the <> instead. diff --git a/src/docs/asciidoc/kotlin.adoc b/src/docs/asciidoc/kotlin.adoc index fb771ddd48..b8472d168e 100644 --- a/src/docs/asciidoc/kotlin.adoc +++ b/src/docs/asciidoc/kotlin.adoc @@ -261,7 +261,7 @@ for more details and up-to-date information. Spring Framework now comes with a {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/-router-function-dsl/[Kotlin routing DSL] -that allows one to leverage the <> for writing clean and idiomatic Kotlin code: [source,kotlin] @@ -583,7 +583,7 @@ https://spring.io/blog/2017/08/01/spring-framework-5-kotlin-apis-the-functional- === Choosing the web flavor Spring Framework now comes with 2 different web stacks: <> and -<>. +<>. Spring WebFlux is recommended if one wants to create applications that will deal with latency, long-lived connections, streaming scenarios or simply if one wants to use the web functional diff --git a/src/docs/asciidoc/reactive-web.adoc b/src/docs/asciidoc/reactive-web.adoc deleted file mode 100644 index 59f078e9ea..0000000000 --- a/src/docs/asciidoc/reactive-web.adoc +++ /dev/null @@ -1,62 +0,0 @@ -[[spring-reactive-web]] -= Web on Reactive Stack -:doc-root: https://docs.spring.io -:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework -:toc: left -:toclevels: 3 -:docinfo1: - -This part of the documentation covers support for reactive stack, web applications built on -http://www.reactive-streams.org/[Reactive Streams] and adapted to non-blocking runtimes -such as Netty, Undertow, and Servlet containers via Servlet 3.1 non-blocking I/O. -Individual chapters cover <> and its -<>. The previous section covers support for -<> applications. - -[[spring-reactive-web-intro]] -== Introduction - - -[[spring-reactive-web-intro-reactive-programming]] -=== What is Reactive Programming? - -In plain terms reactive programming is about non-blocking applications that are asynchronous -and event-driven and require a small number of threads to scale vertically (i.e. within the -JVM) rather than horizontally (i.e. through clustering). - -A key aspect of reactive applications is the concept of backpressure which is -a mechanism to ensure producers don't overwhelm consumers. For example in a pipeline -of reactive components extending from the database to the HTTP response when the -HTTP connection is too slow the data repository can also slow down or stop completely -until network capacity frees up. - -Reactive programming also leads to a major shift from imperative to declarative async -composition of logic. It is comparable to writing blocking code vs using the -`CompletableFuture` from Java 8 to compose follow-up actions via lambda expressions. - -For a longer introduction check the blog series -https://spring.io/blog/2016/06/07/notes-on-reactive-programming-part-i-the-reactive-landscape["Notes on Reactive Programming"] -by Dave Syer. - - -[[spring-reactive-web-intro-reactive-api]] -=== Reactive API and Building Blocks - -Spring Framework 5 embraces -https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams[Reactive Streams] -as the contract for communicating backpressure across async components and -libraries. Reactive Streams is a specification created through industry collaboration that -has also been adopted in Java 9 as `java.util.concurrent.Flow`. - -The Spring Framework uses https://projectreactor.io/[Reactor] internally for its own -reactive support. Reactor is a Reactive Streams implementation that further extends the -basic Reactive Streams `Publisher` contract with the `Flux` and `Mono` composable API -types to provide declarative operations on data sequences of `0..N` and `0..1`. - -The Spring Framework exposes `Flux` and `Mono` in many of its own reactive APIs. -At the application level however, as always, Spring provides choice and fully supports -the use of RxJava. For more on reactive types check the post -https://spring.io/blog/2016/04/19/understanding-reactive-types["Understanding Reactive Types"] -by Sebastien Deleuze. - -include::web/webflux.adoc[leveloffset=+1] \ No newline at end of file diff --git a/src/docs/asciidoc/web-reactive.adoc b/src/docs/asciidoc/web-reactive.adoc new file mode 100644 index 0000000000..e32c78ee45 --- /dev/null +++ b/src/docs/asciidoc/web-reactive.adoc @@ -0,0 +1,15 @@ +[[spring-web-reactive]] += Web on Reactive Stack +:doc-root: https://docs.spring.io +:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework +:toc: left +:toclevels: 4 +:docinfo1: + +This part of the documentation covers support for reactive stack, web applications built on a +http://www.reactive-streams.org/[Reactive Streams] API to run on top of non-blocking +servers such as Netty, Undertow, and Servlet 3.1+ containers. Individual chapters cover +<> and its <>. +For Servlet stack, web applications, to go <>. + +include::web/webflux.adoc[leveloffset=+1] \ No newline at end of file diff --git a/src/docs/asciidoc/web.adoc b/src/docs/asciidoc/web.adoc index 3baf3a6834..6ca3de62bb 100644 --- a/src/docs/asciidoc/web.adoc +++ b/src/docs/asciidoc/web.adoc @@ -9,7 +9,7 @@ This part of the documentation covers support for Servlet stack, web applications built on the Servlet API and deployed to Servlet containers. Individual chapters include <>, <>, <>, and <>. -The next section covers support for <> applications. +For reactive stack, web applications, go to <>. include::web/webmvc.adoc[leveloffset=+1] diff --git a/src/docs/asciidoc/web/webflux-functional.adoc b/src/docs/asciidoc/web/webflux-functional.adoc index 73159587aa..cfe80c6cc9 100644 --- a/src/docs/asciidoc/web/webflux-functional.adoc +++ b/src/docs/asciidoc/web/webflux-functional.adoc @@ -1,17 +1,25 @@ [[webflux-fn]] -= Functional Programming Model += Functional Endpoints + +Spring WebFlux provides a lightweight, functional programming model where functions +are used to route and handle requests and where contracts are designed for immutability. +It is an alternative to the annotated-based programming model but runs on the same +<> foundation + [[webflux-fn-handler-functions]] -== HandlerFunctions +== HandlerFunction Incoming HTTP requests are handled by a **`HandlerFunction`**, which is essentially a function that takes a `ServerRequest` and returns a `Mono`. The annotation counterpart to a -handler function would be a method with `@RequestMapping`. +handler function is an `@RequestMapping` method. `ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK-8 friendly access -to the underlying HTTP messages. Both are fully reactive by -building on top of Reactor: the request expose the body as `Flux` or `Mono`; the response accepts -any http://www.reactive-streams.org[Reactive Streams] `Publisher` as body. +to the underlying HTTP messages with http://www.reactive-streams.org[Reactive Streams] +non-blocking back pressure. The request exposes the body as Reactor `Flux` or `Mono` +types; the response accepts any Reactive Streams `Publisher` as body (see +<>). + `ServerRequest` gives access to various HTTP request elements: the method, URI, query parameters, and -- through the separate `ServerRequest.Headers` interface @@ -26,7 +34,7 @@ contains JSON, or JAXB if XML). Flux people = request.bodyToFlux(Person.class); -The two methods above (`bodyToMono` and `bodyToFlux`) are, in fact, convenience methods that use the +The above -- `bodyToMono` and `bodyToFlux`, are, in fact, convenience methods that use the generic `ServerRequest.body(BodyExtractor)` method. `BodyExtractor` is a functional strategy interface that allows you to write your own extraction logic, but common `BodyExtractor` instances can be found in the `BodyExtractors` utility class. So, the above @@ -43,7 +51,8 @@ a JSON content-type, and a body: Mono person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person); -And here is how to build a response with a 201 Created status, Location header, and empty body: +And here is how to build a response with a 201 CREATED status, a `"Location"` header, and +empty body: URI location = ... ServerResponse.created(location).build(); @@ -111,7 +120,7 @@ variable `id`. We retrieve that `Person` via the repository, and create a JSON r found. If it is not found, we use `switchIfEmpty(Mono)` to return a 404 Not Found response. [[webflux-fn-router-functions]] -== RouterFunctions +== RouterFunction Incoming requests are routed to handler functions with a **`RouterFunction`**, which is a function that takes a `ServerRequest`, and returns a `Mono`. If a request matches a @@ -172,43 +181,23 @@ Most of the predicates found in `RequestPredicates` are compositions. For instance, `RequestPredicates.GET(String)` is a composition of `RequestPredicates.method(HttpMethod)` and `RequestPredicates.path(String)`. + [[webflux-fn-running]] -=== Running a Server +== Running a server -Now there is just one piece of the puzzle missing: running a router function in an HTTP server. -You can convert a router function into a `HttpHandler` by using -`RouterFunctions.toHttpHandler(RouterFunction)`. -The `HttpHandler` allows you to run on a wide variety of reactive runtimes: Reactor Netty, -Servlet 3.1+, and Undertow. -Here is how we run a router function in Reactor Netty, for instance: +How do you run a router function in an HTTP server? A simple option is to convert a +router function to an `HttpHandler` via `RouterFunctions.toHttpHandler(RouterFunction)`. +The `HttpHandler` can then be used with a number of servers adapters. +See <> for server-specific +instructions. -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -RouterFunction route = ... -HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); -ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); -HttpServer server = HttpServer.create(HOST, PORT); -server.newHandler(adapter).block(); ----- - -For Tomcat it looks like this: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -RouterFunction route = ... -HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); -HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler); -Tomcat server = new Tomcat(); -Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir")); -Tomcat.addServlet(rootContext, "servlet", servlet); -rootContext.addServletMapping("/", "servlet"); -tomcatServer.start(); ----- +it is also possible to run with a +<> setup -- side by side +with annotated controllers. The easiest way to do that is through the +<> which creates the necessary configuration to +handle requests with router and handler functions. -// TODO: DispatcherHandler [[webflux-fn-handler-filter-function]] == HandlerFilterFunction diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index 15b4b410e6..152039c96d 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -1,332 +1,1688 @@ [[webflux]] = Spring WebFlux -Spring Framework 5 includes a new `spring-webflux` module. The module contains support -for reactive HTTP and WebSocket clients as well as for reactive server web applications -including REST, HTML browser, and WebSocket style interactions. +[[webflux-introduction]] +== Introduction +The original web framework included in the Spring Framework, Spring Web MVC, was purpose +built for the Servlet API and Servlet containers. The reactive stack, web framework, +Spring WebFlux, was added later in version 5.0. It is built on a +http://www.reactive-streams.org/[Reactive Streams] API and runs on non-blocking +servers such as Netty, Undertow, and Servlet 3.1+ containers. -[[webflux-server]] -== Server Side +Both web frameworks mirror the names of their source modules +https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc[spring-webmvc] and +https://github.com/spring-projects/spring-framework/tree/master/spring-webflux[spring-webflux] +and co-exist side by side in the Spring Framework. Each module is optional. +Applications may use one or the other module, or in some cases both -- +e.g. Spring MVC controllers with the reactive `WebClient`. -On the server-side WebFlux supports 2 distinct programming models: +In addition to the WebFlux web framework, the `spring-webflux` module includes several +other reactive components such as a <> for performing HTTP requests, +a `WebTestClient` for testing web endpoints, and WebSocket support. -* Annotation-based with `@Controller` and the other annotations supported also with Spring MVC -* Functional, Java 8 lambda style routing and handling -Both programming models are executed on the same reactive foundation that adapts -non-blocking HTTP runtimes to the Reactive Streams API. The diagram -below shows the server-side stack including traditional, Servlet-based -Spring MVC on the left from the `spring-webmvc` module and also the -reactive stack on the right from the `spring-webflux` module. +[[webflux-new-framework]] +=== Why a new web framework? -image::images/webflux-overview.png[width=720] +Part of the answer is the need for a non-blocking web stack to handle concurrency with a +small number of threads and scale with less hardware resources. Servlet 3.1 did provide +an API for non-blocking I/O. However the use of that leads away from using the rest of the +Servlet API which remains synchronous -- `Filter`, `Servlet`, and blocking -- `getParameter`, +`getPart`. On the positive side a new common API foundation makes it possible to support any +server and that is important because of runtimes such as Netty that are well established in +the async, non-blocking space. -WebFlux can run on Servlet containers with support for the -Servlet 3.1 Non-Blocking IO API as well as on other async runtimes such as -Netty and Undertow. Each runtime is adapted to a reactive -`ServerHttpRequest` and `ServerHttpResponse` exposing the body of the -request and response as `Flux`, rather than -`InputStream` and `OutputStream`, with reactive backpressure. -REST-style JSON and XML serialization and deserialization is supported on top -as a `Flux`, and so is HTML view rendering and Server-Sent Events. +The other part of the answer is functional programming. Much like the addition of annotations +in Java 5 created opportunities -- e.g. annotated REST controllers or unit tests, the addition +of lambda expressions in Java 8 created opportunities for functional APIs in Java. +This is a boon for non-blocking applications and continuation style APIs -- as popularized +by `CompletableFuture` and http://reactivex.io/[ReactiveX], that allow declarative +composition of asynchronous logic. At the programming model level Java 8 enabled Spring +WebFlux to offer functional web endpoints alongside with annotated controllers. -[[webflux-server-annotation]] -=== Annotation-based Programming Model -The same `@Controller` programming model and the same annotations used in Spring MVC -are also supported in WebFlux. The main difference is that the underlying core, -framework contracts -- i.e. `HandlerMapping`, `HandlerAdapter`, are -non-blocking and operate on the reactive `ServerHttpRequest` and `ServerHttpResponse` -rather than on the `HttpServletRequest` and `HttpServletResponse`. -Below is an example with a reactive controller: +[[webflux-why-reactive]] +=== Reactive: what and why? +We touched on non-blocking and functional but why reactive and what do we mean? + +The term "reactive" refers to programming models that are built around reacting to change -- +network component reacting to I/O events, UI controller reacting to mouse events, etc. +In that sense non-blocking is reactive because instead of being blocked we are now in the mode +of reacting to notifications as operations complete or data becomes available. + +There is also another important mechanism that we on the Spring team associate with "reactive" +and that is non-blocking back pressure. In synchronous, imperative code, blocking calls +serve as a natural form of back pressure that forces the caller to wait. In non-blocking +code it becomes important to control the rate of events so that a fast producer does not +overwhelm its destination. + +Reactive Streams is a +https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.1/README.md#specification[small spec], +also http://download.java.net/java/jdk9/docs/api/java/util/concurrent/Flow.html[adopted] in Java 9, +that defines the interaction between asynchronous components with back pressure. +For example a data repository -- acting as +http://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Publisher.html[Publisher], +can produce data that an HTTP server -- acting as +http://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Subscriber.html[Subscriber], +can then write to the response. The main purpose of Reactive Streams is to allow the +subscriber to control how fast or how slow the publisher will produce data. + +[NOTE] +==== +*Common question: what if a publisher can't slow down?* + +The purpose of Reactive Streams is only to establish the mechanism and a boundary. +If a publisher can't slow down then it has to decide whether to buffer, drop, or fail. +==== + + +[[webflux-reactive-api]] +=== Reactive API + +Reactive Streams plays an important role for interoperability. It is of interest to libraries +and infrastructure components but less useful as an application API because it is too +low level. What applications need is a higher level and richer, functional API to +compose async logic -- similar to the Java 8 `Stream` API but not only for collections. +This is the role that reactive libraries play. + +https://github.com/reactor/reactor[Reactor] is the reactive library of choice for +Spring WebFlux. It provides the +https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html[Mono] and +https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html[Flux] API types +to work on data sequences of 0..1 and 0..N through a rich set of operators aligned with the +ReactiveX http://reactivex.io/documentation/operators.html[vocabulary of operators]. +Reactor is a Reactive Streams library and therefore all of its operators support non-blocking back pressure. +Reactor has a strong focus on server-side Java. +It is developed in close collaboration with and feedback from Spring projects. + +WebFlux requires Reactor as a core dependency but it is interoperable with other reactive +libraries via Reactive Streams. As a general rule WebFlux APIs accept a plain `Publisher` +as input, adapt it to Reactor types internally, use those, and then return either +`Flux` or `Mono` as output. So you can pass any `Publisher` as input and you can apply +operations on the output, but you'll need to adapt the output for use with another reactive library. +Whenever feasible -- e.g. annotated controllers, WebFlux adapts transparently to the use +of RxJava or other reactive library. See <> for more details. + + +[[webflux-programming-models]] +=== Programming models + +The `spring-web` module contains the reactive foundation that underlies Spring WebFlux -- +HTTP abstractions, Reactive Streams server adapters, reactive codecs, and a +core Web API whose role is comparable to the Servlet API but with non-blocking semantics. + +On that foundation Spring WebFlux provides a choice of two programming models: + +- <> -- consistent with Spring MVC, and based on the same annotations +from the `spring-web` module. Both Spring MVC and WebFlux controllers support reactive +(Reactor, RxJava) return types and as a result it is not easy to tell them apart. One notable +difference is that WebFlux also supports reactive `@RequestBody` arguments. +- <> -- lambda-based, lightweight, functional programming model. Think of +this as a small library or a set of utilities that an application can use to route and +handle requests. The big difference with annotated controllers is that the application +is in charge of request handling from start to finish vs declaring intent through +annotations and being called back. + + +[[webflux-framework-choice]] +=== Choosing a web framework + +Should you use Spring MVC or WebFlux? Let's cover a few different perspectives. + +If you have a Spring MVC application that works fine, there is no need to change. +Imperative programming is the easiest way to write, understand, and debug code. +You have maximum choice of libraries since historically most are blocking. + +If you are already shopping for a non-blocking web stack, Spring WebFlux offers the same +execution model benefits as others in this space and also provides a choice of servers -- +Netty, Tomcat, Jetty, Undertow, Servlet 3.1+ containers, a choice of programming models -- +annotated controllers and functional web endpoints, and a choice of reactive libraries -- +Reactor, RxJava, or other. + +If you are interested in a lightweight, functional web framework for use with Java 8 lambdas +or Kotlin then use the Spring WebFlux functional web endpoints. That can also be a good choice +for smaller applications or microservices with less complex requirements that can benefit +from greater transparency and control. + +In a microservice architecture you can have a mix of applications with either Spring MVC +or Spring WebFlux controllers, or with Spring WebFlux functional endpoints. Having support +for the same annotation-based programming model in both frameworks makes it easier to +re-use knowledge while also selecting the right tool for the right job. + +A simple way to evaluate an application is to check its dependencies. If you have blocking +persistence APIs, or networking APIs to use, then Spring MVC is the best choice for common +architectures at least. It is technically feasible with both Reactor and RxJava to perform +blocking calls on a separate thread but you wouldn't be making the most of a non-blocking +web stack. + +If you have a Spring MVC application with calls to remote services, try the reactive `WebClient`. +You can return reactive types (Reactor, RxJava, <>) +directly from Spring MVC controller methods. The greater the latency per call, or the +interdependency among calls, the more dramatic the benefits. Spring MVC controllers +can call other reactive components too. + +If you have a large team, keep in mind the steep learning curve in the shift to non-blocking, +functional, and declarative programming. A practical way to start without a full switch +is to use the reactive `WebClient`. Beyond that start small and measure the benefits. +We expect that for a wide range of applications the shift is unnecessary. + +If you are unsure what benefits to look for, start by learning about how non-blocking I/O +works (e.g. concurrency on single-threaded Node.js is not an oxymoron) and its effects. +The tag line is "scale with less hardware" but that effect is not guaranteed, not without +some network I/O that can be slow or unpredictable. This Netflix +https://medium.com/netflix-techblog/zuul-2-the-netflix-journey-to-asynchronous-non-blocking-systems-45947377fb5c[blog post] +is a good resource. + + +[[webflux-server-choice]] +=== Choosing a server + +Spring WebFlux is supported on Netty, Undertow, Tomcat, Jetty, and Servlet 3.1+ containers. +Each server is adapted to a common Reactive Streams API. The Spring WebFlux programming +models are built on that common API. + +[NOTE] +==== +*Common question: how can Tomcat and Jetty be used in both stacks?* + +Tomcat and Jetty are non-blocking at their core. It's the Servlet API that adds a +blocking facade. Starting in version 3.1 the Servlet API adds a choice for non-blocking I/O. +However its use requires care to avoid other synchronous and blocking parts. For this +reason Spring's reactive web stack has a low-level Servlet adapter to bridge to Reactive +Streams but the Servlet API is otherwise not exposed for direct use. +==== + +Spring Boot 2 uses Netty by default with WebFlux because Netty is more widely used in the +async, non-blocking space and also provides both client and server that can share resources. +By comparison Servlet 3.1 non-blocking I/O hasn't seen much use because the bar to use it +is so high. Spring WebFlux opens one practical path to adoption. + +The default server choice in Spring Boot is mainly about the out-of-the-box experience. +Applications can still choose any of the other supported servers which are also highly +optimized for performance, fully non-blocking, and adapted to Reactive Streams back +pressure. In Spring Boot it is trivial to make the switch. + + +[[webflux-performance]] +=== Performance vs scale + +Performance has many characteristics and meanings. Reactive and non-blocking generally +do not make applications run faster. They can, in some cases, for example if using the +`WebClient` to execute remote calls in parallel. On the whole it requires more work to do +things the non-blocking way and that can increase slightly the required processing time. + +The key expected benefit of reactive and non-blocking is the ability to scale with a small, +fixed number of threads and less memory. That makes applications more resilient under load +because they scale in a more predictable way. In order to observe those benefits however you +need to have some latency including a mix of slow and unpredictable network I/O. +That's where the reactive stack begins to show its strengths and the differences can be +dramatic. + + + +[[webflux-reactive-spring-web]] +== Reactive Spring Web + +The `spring-web` module provides low level infrastructure and HTTP abstractions -- client +and server, to build reactive web applications. All public APIs are build around Reactive +Streams with Reactor as a backing implementation. + +Server support is organized in two layers: + +* <> and server adapters -- the most basic, common API +for HTTP request handling with Reactive Streams back pressure. +* <> -- slightly higher level but still general +purpose server web API with filter chain style processing. + + +[[webflux-httphandler]] +=== HttpHandler + +Every HTTP server has some API for HTTP request handling. +{api-spring-framework}/http/server/reactive/HttpHandler.html[HttpHandler] +is a simple contract with one method to handle a request and response. +It is intentionally minimal. Its main purpose is to provide a common, Reactive Streams +based API for HTTP request handling over different servers. + +The `spring-web` module contains adapters for every supported server. The table below shows +the server APIs are used and where Reactive Streams support comes from: + +[cols="1,2,2", options="header"] +|=== +|Server name|Server API used|Reactive Streams support + +|Netty +|Netty API +|https://github.com/reactor/reactor-netty[Reactor Netty] + +|Undertow +|Undertow API +|spring-web: Undertow to Reactive Streams bridge + +|Tomcat +|Servlet 3.1 non-blocking I/O; Tomcat API to read and write ByteBuffers vs byte[] +|spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge + +|Jetty +|Servlet 3.1 non-blocking I/O; Jetty API to write ByteBuffers vs byte[] +|spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge + +|Servlet 3.1 container +|Servlet 3.1 non-blocking I/O +|spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge +|=== + +Here are required dependencies, +https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-the-Spring-Framework[supported versions], +and code snippets for each server: + +|=== +|Server name|Group id|Artifact name + +|Reactor Netty +|io.projectreactor.ipc +|reactor-netty + +|Undertow +|io.undertow +|undertow-core + +|Tomcat +|org.apache.tomcat.embed +|tomcat-embed-core + +|Jetty +|org.eclipse.jetty +|jetty-server, jetty-servlet +|=== + +Reactor Netty: [source,java,indent=0] [subs="verbatim,quotes"] ---- -@RestController -public class PersonController { - - private final PersonRepository repository; - - public PersonController(PersonRepository repository) { - this.repository = repository; - } - - @PostMapping("/person") - Mono create(@RequestBody Publisher personStream) { - return this.repository.save(personStream).then(); - } - - @GetMapping("/person") - Flux list() { - return this.repository.findAll(); - } - - @GetMapping("/person/{id}") - Mono findById(@PathVariable String id) { - return this.repository.findOne(id); - } -} ----- - -include::webflux-functional.adoc[leveloffset=+2] - - -[[webflux-client]] -== Client Side - -WebFlux includes a functional, reactive `WebClient` that offers a fully -non-blocking and reactive alternative to the `RestTemplate`. It exposes network -input and output as a reactive `ClientHttpRequest` and `ClientHttpResponse` where -the body of the request and response is a `Flux` rather than an -`InputStream` and `OutputStream`. In addition it supports the same reactive JSON, XML, -and SSE serialization mechanism as on the server side so you can work with typed objects. -Below is an example of using the `WebClient` which requires a `ClientHttpConnector` -implementation to plug in a specific HTTP client such as Reactor Netty: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -WebClient client = WebClient.create("http://example.com"); - -Mono account = client.get() - .url("/accounts/{id}", 1L) - .accept(APPLICATION_JSON) - .exchange(request) - .flatMap(response -> response.bodyToMono(Account.class)); ----- - -As stated above, `WebClient` requires a `ClientHttpConnector` to operate, the default being the `ReactorClientHttpConnector`. -When constructing a `ReactorClientHttpConnector`, you can use the `HttpClientOptions.Builder` to further customize it. -For instance, to customize the Netty `SslContext`: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -SslContext sslContext = ... -ReactorClientHttpConnector connector = new ReactorClientHttpConnector(builder -> { - builder.sslContext(sslContext); -}); -WebClient webClient = WebClient.builder() - .clientConnector(connector) - .build(); ----- - -[[webflux-http-body]] -== Request and Response Body Conversion - -The `spring-core` module provides reactive `Encoder` and `Decoder` contracts -that enable the serialization of a `Flux` of bytes to and from typed objects. -The `spring-web` module adds JSON (Jackson) and XML (JAXB) implementations for use in -web applications as well as others for SSE streaming and zero-copy file transfer. - -The following Reactive APIs are supported: - -* Reactor 3.x is supported out of the box -* RxJava 2.x is supported when `io.reactivex.rxjava2:rxjava` dependency is on the classpath -* RxJava 1.x is supported when both `io.reactivex:rxjava` and `io.reactivex:rxjava-reactive-streams` (https://github.com/ReactiveX/RxJavaReactiveStreams[adapter between RxJava and Reactive Streams]) dependencies are on the classpath - -For example the request body can be one of the following way and it will be decoded -automatically in both the annotation and the functional programming models: - -* `Account account` -- the account is deserialized without blocking before the controller is invoked. -* `Mono account` -- the controller can use the `Mono` to declare logic to be executed after the account is deserialized. -* `Single account` -- same as with `Mono` but using RxJava -* `Flux accounts` -- input streaming scenario. -* `Observable accounts` -- input streaming with RxJava. - -The response body can be one of the following: - -* `Mono` -- serialize without blocking the given Account when the `Mono` completes. -* `Single` -- same but using RxJava. -* `Flux` -- streaming scenario, possibly SSE depending on the requested content type. -* `Observable` -- same but using RxJava `Observable` type. -* `Flowable` -- same but using RxJava 2 `Flowable` type. -* `Publisher` or `Flow.Publisher` -- any type implementing Reactive Streams `Publisher` is supported. -* `Flux` -- SSE streaming. -* `Mono` -- request handling completes when the `Mono` completes. -* `Account` -- serialize without blocking the given Account; implies a synchronous, non-blocking controller method. -* `void` -- specific to the annotation-based programming model, request handling completes -when the method returns; implies a synchronous, non-blocking controller method. - -When using stream types like `Flux` or `Observable`, the media type specified in the -request/response or at mapping/routing level is used to determine how the data should be serialized -and flushed. For example a REST endpoint that returns a `Flux` will be serialized by -default as following: - -* `application/json`: a `Flux` is handled as an asynchronous collection and - serialized as a JSON array with an explicit flush when the `complete` event is emitted. -* `application/stream+json`: a `Flux` will be handled as a stream of `Account` elements - serialized as individual JSON object separated by new lines and explicitly flushed after - each element. The `WebClient` supports JSON stream decoding so this is a good use case - for server to server use case. -* `text/event-stream`: a `Flux` or `Flux>` will be handled as - a stream of `Account` or `ServerSentEvent` elements serialized as individual SSE elements - using by default JSON for data encoding and explicit flush after each element. This - is well suited for exposing a stream to browser clients. `WebClient` supports - reading SSE streams as well. - - -[[webflux-websocket-support]] -== Reactive WebSocket Support - -WebFlux includes reactive WebSocket client and server support. -Both client and server are supported on the Java WebSocket API -(JSR-356), Jetty, Undertow, and Reactor Netty. - -On the server side, declare a `WebSocketHandlerAdapter` and then simply add -mappings to `WebSocketHandler`-based endpoints: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -@Bean -public HandlerMapping webSocketMapping() { - Map map = new HashMap<>(); - map.put("/foo", new FooWebSocketHandler()); - map.put("/bar", new BarWebSocketHandler()); - - SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); - mapping.setOrder(10); - mapping.setUrlMap(map); - return mapping; -} - -@Bean -public WebSocketHandlerAdapter handlerAdapter() { - return new WebSocketHandlerAdapter(); -} ----- - -On the client side create a `WebSocketClient` for one of the supported libraries -listed above: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -WebSocketClient client = new ReactorNettyWebSocketClient(); -client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000); ----- - -[[webflux-tests]] -== Testing - -The `spring-test` module includes a `WebTestClient` that can be used to test -WebFlux server endpoints with or without a running server. - -Tests without a running server are comparable to `MockMvc` from Spring MVC -where mock request and response are used instead of connecting over the network -using a socket. The `WebTestClient` however can also perform tests against a -running server. - -For more see -https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples[sample tests] -in the framework. - - - -[[webflux-getting-started]] -= Getting Started - - -[[webflux-getting-started-boot]] -== Spring Boot Starter - -The -Spring Boot WebFlux starter available via http://start.spring.io is the fastest way to get started. -It does all that's necessary so you to start writing `@Controller` classes -just like with Spring MVC. Simply go to http://start.spring.io, choose -version 2.0.0.BUILD-SNAPSHOT, and type reactive in the dependencies box. -By default the starter runs with Reactor Netty but the dependencies can be changed as usual -with Spring Boot to switch to a different runtime. -See the Spring Boot reference documentation page for more details and instruction. - -[[webflux-getting-started-manual]] -== Manual Bootstrapping - -This section outlines the steps to get up and running manually. - -For dependencies start with `spring-webflux` and `spring-context`. -Then add `jackson-databind` and `io.netty:netty-buffer` -(temporarily see https://jira.spring.io/browse/SPR-14528[SPR-14528]) for JSON support. -Lastly add the dependencies for one of the supported runtimes: - -* Tomcat -- `org.apache.tomcat.embed:tomcat-embed-core` -* Jetty -- `org.eclipse.jetty:jetty-server` and `org.eclipse.jetty:jetty-servlet` -* Reactor Netty -- `io.projectreactor.ipc:reactor-netty` -* Undertow -- `io.undertow:undertow-core` - -For the **annotation-based programming model** bootstrap with: -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebFluxConfiguration.class); // (1) -HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2) ----- - -The above loads default Spring Web framework configuration (1), then creates a -`DispatcherHandler`, the main class driving request processing (2), and adapts -it to `HttpHandler` -- the lowest level Spring abstraction for reactive HTTP request handling. - -For the **functional programming model** bootstrap as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // (1) -context.registerBean(FooBean.class, () -> new FooBeanImpl()); // (2) -context.registerBean(BarBean.class); // (3) -context.refresh(); - -HttpHandler handler = WebHttpHandlerBuilder - .webHandler(RouterFunctions.toHttpHandler(...)) - .applicationContext(context) - .build(); // (4) ----- - -The above creates an `AnnotationConfigApplicationContext` instance (1) that can take advantage -of the new functional bean registration API (2) to register beans using a Java 8 `Supplier` -or just by specifying its class (3). The `HttpHandler` is created using `WebHttpHandlerBuilder` (4). - -The `HttpHandler` can then be installed in one of the supported runtimes: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- -// Tomcat and Jetty (also see notes below) -HttpServlet servlet = new ServletHttpHandlerAdapter(handler); -... - -// Reactor Netty +HttpHandler handler = ... ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer.create(host, port).newHandler(adapter).block(); +---- -// Undertow +Undertow: +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +HttpHandler handler = ... UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler); Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build(); server.start(); ---- +Tomcat: +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +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(); +---- + +Jetty: +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +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(); +---- + +You can also deploy as a WAR to any Servlet 3.1 container by wrapping the handler with +`ServletHttpHandlerAdapter` as a `Servlet`. + + + +[[webflux-web-handler-api]] +=== WebHandler API + +`HttpHandler` is the basis for running on different servers. On that base the WebHandler +API provides a slightly higher level processing chain of +exception handlers +({api-spring-framework}/web/server/WebExceptionHandler.html[WebExceptionHandler]), filters +({api-spring-framework}/web/server/WebFilter.html[WebFilter]), and a target handler +({api-spring-framework}/web/server/WebHandler.html[WebHandler]). + +All components work on `ServerWebExchange` -- a container for the HTTP request and +response that also adds request attributes, session attributes, access to form data, +multipart data, and more. + +The processing chain can be put together with `WebHttpHandlerBuilder` which builds an +`HttpHandler` that in turn can be run with a <>. +To use the builder either add components individually or point to an `ApplicationContext` +to have the following detected: + +[cols="2,2,1,3", options="header"] +|=== +|Bean name|Bean type|Count|Description + +|"webHandler" +|WebHandler +|1 +|Target handler after filters + +| +|WebFilter +|0..N +|Filters + +| +|WebExceptionHandler +|0..N +|Exception handlers after filter chain + +|"webSessionManager" +|WebSessionManager +|0..1 +|Custom session manager; `DefaultWebSessionManager` by default + +|"serverCodecConfigurer" +|ServerCodecConfigurer +|0..1 +|Custom form and multipart data decoders; `ServerCodecConfigurer.create()` by default + +|"localeContextResolver" +|LocaleContextResolver +|0..1 +|Custom resolver for `LocaleContext`; `AcceptHeaderLocaleContextResolver` by default +|=== + + +[[webflux-codecs]] +=== Codecs + +The `spring-web` module provides +{api-spring-framework}/http/codec/HttpMessageReader.html[HttpMessageReader] and +{api-spring-framework}/http/codec/HttpMessageWriter.html[HttpMessageWriter] +for encoding and decoding the HTTP request and response body with Reactive Streams. +It builds on lower level contracts from `spring-core`: + +* {api-spring-framework}/core/io/buffer/DataBuffer.html[DataBuffer] -- abstraction for +byte buffers -- e.g. Netty `ByteBuf`, `java.nio.ByteBuffer` +* {api-spring-framework}/core/codec/Encoder.html[Encoder] -- serialize a stream of Objects +to a stream of data buffers +* {api-spring-framework}/core/codec/Decoder.html[Decoder] -- deserialize a stream of data +buffers into a stream of Objects + +Basic `Encoder` and `Decoder` implementations exist in `spring-core` but `spring-web` adds +more for JSON, XML, and other formats. You can wrap any `Encoder` and `Decoder` as a reader +or writer with `EncoderHttpMessageWriter` and `DecoderHttpMessageReader`. There are some +additional, web-only reader and writer implementations for server-sent events, form data, +and more. + +Finally, `ClientCodecConfigurer` and `ServerCodecConfigurer` can be used to initialize +a list of readers and writers. They include support for classpath detection and a +of defaults along with the ability to override or replace those defaults. + + + +[[webflux-dispatcher-handler]] +== The DispatcherHandler +[.small]#<># + +Spring WebFlux, like Spring MVC, is designed around the front controller pattern where a +central `WebHandler`, the `DispatcherHandler`, provides a shared algorithm for request +processing while actual work is performed by configurable, delegate components. +This model is flexible and supports diverse workflows. + +`DispatcherHandler` discovers the delegate components it needs from Spring configuration. +It is also designed to be a Spring bean itself and implements `ApplicationContextAware` +for access to the context it runs in. If `DispatcherHandler` is declared with the bean +name "webHandler" it is in turn discovered by +{api-spring-framework}/web/server/adapter/WebHttpHandlerBuilder.html[WebHttpHandlerBuilder] +which puts together a request processing chain as described in +<>. + +Spring configuration in a WebFlux application typically contains: + +* `DispatcherHandler` with the bean name "webHandler" +* `WebFilter` and `WebExceptionHandler` beans +* <> +* Others + +The configuration is given to `WebHttpHandlerBuilder` to build the processing chain: +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +ApplicationContext context = ... +HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context); +---- + +The resulting `HttpHandler` is ready for use with a +<>. + + + +[[webflux-special-bean-types]] +=== Special bean types +[.small]#<># + +The `DispatcherHandler` delegates to special beans to process requests and render the +appropriate responses. By "special beans" we mean Spring-managed Object instances that +implement one of the framework contracts listed in the table below. +Spring WebFlux provides built-in implementations of these contracts but you can also +customize, extend, or replace them. + +.Special bean types in the ApplicationContext +|=== +| Bean type| Explanation + +| HandlerMapping +| Map a request to a handler. The mapping is based on some criteria the details of + which vary by `HandlerMapping` implementation -- annotated controllers, simple + URL pattern mappings, etc. + +| HandlerAdapter +| Helps the `DispatcherHandler` to invoke a handler mapped to a request regardless of + how the handler is actually invoked. For example invoking an annotated controller + requires resolving various annotations. The main purpose of a `HandlerAdapter` is + to shield the `DispatcherHandler` from such details. + +| HandlerResultHandler +| Process the `HandlerResult` returned from a `HandlerAdapter`. +|=== + + +[[webflux-dispatcher-handler-sequence]] +=== Processing sequence +[.small]#<># + +The `DispatcherHandler` processes requests as follows: + +* Each `HandlerMapping` is asked to find a matching handler and the first match is used. +* If a handler is found, it is executed through an appropriate `HandlerAdapter` which +exposes the return value from the execution as `HandlerResult`. +* The `HandlerResult` is given to an appropriate `HandlerResultHandler` to complete +processing by writing to the response directly or using a view to render. + + + +[[webflux-controller]] +== Annotated Controllers +[.small]#<># + +Spring WebFlux provides an annotation-based programming model where `@Controller` and +`@RestController` components use annotations to express request mappings, request input, +exception handling, and more. Annotated controllers have flexible method signatures and +do not have to extend base classes nor implement specific interfaces. + +Here is a basic example: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @RestController + public class HelloController { + + @GetMapping("/hello") + public String handle() { + return "Hello WebFlux"; + } + } +---- + +In this example the methods returns a String to be written to the response body. + + + +[[webflux-ann-controller]] +=== @Controller declaration +[.small]#<># + +You can define controller beans using a standard Spring bean definition. +The `@Controller` stereotype allows for auto-detection, aligned with Spring general support +for detecting `@Component` classes in the classpath and auto-registering bean definitions +for them. It also acts as a stereotype for the annotated class, indicating its role as +a web component. + +To enable auto-detection of such `@Controller` beans, you can add component scanning to +your Java configuration: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @ComponentScan("org.example.web") + public class WebConfig { + + // ... + } +---- + [NOTE] ==== -For Servlet containers especially with WAR deployment you can use the -`AbstractAnnotationConfigDispatcherHandlerInitializer` which as a -`WebApplicationInitializer` and is auto-detected by Servlet containers. -It takes care of registering the `ServletHttpHandlerAdapter` as shown above. -You will need to implement one abstract method in order to point to your -Spring configuration. +`@RestController` is a composed annotation that is itself annotated with `@Controller` and +`@ResponseBody` indicating a controller whose every method inherits the type-level +`@ResponseBody` annotation and therefore writes to the response body (vs model-and-vew +rendering). ==== -[[webflux-getting-started-examples]] -== Examples -You will find code examples useful to build reactive Web application in the following projects: +[[webflux-ann-requestmapping]] +=== Mapping Requests +[.small]#<># + +The `@RequestMapping` annotation is used to map requests to controllers methods. It has +various attributes to match by URL, HTTP method, request parameters, headers, and media +types. It can be used at the class-level to express shared mappings or at the method level +to narrow down to a specific endpoint mapping. + +There are also HTTP method specific shortcut variants of `@RequestMapping`: + +- `@GetMapping` +- `@PostMapping` +- `@PutMapping` +- `@DeleteMapping` +- `@PatchMapping` + +The shortcut variants are +https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model#composed-annotations[composed annotations] +-- themselves annotated with `@RequestMapping`. They are commonly used at the method level. +At the class level an `@RequestMapping` is more useful for expressing shared mappings. + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @RestController + @RequestMapping("/persons") + class PersonController { + + @GetMapping("/{id}") + public Person getPerson(@PathVariable Long id) { + // ... + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public void add(@RequestBody Person person) { + // ... + } + } +---- + + +[[webflux-ann-requestmapping-uri-templates]] +==== URI Patterns +[.small]#<># + +You can map requests using glob patterns and wildcards: + +* `?` matches one character +* `*` matches zero or more characters within a path segment +* `**` match zero or more path segments + +You can also declare URI variables and access their values with `@PathVariable`: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @GetMapping("/owners/{ownerId}/pets/{petId}") + public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { + // ... + } +---- + +URI variables can be declared at the class and method level: +[source,java,intent=0] +[subs="verbatim,quotes"] +---- +@Controller +@RequestMapping("/owners/{ownerId}") +public class OwnerController { + + @GetMapping("/pets/{petId}") + public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { + // ... + } +} +---- + +URI variables are automatically converted to the appropriate type or`TypeMismatchException` +is raised. Simple types -- `int`, `long`, `Date`, are supported by default and you can +register support for any other data type. +// TODO: see <> and <>. + +URI variables can be named explicitly -- e.g. `@PathVariable("customId")`, but you can +leave that detail out if the names are the same and your code is compiled with debugging +information or with the `-parameters` compiler flag on Java 8. + +The syntax `{*varName}` declares a URI variable that matches zero or more remaining +path segments. For example `/resources/{*path}` matches all files `/resources/` and the +`"path"` variable captures the complete relative path. + +The syntax `{varName:regex}` declares a URI variable with a regular expressions with the +syntax `{varName:regex}` -- e.g. given URL `"/spring-web-3.0.5 .jar"`, the below method +extracts the name, version, and file extension: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") + public void handle(@PathVariable String version, @PathVariable String ext) { + // ... + } +---- + +URI path patterns can also have embedded `${...}` placeholders that are resolved on startup + via `PropertyPlaceHolderConfigurer` against local, system, environment, and other property +sources. This can be used for example to parameterize a base URL based on some external +configuration. + +[NOTE] +==== +Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support +both of which are located in `spring-web` and expressly designed for use with HTTP URL +paths in web applications where a large number of URI path patterns are matched at runtime. +==== + +Spring WebFlux does not support suffix pattern matching -- unlike Spring MVC, where a +mapping such as `/person` also matches to `/person.{asterisk}`. For URL based content +negotiation, if needed, we recommend using a query parameter, which is more simpler, more +explicit, and less vulnerable to URL path based exploits. + + +[[webflux-ann-requestmapping-pattern-comparison]] +==== Pattern Comparison +[.small]#<># + +When multiple patterns match a URL, they must be compared to find the best match. This is done +with `PathPattern.SPECIFICITY_COMPARATOR` which looks for patterns that more specific. + +For every pattern, a score is computed based the number of URI variables and wildcards +where a URI variable scores lower than a wildcard. A pattern with a lower total score +wins. If two patterns have the same score, then the longer is chosen. + +Catch-all patterns, e.g. `**`, `{*varName}`, are excluded from the scoring and are always +sorted last instead. If two patterns are both catch-all, the longer is chosen. + + +[[webflux-ann-requestmapping-consumes]] +==== Consumable Media Types +[.small]#<># + +You can narrow the request mapping based on the `Content-Type` of the request: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @PostMapping(path = "/pets", **consumes = "application/json"**) + public void addPet(@RequestBody Pet pet) { + // ... + } +---- + +The consumes attribute also supports negation expressions -- e.g. `!text/plain` means any +content type other than "text/plain". + +You can declare a shared consumes attribute at the class level. Unlike most other request +mapping attributes however when used at the class level, a method-level consumes attribute +will overrides rather than extend the class level declaration. + +[TIP] +==== +`MediaType` provides constants for commonly used media types -- e.g. +`APPLICATION_JSON_VALUE`, `APPLICATION_JSON_UTF8_VALUE`. +==== + + +[[webflux-ann-requestmapping-produces]] +==== Producible Media Types +[.small]#<># + +You can narrow the request mapping based on the `Accept` request header and the list of +content types that a controller method produces: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @GetMapping(path = "/pets/{petId}", **produces = "application/json;charset=UTF-8"**) + @ResponseBody + public Pet getPet(@PathVariable String petId) { + // ... + } +---- + +The media type can specify a character set. Negated expressions are supported -- e.g. +`!text/plain` means any content type other than "text/plain". + +You can declare a shared produces attribute at the class level. Unlike most other request +mapping attributes however when used at the class level, a method-level produces attribute +will overrides rather than extend the class level declaration. + +[TIP] +==== +`MediaType` provides constants for commonly used media types -- e.g. +`APPLICATION_JSON_VALUE`, `APPLICATION_JSON_UTF8_VALUE`. +==== + + +[[webflux-ann-requestmapping-params-and-headers]] +==== Parameters and Headers +[.small]#<># + +You can narrow request mappings based on query parameter conditions. You can test for the +presence of a query parameter (`"myParam"`), for the absence (`"!myParam"`), or for a +specific value (`"myParam=myValue"`): + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @GetMapping(path = "/pets/{petId}", **params = "myParam=myValue"**) + public void findPet(@PathVariable String petId) { + // ... + } +---- + +You can also use the same with request header conditions: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @GetMapping(path = "/pets", **headers = "myHeader=myValue"**) + public void findPet(@PathVariable String petId) { + // ... + } +---- + + +[[webflux-ann-requestmapping-head-options]] +==== HTTP HEAD, OPTIONS +[.small]#<># + +`@GetMapping` -- and also `@RequestMapping(method=HttpMethod.GET)`, are implicitly mapped to +and also support HTTP HEAD. An HTTP HEAD request is processed as if it were HTTP GET except +but instead of writing the body, the number of bytes are counted and the "Content-Length" +header set. + +By default HTTP OPTIONS is handled by setting the "Allow" response header to the list of HTTP +methods listed in all `@RequestMapping` methods with matching URL patterns. + +For a `@RequestMapping` without HTTP method declarations, the "Allow" header is set to +`"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"`. Controller methods should always declare the +supported HTTP methods for example by using the HTTP method specific variants -- +`@GetMapping`, `@PostMapping`, etc. + +`@RequestMapping` method can be explicitly mapped to HTTP HEAD and HTTP OPTIONS, but that +is not necessary in the common case. + + + +[[webflux-ann-methods]] +=== Handler methods +[.small]#<># + +`@RequestMapping` handler methods have a flexible signature and can choose from a range of +supported controller method arguments and return values. + +[[webflux-ann-arguments]] +==== Method arguments +[.small]#<># + +The table below shows supported controller method arguments. + +Reactive types (Reactor, RxJava, <>) are +supported on arguments that require blocking I/O, e.g. reading the request body, to +be resolved. This is marked in the description column. Reactive types are not expected +on arguments that don't require blocking. + +JDK 1.8's `java.util.Optional` is supported as a method argument in combination with +annotations that have a `required` attribute -- e.g. `@RequestParam`, `@RequestHeader`, +etc, and is equivalent to `required=false`. + +[cols="1,2", options="header"] +|=== +|Controller method argument|Description + +|`ServerWebExchange` +|Access to the full `ServerWebExchange` -- container for the HTTP request and response, +request and session attributes, `checkNotModified` methods, and others. + +|`ServerHttpRequest`, `ServerHttpResponse` +|Access to the HTTP request or response. + +|`WebSession` +|Access to the session; this does not forcing the start of a new session unless attributes +are added. Supports reactive types. + +|`java.security.Principal` +|Currently authenticated user; possibly a specific `Principal` implementation class if known. +Supports reactive types. + +|`org.springframework.http.HttpMethod` +|The HTTP method of the request. + +// TODO: not supported +// |`java.util.Locale` +// |The current request locale, determined by the most specific `LocaleResolver` available, in +// effect, the configured `LocaleResolver`/`LocaleContextResolver`. + +// TODO: not supported +//|Java 6+: `java.util.TimeZone` + +//Java 8+: `java.time.ZoneId` +//|The time zone associated with the current request, as determined by a `LocaleContextResolver`. + +|`@PathVariable` +|For access to URI template variables. +// TODO: See <>. + +|`@RequestParam` +|For access to Servlet request parameters. Parameter values are converted to the declared +method argument type. +// TODO: See <>. + +|`@RequestHeader` +|For access to request headers. Header values are converted to the declared method argument +type. +// TODO: See <>. + +|`@RequestBody` +|For access to the HTTP request body. Body content is converted to the declared method +argument type using ``HttpMessageReader``'s. Supports reactive types. +// TODO: See <>. + +|`HttpEntity` +|For access to request headers and body. The body is converted with ``HttpMessageReader``'s. +Supports reactive types. +// TODO: See <>. + +|`@RequestPart` +|For access to a part in a "multipart/form-data" request. Supports reactive types. +// TODO: See <> and <>. + +|`java.util.Map`, `org.springframework.ui.Model`, `org.springframework.ui.ModelMap` +|For access and updates of the implicit model that is exposed to the web view. + +|Command or form object (with optional `@ModelAttribute`) +|Command object whose properties to bind to request parameters -- via setters or directly to +fields, with customizable type conversion, depending on `@InitBinder` methods and/or the +HandlerAdapter configuration (see the `webBindingInitializer` property on +`RequestMappingHandlerAdapter`). + +Command objects along with their validation results are exposed as model attributes, by +default using the command class name - e.g. model attribute "orderAddress" for a command +object of type "some.package.OrderAddress". `@ModelAttribute` can be used to customize the +model attribute name. + +Supports reactive types. + +|`Errors`, `BindingResult` +|Validation results for the command/form object data binding; this argument must be +declared immediately after the command/form object in the controller method signature. + +|`SessionStatus` +|For marking form processing complete which triggers cleanup of session attributes +declared through a class-level `@SessionAttributes` annotation. + +|`UriComponentsBuilder` +|For preparing a URL relative to the current request's host, port, scheme, context path, and +the literal part of the servlet mapping also taking into account `Forwarded` and +`X-Forwarded-*` headers. + +|`@SessionAttribute` +|For access to any session attribute; in contrast to model attributes stored in the session +as a result of a class-level `@SessionAttributes` declaration. + +|`@RequestAttribute` +|For access to request attributes. +|=== + +[[webflux-ann-return-types]] +==== Return values +[.small]#<># + +The table below shows supported controller method return values. Reactive types -- +Reactor, RxJava, <> are supported for all return +values. + +[cols="1,2", options="header"] +|=== +|Controller method return value|Description + +|`@ResponseBody` +|The return value is encoded through ``HttpMessageWriter``s and written to the response. +// TODO: See <>. + +|`HttpEntity`, `ResponseEntity` +|The return value specifies the full response including HTTP headers and body be encoded +through ``HttpMessageWriter``s and written to the response. +// TODO: See <>. + +|`HttpHeaders` +|For returning a response with headers and no body. + +|`String` +|A view name to be resolved with ``ViewResolver``'s and used together with the implicit +model -- determined through command objects and `@ModelAttribute` methods. The handler +method may also programmatically enrich the model by declaring a `Model` argument (see +above). + +|`View` +|A `View` instance to use for rendering together with the implicit model -- determined +through command objects and `@ModelAttribute` methods. The handler method may also +programmatically enrich the model by declaring a `Model` argument (see above). + +|`java.util.Map`, `org.springframework.ui.Model` +|Attributes to be added to the implicit model with the view name implicitly determined +from the request path. + +|`Rendering` +|An API for model and view rendering scenarios. + +|`void` +|For use in method that don't write the response body; or methods where the view name is +supposed to be determined implicitly from the request path. + +|`Flux`, `Observable`, or other reactive type +|Emit server-sent events; the `SeverSentEvent` wrapper can be omitted when only data needs +to be written (however `text/event-stream` must be requested or declared in the mapping +through the produces attribute). + +|Any other return type +|A single model attribute to be added to the implicit model with the view name implicitly +determined through a `RequestToViewNameTranslator`; the attribute name may be specified +through a method-level `@ModelAttribute` or otherwise a name is selected based on the +class name of the return type. +|=== + + + +include::webflux-functional.adoc[leveloffset=+1] + + + +[[webflux-config]] +== WebFlux Java Config +[.small]#<># + +The WebFlux Java config provides default configuration suitable for most applications along +with a configuration API to customize it. For more advanced customizations, not available in +the configuration API, see <>. + +You do not need to understand the underlying beans created by the Java config, but it's +easy to seem them in `WebFluxConfigurationSupport`, and if you want to learn more, see +<>. + + +[[webflux-config-enable]] +=== Enable the configuration +[.small]#<># + +Use the `@EnableWebFlux` annotation in your Java config: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig { + } +---- + +The above registers a number of Spring WebFlux +<> also adapting to dependencies +available on the classpath -- for JSON, XML, etc. + + +[[webflux-config-customize]] +=== Configuration API +[.small]#<># + +In your Java config implement the `WebFluxConfigurer` interface: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + // Implement configuration methods... + + } +---- + + + +[[webflux-config-conversion]] +=== Conversion, formatting +[.small]#<># + +By default formatters for `Number` and `Date` types are installed, including support for +the `@NumberFormat` and `@DateTimeFormat` annotations. Full support for the Joda Time +formatting library is also installed if Joda Time is present on the classpath. + +To register custom formatters and converters: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + // ... + } + + } +---- + +[NOTE] +==== +See <> +and the `FormattingConversionServiceFactoryBean` for more information on when to use FormatterRegistrars. +==== + + +[[webflux-config-validation]] +=== Validation +[.small]#<># + +By default if <> is present +on the classpath -- e.g. Hibernate Validator, the `LocalValidatorFactoryBean` is registered +as a global <> for use with `@Valid` and `Validated` on +`@Controller` method arguments. + +In your Java config, you can customize the global `Validator` instance: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public Validator getValidator(); { + // ... + } + + } +---- + +Note that you can also register ``Validator``'s locally: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Controller + public class MyController { + + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.addValidators(new FooValidator()); + } + + } +---- + +[TIP] +==== +If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and +mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config. +==== + + +[[webflux-config-content-negotiation]] +=== Content type resolvers +[.small]#<># + +You can configure how Spring WebFlux determines the requested media types for +``@Controller``'s from the request. By default only the "Accept" header is checked but you +can also enable a query parameter based strategy. + +To customize the requested content type resolution: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) { + // ... + } + } +---- + +[[webflux-config-message-codecs]] +=== HTTP message codecs +[.small]#<># + +To customize how the request and response body are read and written: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + // ... + } + } +---- + +`ServerCodecConfigurer` provides a set of default readers and writers. You can use it to add +more readers and writers, customize the default ones, or replace the default ones completely. + +For Jackson JSON and XML, consider using the +{api-spring-framework}/http/converter/json/Jackson2ObjectMapperBuilder.html[Jackson2ObjectMapperBuilder] +which customizes Jackson's default properties with the following ones: + +. http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES[`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`] is disabled. +. http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html#DEFAULT_VIEW_INCLUSION[`MapperFeature.DEFAULT_VIEW_INCLUSION`] is disabled. + +It also automatically registers the following well-known modules if they are detected on the classpath: + +. https://github.com/FasterXML/jackson-datatype-jdk7[jackson-datatype-jdk7]: support for Java 7 types like `java.nio.file.Path`. +. https://github.com/FasterXML/jackson-datatype-joda[jackson-datatype-joda]: support for Joda-Time types. +. https://github.com/FasterXML/jackson-datatype-jsr310[jackson-datatype-jsr310]: support for Java 8 Date & Time API types. +. https://github.com/FasterXML/jackson-datatype-jdk8[jackson-datatype-jdk8]: support for other Java 8 types like `Optional`. + + +[[webflux-config-view-resolvers]] +=== View resolvers +[.small]#<># + +To configure view resolution: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void configureViewResolvers(ViewResolverRegistry registry) { + // ... + } + } +---- + +Note that FreeMarker also requires configuration of the underlying view technology: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + // ... + + @Bean + public FreeMarkerConfigurer freeMarkerConfigurer() { + FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); + configurer.setTemplateLoaderPath("classpath:/templates"); + return configurer; + } + + } +---- + + +[[webflux-config-static-resources]] +=== Static resources +[.small]#<># + +This option provides a convenient way to serve static resources from a list of +{api-spring-framework}/core/io/Resource.html[Resource]-based locations. + +In the example below, given a request that starts with `"/resources"`, the relative path is +used to find and serve static resources relative to `"/static"` on the classpath. Resources +will be served with a 1-year future expiration to ensure maximum use of the browser cache +and a reduction in HTTP requests made by the browser. The `Last-Modified` header is also +evaluated and if present a `304` status code is returned. + +[source,java,indent=0] +[subs="verbatim"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**") + .addResourceLocations("/public", "classpath:/static/") + .setCachePeriod(31556926); + } + + } +---- + +// TODO: +// See also <>. + +The resource handler also supports a chain of +{api-spring-framework}/web/reactive/resource/ResourceResolver.html[ResourceResolver]'s and +{api-spring-framework}/web/reactive/resource/ResourceTransformer.html[ResourceResolver]'s. +which can be used to create a toolchain for working with optimized resources. + +The `VersionResourceResolver` can be used for versioned resource URLs based on an MD5 hash +computed from the content, a fixed application version, or other. A +`ContentVersionStrategy` (MD5 hash) is a good choice with some notable exceptions such as +JavaScript resources used with a module loader. + +For example in your Java config; + +[source,java,indent=0] +[subs="verbatim"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**") + .addResourceLocations("/public/") + .resourceChain(true) + .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**")); + } + + } +---- + +You can use `ResourceUrlProvider` to rewrite URLs and apply the full chain of resolvers and +transformers -- e.g. to insert versions. The WebFlux config provides a `ResourceUrlProvider` +so it can be injected into others. + +Unlike Spring MVC at present in WebFlux there is no way to transparely rewrite static +resource URLs since the are no view technologies that can make use of a non-blocking chain +of resolvers and transformers (e.g. resources on Amazon S3). When serving only local +resources the workaround is to use `ResourceUrlProvider` directly (e.g. through a custom +tag) and block for 0 seconds. + +http://www.webjars.org/documentation[WebJars] is also supported via `WebJarsResourceResolver` +and automatically registered when `"org.webjars:webjars-locator"` is present on the +classpath. The resolver can re-write URLs to include the version of the jar and can also +match to incoming URLs without versions -- e.g. `"/jquery/jquery.min.js"` to +`"/jquery/1.2.0/jquery.min.js"`. + + +[[webflux-config-path-matching]] +=== Path Matching +[.small]#<># + +Spring WebFlux uses parsed representation of path patterns -- i.e. `PathPattern`, and also +the incoming request path -- i.e. `RequestPath`, which eliminates the need to indicate +whether to decode the request path, or remove semicolon content, since `PathPattern` +can now access decoded path segment values and match safely. + +Spring WebFlux also does not support suffix pattern matching so effectively there are only two +minor options to customize related to path matching -- whether to match trailing slashes +(`true` by default) and whether the match is case-sensitive (`false`). + +To customize those options: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + // ... + } + + } +---- + + +[[webflux-config-advanced-java]] +=== Advanced config mode +[.small]#<># + +`@EnableWebFlux` imports `DelegatingWebFluxConfiguration` that (1) provides default +Spring configuration for WebFlux applications and (2) detects and delegates to +``WebFluxConfigurer``'s to customize that configuration. + +For advanced mode, remove `@EnableWebFlux` and extend directly from +`DelegatingWebFluxConfiguration` instead of implementing `WebFluxConfigurer`: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + public class WebConfig extends DelegatingWebFluxConfiguration { + + // ... + + } +---- + +You can keep existing methods in `WebConfig` but you can now also override bean declarations +from the base class and you can still have any number of other ``WebMvcConfigurer``'s on +the classpath. + + +[[webflux-client]] +== WebClient + +The `spring-webflux` module includes a non-blocking, reactive client for HTTP requests +with Reactive Streams back pressure. It shares <> and other +infrastructure with the server <>. + +`WebClient` provides a higher level API over HTTP client libraries. By default +it uses https://github.com/reactor/reactor-netty[Reactor Netty] but that is pluggable +with a different `ClientHttpConnector`. The `WebClient` API returns Reactor `Flux` or +`Mono` for output and accepts Reactive Streams `Publisher` as input (see +<>). + +[TIP] +==== +By comparison to the +<>, the `WebClient` offers a more +functional and fluent API that taking full advantage of Java 8 lambdas. It supports both +sync and async scenarios, including streaming, and brings the efficiency of +non-blocking I/O. +==== + + +[[webflux-client-retrieve]] +=== Retrieve + +The `retrieve()` method is the easiest way to get a decoded response body: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + WebClient client = WebClient.create("http://example.org"); + + Mono result = client.get() + .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(Person.class); +---- + +You can also get a stream of decoded objects: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Flux result = client.get() + .uri("/quotes").accept(TEXT_EVENT_STREAM) + .retrieve() + .bodyToFlux(Quote.class); +---- + +By default, a response with 4xx or 5xx status code results in an error of type +`WebClientResponseException` but you can customize that: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Mono result = client.get() + .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) + .retrieve() + .onStatus(HttpStatus::is4xxServerError, response -> ...) + .onStatus(HttpStatus::is5xxServerError, response -> ...) + .bodyToFlux(Person.class); +---- + + + +[[webflux-client-exchange]] +=== Exchange + +The `exchange()` method provides more control. The below example is equivalent +to `retrieve()` but with access to the `ClientResponse`: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Mono result = client.get() + .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) + .exchange() + .flatMap(response -> response.bodyToMono(Person.class)); +---- + +At this level you can also create a full `ResponseEntity`: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Mono> result = client.get() + .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) + .exchange() + .flatMap(response -> response.bodyToEntity(Person.class)); +---- + +Note that unlike `retrieve()`, with `exchange()` there are no automatic error signals for +4xx and 5xx responses. You have to check the status code and decide how to proceed. + +[CAUTION] +==== +When you use `exchange()`, you must call `response.close()` if you do not intend to read +the response body in order to close the underlying HTTP connection. Not doing so can +result in connection pool inconsistencies or memory leaks. + +You do not have to call `response.close()` if you consume the body because forcing a +connection to be closed negates the benefits of persistent connections and connection +pooling. +==== + + +[[webflux-client-body]] +=== Request body + +The request body can be encoded from an Object: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Mono personMono = ... ; + + Mono result = client.post() + .uri("/persons/{id}", id) + .contentType(MediaType.APPLICATION_JSON) + .body(personMono, Person.class) + .retrieve() + .bodyToMono(Void.class); +---- + +You can also encode from a stream of objects: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Flux personFlux = ... ; + + Mono result = client.post() + .uri("/persons/{id}", id) + .contentType(MediaType.APPLICATION_STREAM_JSON) + .body(personFlux, Person.class) + .retrieve() + .bodyToMono(Void.class); +---- + +Or if you have the actual value, use the `syncBody` shortcut: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Person person = ... ; + + Mono result = client.post() + .uri("/persons/{id}", id) + .contentType(MediaType.APPLICATION_JSON) + .syncBody(person) + .retrieve() + .bodyToMono(Void.class); +---- + + +[[webflux-client-builder]] +=== Builder options + +A simple way to create `WebClient` is through the static factory methods `create()` and +`create(String)` with a base URL for all requests. You can also use `WebClient.builder()` +for further options. + +To customize options of the underlying HTTP client: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + SslContext sslContext = ... + + ClientHttpConnector connector = new ReactorClientHttpConnector( + builder -> builder.sslContext(sslContext)); + + WebClient webClient = WebClient.builder() + .clientConnector(connector) + .build(); +---- + +To customize <> used for encoding and decoding: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + ExchangeStrategies strategies = ExchangeStrategies.builder() + .codecs(configurer -> { + // ... + }) + .build(); + + WebClient webClient = WebClient.builder() + .exchangeStrategies(strategies) + .build(); + +---- + +The builder can be used to insert <>. + +You can also customize URI building, set default headers, cookies, and more. Explore +the `WebClient.Builder` in your IDE. + +Note that you can also obtain a builder from an already existing `WebClient` instance +and create a modified version without affecting the original instance: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + WebClient modifiedClient = client.mutate() + // user builder methods... + .build(); + +---- + + + + + +[[webflux-client-filter]] +=== Filters + +`WebClient` supports interception style request filtering: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + WebClient client = WebClient.builder() + .filter((request, next) -> { + + ClientRequest filtered = ClientRequest.from(request) + .header("foo", "bar") + .build(); + + return next.exchange(filtered); + }) + .build(); +---- + +`ExchangeFilterFunctions` provides a filter for basic authentication: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + +// static import of ExchangeFilterFunctions.basicAuthentication + + WebClient client = WebClient.builder() + .filter(basicAuthentication("user", "pwd")) + .build(); +---- + +You can also mutate an existing `WebClient` instance without affecting the original: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + WebClient filteredClient = client.mutate() + .filter(basicAuthentication("user", "pwd") + .build(); +---- + + + + +[[webflux-reactive-libraries]] +== Reactive Libraries + +Reactor is a required dependency for the `spring-webflux` module and is used internally +for composing logic and for Reactive Streams support. An easy rule to remember is that +WebFlux APIs return `Flux` or `Mono` -- since that's what's used internally, and +leniently accept any Reactive Streams `Publisher` implementation. + +The use of `Flux` and `Mono` helps to express cardinality -- e.g. +whether a single or multiple async values are expected. This is important for API design +but also essential in some cases, e.g. when encoding an HTTP message. + +For annotated controllers, WebFlux adapts transparently to the reactive library in use +with proper translation of cardinality. This is done with the help of the +{api-spring-framework}/core/ReactiveAdapterRegistry.html[ReactiveAdapterRegistry] from +`spring-core` which provides pluggable support for reactive and async types. The registry +has built-in support for RxJava and `CompletableFuture` but others can be registered. + +For functional endpoints and other functional APIs such as the `WebClient`: + +* `Flux` or `Mono` for output -- use them to compose logic or pass to any Reactive +Streams library (both are `Publisher` implementations). +* `Publisher` for input -- if a `Publisher` from another reactive library is provided +it can only be treated as a stream with unknown semantics (0..N). If the semantics are +known -- e.g. `io.reactivex.Single`, you can use `Mono.from(Publisher)` and pass that +in instead of the raw `Publisher`. + +[NOTE] +==== +For example, given a `Publisher` that is not a `Mono`, the Jackson JSON message writer +expects multiple values. If the media type implies an infinite stream -- e.g. +`"application/json+stream"`, values are written and flushed individually; otherwise +values are buffered into a list and rendered as a JSON array. +==== -* https://github.com/poutsma/web-function-sample[Functional programming model sample] -* https://github.com/sdeleuze/spring-reactive-playground[Spring Reactive Playground]: playground for most Spring Web reactive features -* https://github.com/reactor/projectreactor.io/tree/spring-functional[Reactor website]: the `spring-functional` branch is a Spring 5 functional, Java 8 lambda-style application -* https://github.com/bclozel/spring-reactive-university[Spring Reactive University session]: live-coded project from https://www.youtube.com/watch?v=Cj4foJzPF80[this Devoxx BE 2106 university talk] -* https://github.com/thymeleaf/thymeleafsandbox-biglist-reactive[Reactive Thymeleaf Sandbox] -* https://github.com/mixitconf/mixit/[MiXiT website]: Boot + Kotlin + Reactive + Functional web registration API application -* https://github.com/sdeleuze/spring-kotlin-functional[Spring Kotlin Functional]: Standalone WebFlux application with functional web and bean DSLs -* https://github.com/simonbasle/reactor-by-example[Reactor by example]: code snippets coming from this https://www.infoq.com/articles/reactor-by-example[InfoQ article] -* https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation[Spring integration tests]: various features tested with Reactor https://projectreactor.io/docs/test/release/api/index.html?reactor/test/StepVerifier.html[`StepVerifier`] diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 3291294672..b6bd381d0d 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -1,5 +1,5 @@ [[mvc]] -= Spring Web MVC framework += Spring Web MVC :doc-spring-security: {doc-root}/spring-security/site/docs/current/reference [[mvc-introduction]] @@ -13,22 +13,19 @@ but it is more commonly known as "Spring MVC". Parallel to Spring Web MVC, Spring Framework 5.0 introduced a reactive stack, web framework whose name Spring WebFlux is also based on its source module https://github.com/spring-projects/spring-framework/tree/master/spring-webflux[spring-webflux]. - -This section covers Spring Web MVC. The <> +This section covers Spring Web MVC. The <> covers Spring WebFlux. [[mvc-servlet]] == The DispatcherServlet +[.small]#<># Spring MVC, like many other web frameworks, is designed around the front controller -pattern where a central `Servlet`, the `DispatcherServlet`, dispatches incoming -requests to registered handlers for request processing. - -`DispatcherServlet` provides a shared algorithm for request processing while -actual work is performed by configurable, delegate components. This model is very -flexible and supports diverse workflows. +pattern where a central `Servlet`, the `DispatcherServlet`, provides a shared algorithm +for request processing while actual work is performed by configurable, delegate components. +This model is flexible and supports diverse workflows. The `DispatcherServlet`, as any `Servlet`, needs to be declared and mapped according to the Servlet specification using Java configuration or in `web.xml`. @@ -202,6 +199,7 @@ And the `web.xml` equivalent: [[mvc-servlet-special-bean-types]] === Special Bean Types In the WebApplicationContext +[.small]#<># The `DispatcherServlet` delegates to special beans to process requests and render the appropriate responses. By "special beans" we mean Spring-managed Object instances that @@ -273,6 +271,8 @@ provides many extra convenient options on top. [[mvc-servlet-sequence]] === DispatcherServlet Processing Sequence +[.small]#<># + The `DispatcherServlet` processes requests as follows: * The `WebApplicationContext` is searched for and bound in the request as an attribute @@ -337,29 +337,29 @@ initialization parameters ( `init-param` elements) to the Servlet declaration in [[mvc-controller]] == Annotated Controllers -Spring MVC provides an annotation-based programming model where `@Controller` components -use annotations to express request mappings, to bind request input to controller method -arguments, to declare exception handling, and much more. Here is a basic example: +[.small]#<># + +Spring MVC provides an annotation-based programming model where `@Controller` and +`@RestController` components use annotations to express request mappings, request input, +exception handling, and more. Annotated controllers have flexible method signatures and +do not have to extend base classes nor implement specific interfaces. [source,java,indent=0] [subs="verbatim,quotes"] ---- @Controller - public class HelloWorldController { + public class HelloController { - @GetMapping("/helloWorld") - public String helloWorld(Model model) { + @GetMapping("/hello") + public String handle(Model model) { model.addAttribute("message", "Hello World!"); return "index"; } } ---- -Annotated controllers have flexible method signatures and do not have to extend base -classes or implement specific interfaces. They do not need to have direct dependencies -on Servlet APIs either. In this particular example the method accepts a `Model` and -returns a view name as a `String` but many other options exist and are explained further -below in this chapter. +In this particular example the method accepts a `Model` and returns a view name as a `String` +but many other options exist and are explained further below in this chapter. [TIP] ==== @@ -371,6 +371,7 @@ programming model described in this section. [[mvc-ann-controller]] === Defining a controller with @Controller +[.small]#<># You can define controller beans using a standard Spring bean definition in the Servlet's `WebApplicationContext`. The `@Controller` stereotype allows for auto-detection, @@ -415,111 +416,26 @@ The XML configuration equivalent: ---- +[NOTE] +==== +`@RestController` is a composed annotation that is itself annotated with `@Controller` and +`@ResponseBody` indicating a controller whose every method inherits the type-level +`@ResponseBody` annotation and therefore writes to the response body (vs model-and-vew +rendering). +==== + [[mvc-ann-requestmapping]] === Mapping Requests With @RequestMapping +[.small]#<># -The `@RequestMapping` annotation is used to map URLs such as `/appointments` onto an -entire class or a particular handler method. A class-level annotation can express -mappings shared across all controller methods such as a URL prefix. Each controller -method then can complete the URL mapping and for example narrow down to a specific -HTTP method such as "GET", "POST", etc. +The `@RequestMapping` annotation is used to map requests to controllers methods. It has +various attributes to match by URL, HTTP method, request parameters, headers, and media +types. It can be used at the class-level to express shared mappings or at the method level +to narrow down to a specific endpoint mapping. -The following example from the __Petcare__ sample shows a controller in a Spring MVC -application that uses this annotation: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @Controller - **@RequestMapping("/appointments")** - public class AppointmentsController { - - private final AppointmentBook appointmentBook; - - @Autowired - public AppointmentsController(AppointmentBook appointmentBook) { - this.appointmentBook = appointmentBook; - } - - **@RequestMapping(method = RequestMethod.GET)** - public Map get() { - return appointmentBook.getAppointmentsForToday(); - } - - **@RequestMapping(path = "/{day}", method = RequestMethod.GET)** - public Map getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { - return appointmentBook.getAppointmentsForDay(day); - } - - **@RequestMapping(path = "/new", method = RequestMethod.GET)** - public AppointmentForm getNewForm() { - return new AppointmentForm(); - } - - **@RequestMapping(method = RequestMethod.POST)** - public String add(@Valid AppointmentForm appointment, BindingResult result) { - if (result.hasErrors()) { - return "appointments/new"; - } - appointmentBook.addAppointment(appointment); - return "redirect:/appointments"; - } - } ----- - -In the above example, `@RequestMapping` is used in a number of places. The first usage is -on the type (class) level, which indicates that all handler methods in this controller -are relative to the `/appointments` path. The `get()` method has a further -`@GetMapping` refinement: it only accepts `GET` requests, meaning that an HTTP `GET` for -`/appointments` invokes this method. The `add()` has a similar refinement, and the -`getNewForm()` combines the definition of HTTP method and path into one, so that `GET` -requests for `appointments/new` are handled by that method. - -The `getForDay()` method shows another usage of `@RequestMapping`: URI templates. (See -<>). - -A `@RequestMapping` on the class level is not required. Without it, all paths are simply -absolute, and not relative to it. The following example from the __PetClinic__ sample -application shows a multi-action controller using `@RequestMapping`: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @Controller - public class ClinicController { - - private final Clinic clinic; - - @Autowired - public ClinicController(Clinic clinic) { - this.clinic = clinic; - } - - **@RequestMapping("/")** - public void welcomeHandler() { - } - - **@RequestMapping("/vets")** - public ModelMap vetsHandler() { - return new ModelMap(this.clinic.getVets()); - } - - } ----- - -Note that the above example does not specify `GET` vs. `PUT`, `POST`, and so forth, because -`@RequestMapping` maps all HTTP methods by default. Use `@GetMapping(method=GET)` or -`@GetMapping` to narrow the mapping. - -[[mvc-ann-requestmapping-composed]] -==== Composed @RequestMapping Variants - -Spring MVC also supports _composed_ shortcut variants of the `@RequestMapping` annotation -that help to simplify mappings for common HTTP methods and better express the semantics of -the annotated handler method. For example, a `@GetMapping` can be read as a `GET` -`@RequestMapping`. +There are also HTTP method specific shortcut variants of `@RequestMapping`: - `@GetMapping` - `@PostMapping` @@ -527,32 +443,32 @@ the annotated handler method. For example, a `@GetMapping` can be read as a `GET - `@DeleteMapping` - `@PatchMapping` -The following example shows a modified version of the `AppointmentsController` from the -previous section that has been simplified with _composed_ `@RequestMapping` annotations. +The shortcut variants are +https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model#composed-annotations[composed annotations] +-- themselves annotated with `@RequestMapping`. They are commonly used at the method level. +At the class level an `@RequestMapping` is more useful for expressing shared mappings. [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Controller - **@RequestMapping("/appointments")** - public class AppointmentsController { + @RestController + @RequestMapping("/persons") + class PersonController { - // ... + @GetMapping("/{id}") + public Person getPerson(@PathVariable Long id) { + // ... + } - **@GetMapping** - public Map get() {} - - **@GetMapping("/{day}")** - public Map getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {} - - **@GetMapping("/new")** - public AppointmentForm getNewForm() {} - - **@PostMapping** - public String add(@Valid AppointmentForm appointment, BindingResult result) {} + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public void add(@RequestBody Person person) { + // ... + } } ---- + [[mvc-ann-requestmapping-proxying]] ==== @Controller and AOP Proxying @@ -565,239 +481,149 @@ callback (e.g. `InitializingBean`, `*Aware`, etc), you may need to explicitly configure class-based proxying. For example with ``, change to ``. + [[mvc-ann-requestmapping-uri-templates]] -==== URI Template Patterns -__URI templates__ can be used for convenient access to selected parts of a URL in a -`@RequestMapping` method. +==== URI Path Patterns +[.small]#<># -A URI Template is a URI-like string, containing one or more variable names. When you -substitute values for these variables, the template becomes a URI. For example -`http://www.example.com/users/{userId}` contains the variable __userId__. Assigning the -value __fred__ to the variable yields `http://www.example.com/users/fred`. +You can map requests using glob patterns and wildcards: -In Spring MVC you can use the `@PathVariable` annotation on a method argument to bind it -to the value of a URI template variable: +* `?` matches one character +* `*` matches zero or more characters within a path segment +* `**` match zero or more path segments -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @GetMapping("/owners/{ownerId}") - public String findOwner(**@PathVariable** String ownerId, Model model) { - Owner owner = ownerService.findOwner(ownerId); - model.addAttribute("owner", owner); - return "displayOwner"; - } ----- - -The URI Template ++"/owners/{ownerId}"++ specifies the variable name `ownerId`. When the -controller handles this request, the value of `ownerId` is set to the value found in the -appropriate part of the URI. For example, when a request comes in for `/owners/fred`, -the value of `ownerId` is `fred`. - -[TIP] -==== - -To process the @PathVariable annotation, Spring MVC needs to find the matching URI -template variable by name. You can specify it in the annotation: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @GetMapping("/owners/{ownerId}") - public String findOwner(**@PathVariable("ownerId")** String theOwner, Model model) { - // implementation omitted - } ----- - -Or if the URI template variable name matches the method argument name you can omit that -detail. As long as your code is compiled with debugging information or the `-parameters` -compiler flag on Java 8, Spring MVC will match the method argument name to the URI -template variable name: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @GetMapping("/owners/{ownerId}") - public String findOwner(**@PathVariable** String ownerId, Model model) { - // implementation omitted - } ----- -==== - -A method can have any number of `@PathVariable` annotations: +You can also declare URI variables and access their values with `@PathVariable`: [source,java,indent=0] [subs="verbatim,quotes"] ---- @GetMapping("/owners/{ownerId}/pets/{petId}") - public String findPet(**@PathVariable** String ownerId, **@PathVariable** String petId, Model model) { - Owner owner = ownerService.findOwner(ownerId); - Pet pet = owner.getPet(petId); - model.addAttribute("pet", pet); - return "displayPet"; - } ----- - -When a `@PathVariable` annotation is used on a `Map` argument, the map -is populated with all URI template variables. - -A URI template can be assembled from type and method level __@RequestMapping__ -annotations. As a result the `findPet()` method can be invoked with a URL such as -`/owners/42/pets/21`. - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @Controller - @RequestMapping(**"/owners/{ownerId}"**) - public class RelativePathUriTemplateController { - - @RequestMapping(**"/pets/{petId}"**) - public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { - // implementation omitted - } - - } ----- - -A `@PathVariable` argument can be of __any simple type__ such as `int`, `long`, `Date`, etc. -Spring automatically converts to the appropriate type or throws a -`TypeMismatchException` if it fails to do so. You can also register support for parsing -additional data types. See <> and <>. - - -[[mvc-ann-requestmapping-uri-templates-regex]] -==== URI Template Patterns with Regular Expressions -Sometimes you need more precision in defining URI template variables. Consider the URL -`"/spring-web/spring-web-3.0.5.jar"`. How do you break it down into multiple parts? - -The `@RequestMapping` annotation supports the use of regular expressions in URI template -variables. The syntax is `{varName:regex}` where the first part defines the variable -name and the second - the regular expression. For example: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}") - public void handle(@PathVariable String version, @PathVariable String extension) { + public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { // ... } ---- +URI variables can be declared at the class and method level: +[source,java,intent=0] +[subs="verbatim,quotes"] +---- +@Controller +@RequestMapping("/owners/{ownerId}") +public class OwnerController { -[[mvc-ann-requestmapping-patterns]] -==== Path Patterns -In addition to URI templates, the `@RequestMapping` annotation and all _composed_ -`@RequestMapping` variants also support Ant-style path patterns (for example, -`/myPath/{asterisk}.do`). A combination of URI template variables and Ant-style globs is -also supported (e.g. `/owners/{asterisk}/pets/{petId}`). + @GetMapping("/pets/{petId}") + public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { + // ... + } +} +---- + +URI variables are automatically converted to the appropriate type or`TypeMismatchException` +is raised. Simple types -- `int`, `long`, `Date`, are supported by default and you can +register support for any other data type. +See <> and <>. + +URI variables can be named explicitly -- e.g. `@PathVariable("customId")`, but you can +leave that detail out if the names are the same and your code is compiled with debugging +information or with the `-parameters` compiler flag on Java 8. + +The syntax `{varName:regex}` declares a URI variable with a regular expressions with the +syntax `{varName:regex}` -- e.g. given URL `"/spring-web-3.0.5 .jar"`, the below method +extracts the name, version, and file extension: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") + public void handle(@PathVariable String version, @PathVariable String ext) { + // ... + } +---- + +URI path patterns can also have embedded `${...}` placeholders that are resolved on startup + via `PropertyPlaceHolderConfigurer` against local, system, environment, and other property +sources. This can be used for example to parameterize a base URL based on some external +configuration. + +[NOTE] +==== +Spring MVC uses the `PathMatcher` contract and the `AntPathMatcher` implementation from +`spring-core` for URI path matching. +==== [[mvc-ann-requestmapping-pattern-comparison]] ==== Path Pattern Comparison -When a URL matches multiple patterns, a sort is used to find the most specific match. +[.small]#<># -A pattern with a lower count of URI variables and wild cards is considered more specific. -For example `/hotels/{hotel}/{asterisk}` has 1 URI variable and 1 wild card and is considered -more specific than `/hotels/{hotel}/{asterisk}{asterisk}` which as 1 URI variable and 2 wild cards. +When multiple patterns match a URL, they must be compared to find the best match. This done +via `AntPathMatcher.getPatternComparator(String path)` which looks for patterns that more +specific. -If two patterns have the same count, the one that is longer is considered more specific. -For example `/foo/bar{asterisk}` is longer and considered more specific than `/foo/{asterisk}`. +A pattern is less specific if it has a lower count of URI variables and single wildcards +counted as 1 and double wildcards counted as 2. Given an equal score, the longer pattern is +chosen. Given the same score and length, the pattern with more URI variables than wildcards +is chosen. -When two patterns have the same count and length, the pattern with fewer wild cards is considered more specific. -For example `/hotels/{hotel}` is more specific than `/hotels/{asterisk}`. +The default mapping pattern `/{asterisk}{asterisk}` is excluded from scoring and always +sorted last. Also prefix patterns such as `/public/{asterisk}{asterisk}` are considered less +specific than other pattern that don't have double wildcards. -There are also some additional special rules: - -* The *default mapping pattern* `/{asterisk}{asterisk}` is less specific than any other pattern. -For example `/api/{a}/{b}/{c}` is more specific. -* A *prefix pattern* such as `/public/{asterisk}{asterisk}` is less specific than any other pattern that doesn't contain double wildcards. -For example `/public/path3/{a}/{b}/{c}` is more specific. - -For the full details see `AntPatternComparator` in `AntPathMatcher`. Note that the PathMatcher -can be customized (see <> in the section on configuring Spring MVC). - - -[[mvc-ann-requestmapping-placeholders]] -==== Path Patterns with Placeholders -Patterns in `@RequestMapping` annotations support `${...}` placeholders against local -properties and/or system properties and environment variables. This may be useful in -cases where the path a controller is mapped to may need to be customized through -configuration. For more information on placeholders, see the javadocs of the -`PropertyPlaceholderConfigurer` class. +For the full details see `AntPatternComparator` in `AntPathMatcher` and also keep mind that +the `PathMatcher` implementation used can be customized. See <> +in the configuration section. [[mvc-ann-requestmapping-suffix-pattern-match]] ==== Suffix Pattern Matching + By default Spring MVC performs `".{asterisk}"` suffix pattern matching so that a controller mapped to `/person` is also implicitly mapped to `/person.{asterisk}`. -This makes it easy to request different representations of a resource through the -URL path (e.g. `/person.pdf`, `/person.xml`). +This is used for URL based content negotiation, e.g. `/person.pdf`, `/person.xml`, etc. -Suffix pattern matching can be turned off or restricted to a set of path extensions -explicitly registered for content negotiation purposes. This is generally -recommended to minimize ambiguity with common request mappings such as -`/person/{id}` where a dot might not represent a file extension, e.g. -`/person/joe@email.com` vs `/person/joe@email.com.json`. Furthermore as explained -in the note below suffix pattern matching as well as content negotiation may be -used in some circumstances to attempt malicious attacks and there are good -reasons to restrict them meaningfully. +Suffix pattern matching was quite helpful when browsers used to send Accept headers that +are hard to interpet consistently. In the present, and for REST services, the `Accept` +header should be the preferred choice. -See <> for suffix pattern matching configuration and -also <> for content negotiation configuration. +Suffix patterns can cause ambiguity and complexity in combination with path parameters, +encoded characters, and URI variables. It also makes it harder to reason about URL-based +authorization rules and security (see <>). +Suffix pattern matching can be turned off completely or restricted to a set of explicitly +registered path extensions. We strongly recommend using of one those options. See +<> and <>. If you need URL based +content negotiation consider using query parameters instead. [[mvc-ann-requestmapping-rfd]] ==== Suffix Pattern Matching and RFD -Reflected file download (RFD) attack was first described in a -https://www.trustwave.com/Resources/SpiderLabs-Blog/Reflected-File-Download---A-New-Web-Attack-Vector/[paper by Trustwave] -in 2014. The attack is similar to XSS in that it relies on input -(e.g. query parameter, URI variable) being reflected in the response. -However instead of inserting JavaScript into HTML, an RFD attack relies on the -browser switching to perform a download and treating the response as an executable -script if double-clicked based on the file extension (e.g. .bat, .cmd). +Reflected file download (RFD) attack is similar to XSS in that it relies on request input, +e.g. query parameter, URI variable, being reflected in the response. However instead of +inserting JavaScript into HTML, an RFD attack relies on the browser switching to perform a +download and treating the response as an executable script when double-clicked later. In Spring MVC `@ResponseBody` and `ResponseEntity` methods are at risk because -they can render different content types which clients can request including -via URL path extensions. Note however that neither disabling suffix pattern matching -nor disabling the use of path extensions for content negotiation purposes alone -are effective at preventing RFD attacks. +they can render different content types which clients can request via URL path extensions. +Disabling suffix pattern matching and the use of path extensions for content negotiation +lower the risk but are not sufficient to prevent RFD attacks. -For comprehensive protection against RFD, prior to rendering the response body -Spring MVC adds a `Content-Disposition:inline;filename=f.txt` header to -suggest a fixed and safe download file. This is done only if the URL -path contains a file extension that is neither whitelisted nor explicitly -registered for content negotiation purposes. However it may potentially have +To prevent RFD attacks, prior to rendering the response body Spring MVC adds a +`Content-Disposition:inline;filename=f.txt` header to suggest a fixed and safe download +file. This is done only if the URL path contains a file extension that is neither whitelisted +nor explicitly registered for content negotiation purposes. However it may potentially have side effects when URLs are typed directly into a browser. -Many common path extensions are whitelisted by -default. Furthermore REST API calls are typically not meant to be used as URLs -directly in browsers. Nevertheless applications that use custom -`HttpMessageConverter` implementations can explicitly register file extensions -for content negotiation and the Content-Disposition header will not be added -for such extensions. See <>. - -[NOTE] -==== -This was originally introduced as part of work for -http://pivotal.io/security/cve-2015-5211[CVE-2015-5211]. -Below are additional recommendations from the report: - -* Encode rather than escape JSON responses. This is also an OWASP XSS recommendation. - For an example of how to do that with Spring see https://github.com/rwinch/spring-jackson-owasp[spring-jackson-owasp]. -* Configure suffix pattern matching to be turned off or restricted to explicitly - registered suffixes only. -* Configure content negotiation with the properties "useJaf" and "ignoreUnknownPathExtensions" - set to false which would result in a 406 response for URLs with unknown extensions. - Note however that this may not be an option if URLs are naturally expected to have - a dot towards the end. -* Add `X-Content-Type-Options: nosniff` header to responses. Spring Security 4 does - this by default. -==== +Many common path extensions are whitelisted by default. Applications with custom +`HttpMessageConverter` implementations can explicitly register file extensions for content +negotiation to avoid having a `Content-Disposition` header added for those extensions. +See <>. +Check http://pivotal.io/security/cve-2015-5211[CVE-2015-5211] for additional +recommendations related to RFD. @@ -927,334 +753,331 @@ to `false`. [[mvc-ann-requestmapping-consumes]] ==== Consumable Media Types -You can narrow the primary mapping by specifying a list of consumable media types. The -request will be matched only if the `Content-Type` request header matches the specified -media type. For example: +[.small]#<># + +You can narrow the request mapping based on the `Content-Type` of the request: [source,java,indent=0] [subs="verbatim,quotes"] ---- @PostMapping(path = "/pets", **consumes = "application/json"**) - public void addPet(@RequestBody Pet pet, Model model) { - // implementation omitted + public void addPet(@RequestBody Pet pet) { + // ... } ---- -Consumable media type expressions can also be negated as in `!text/plain` to match to -all requests other than those with `Content-Type` of `text/plain`. Also consider -using constants provided in `MediaType` such as `APPLICATION_JSON_VALUE` and -`APPLICATION_JSON_UTF8_VALUE`. +The consumes attribute also supports negation expressions -- e.g. `!text/plain` means any +content type other than "text/plain". + +You can declare a shared consumes attribute at the class level. Unlike most other request +mapping attributes however when used at the class level, a method-level consumes attribute +will overrides rather than extend the class level declaration. [TIP] ==== - -The __consumes__ condition is supported on the type and on the method level. Unlike most -other conditions, when used at the type level, method-level consumable types override -rather than extend type-level consumable types. +`MediaType` provides constants for commonly used media types -- e.g. +`APPLICATION_JSON_VALUE`, `APPLICATION_JSON_UTF8_VALUE`. ==== [[mvc-ann-requestmapping-produces]] ==== Producible Media Types -You can narrow the primary mapping by specifying a list of producible media types. The -request will be matched only if the `Accept` request header matches one of these -values. Furthermore, use of the __produces__ condition ensures the actual content type -used to generate the response respects the media types specified in the __produces__ -condition. For example: +[.small]#<># + +You can narrow the request mapping based on the `Accept` request header and the list of +content types that a controller method produces: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @GetMapping(path = "/pets/{petId}", **produces = MediaType.APPLICATION_JSON_UTF8_VALUE**) + @GetMapping(path = "/pets/{petId}", **produces = "application/json;charset=UTF-8"**) @ResponseBody - public Pet getPet(@PathVariable String petId, Model model) { - // implementation omitted + public Pet getPet(@PathVariable String petId) { + // ... } ---- -[NOTE] -==== -Be aware that the media type specified in the __produces__ condition can also optionally -specify a character set. For example, in the code snippet above we specify the same media -type than the default one configured in `MappingJackson2HttpMessageConverter`, including -the `UTF-8` charset. -==== +The media type can specify a character set. Negated expressions are supported -- e.g. +`!text/plain` means any content type other than "text/plain". -Just like with __consumes__, producible media type expressions can be negated as in -`!text/plain` to match to all requests other than those with an `Accept` header -value of `text/plain`. Also consider using constants provided in `MediaType` such -as `APPLICATION_JSON_VALUE` and `APPLICATION_JSON_UTF8_VALUE`. +You can declare a shared produces attribute at the class level. Unlike most other request +mapping attributes however when used at the class level, a method-level produces attribute +will overrides rather than extend the class level declaration. [TIP] ==== -The __produces__ condition is supported on the type and on the method level. Unlike most -other conditions, when used at the type level, method-level producible types override -rather than extend type-level producible types. +`MediaType` provides constants for commonly used media types -- e.g. +`APPLICATION_JSON_VALUE`, `APPLICATION_JSON_UTF8_VALUE`. ==== [[mvc-ann-requestmapping-params-and-headers]] ==== Request Parameters and Header Values -You can narrow request matching through request parameter conditions such as -`"myParam"`, `"!myParam"`, or `"myParam=myValue"`. The first two test for request -parameter presence/absence and the third for a specific parameter value. Here is an -example with a request parameter value condition: +[.small]#<># + +You can narrow request mappings based on request parameter conditions. You can test for the +presence of a request parameter (`"myParam"`), for the absence (`"!myParam"`), or for a +specific value (`"myParam=myValue"`): [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Controller - @RequestMapping("/owners/{ownerId}") - public class RelativePathUriTemplateController { - - @GetMapping(path = "/pets/{petId}", **params = "myParam=myValue"**) - public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { - // implementation omitted - } - + @GetMapping(path = "/pets/{petId}", **params = "myParam=myValue"**) + public void findPet(@PathVariable String petId) { + // ... } ---- -The same can be done to test for request header presence/absence or to match based on a -specific request header value: +You can also use the same with request header conditions: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Controller - @RequestMapping("/owners/{ownerId}") - public class RelativePathUriTemplateController { - - @GetMapping(path = "/pets", **headers = "myHeader=myValue"**) - public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { - // implementation omitted - } - + @GetMapping(path = "/pets", **headers = "myHeader=myValue"**) + public void findPet(@PathVariable String petId) { + // ... } ---- [TIP] ==== - -Although you can match to __Content-Type__ and __Accept__ header values using media type -wild cards (for example __"content-type=text/*"__ will match to __"text/plain"__ and -__"text/html"__), it is recommended to use the __consumes__ and __produces__ conditions -respectively instead. They are intended specifically for that purpose. +You can match `Content-Type` and `Accept` with the headers condition but it is better to use +<> and <> +instead. ==== [[mvc-ann-requestmapping-head-options]] -==== HTTP HEAD and HTTP OPTIONS +==== HTTP HEAD and OPTIONS +[.small]#<># -`@RequestMapping` methods mapped to "GET" are also implicitly mapped to "HEAD", -i.e. there is no need to have "HEAD" explicitly declared. An HTTP HEAD request -is processed as if it were an HTTP GET except instead of writing the body only -the number of bytes are counted and the "Content-Length" header set. +`@GetMapping` -- and also `@RequestMapping(method=HttpMethod.GET)`, are implicitly mapped to +and also support HTTP HEAD. An HTTP HEAD request is processed as if it were HTTP GET except +but instead of writing the body, the number of bytes are counted and the "Content-Length" +header set. -`@RequestMapping` methods have built-in support for HTTP OPTIONS. By default an -HTTP OPTIONS request is handled by setting the "Allow" response header to the -HTTP methods explicitly declared on all `@RequestMapping` methods with matching -URL patterns. When no HTTP methods are explicitly declared the "Allow" header -is set to "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS". Ideally always declare the -HTTP method(s) that an `@RequestMapping` method is intended to handle, or alternatively -use one of the dedicated _composed_ `@RequestMapping` variants (see -<>). +By default HTTP OPTIONS is handled by setting the "Allow" response header to the list of HTTP +methods listed in all `@RequestMapping` methods with matching URL patterns. -Although not necessary an `@RequestMapping` method can be mapped to and handle -either HTTP HEAD or HTTP OPTIONS, or both. +For a `@RequestMapping` without HTTP method declarations, the "Allow" header is set to +`"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"`. Controller methods should always declare the +supported HTTP methods for example by using the HTTP method specific variants -- +`@GetMapping`, `@PostMapping`, etc. +`@RequestMapping` method can be explicitly mapped to HTTP HEAD and HTTP OPTIONS, but that +is not necessary in the common case. [[mvc-ann-methods]] -=== Defining @RequestMapping handler methods - -`@RequestMapping` handler methods can have very flexible signatures. The supported -method arguments and return values are described in the following section. Most -arguments can be used in arbitrary order with the only exception being `BindingResult` -arguments. This is described in the next section. - -[NOTE] -==== -Spring 3.1 introduced a new set of support classes for `@RequestMapping` methods called -`RequestMappingHandlerMapping` and `RequestMappingHandlerAdapter` respectively. They are -recommended for use and even required to take advantage of new features in Spring MVC -3.1 and going forward. The new support classes are enabled by default from the MVC -namespace and with use of the MVC Java config but must be configured explicitly if using -neither. -==== +=== Defining @RequestMapping methods +[.small]#<># +`@RequestMapping` handler methods have a flexible signature and can choose from a range of +supported controller method arguments and return values. [[mvc-ann-arguments]] -==== Supported method argument types -The following are the supported method arguments: +==== Supported Controller Method Arguments +[.small]#<># -* `org.springframework.web.context.request.WebRequest` or - `org.springframework.web.context.request.NativeWebRequest`. Allows for generic - request parameter access as well as request/session attribute access, without ties - to the native Servlet API. -* Request or response objects (Servlet API). Choose any specific request or response - type, for example `ServletRequest` or `HttpServletRequest` or Spring's - `MultipartRequest`/`MultipartHttpServletRequest`. -* Session object (Servlet API) of type `HttpSession`. An argument of this type enforces - the presence of a corresponding session. As a consequence, such an argument is never - `null`. +The table below shows supported controller method arguments. Reactive types are not supported +for any arguments. -[NOTE] -==== -Session access may not be thread-safe, in particular in a Servlet environment. Consider -setting the ``RequestMappingHandlerAdapter``'s "synchronizeOnSession" flag to "true" if -multiple requests are allowed to access a session concurrently. -==== +JDK 1.8's `java.util.Optional` is supported as a method argument in combination with +annotations that have a `required` attribute -- e.g. `@RequestParam`, `@RequestHeader`, +etc, and is equivalent to `required=false`. -* `java.servlet.http.PushBuilder` for the associated Servlet 4.0 push builder API, - allowing for programmatic HTTP/2 resource pushes. -* `java.security.Principal` (or a specific `Principal` implementation class if known), - containing the currently authenticated user. -* `org.springframework.http.HttpMethod` for the HTTP request method, represented as - Spring's `HttpMethod` enum. -* `java.util.Locale` for the current request locale, determined by the most specific - locale resolver available, in effect, the configured `LocaleResolver` / - `LocaleContextResolver` in an MVC environment. -* `java.util.TimeZone` (Java 6+) / `java.time.ZoneId` (Java 8+) for the time zone - associated with the current request, as determined by a `LocaleContextResolver`. -* `java.io.InputStream` / `java.io.Reader` for access to the request's content. - This value is the raw InputStream/Reader as exposed by the Servlet API. -* `java.io.OutputStream` / `java.io.Writer` for generating the response's content. - This value is the raw OutputStream/Writer as exposed by the Servlet API. -* `@PathVariable` annotated parameters for access to URI template variables. See - <>. -* `@MatrixVariable` annotated parameters for access to name-value pairs located in - URI path segments. See <>. -* `@RequestParam` annotated parameters for access to specific Servlet request - parameters. Parameter values are converted to the declared method argument type. - See <>. -* `@RequestHeader` annotated parameters for access to specific Servlet request HTTP - headers. Parameter values are converted to the declared method argument type. - See <>. -* `@RequestBody` annotated parameters for access to the HTTP request body. Parameter - values are converted to the declared method argument type using - ``HttpMessageConverter``s. See <>. -* `@RequestPart` annotated parameters for access to the content of a - "multipart/form-data" request part. See <> and - <>. -* `@SessionAttribute` annotated parameters for access to existing, permanent - session attributes (e.g. user authentication object) as opposed to model - attributes temporarily stored in the session as part of a controller workflow - via `@SessionAttributes`. -* `@RequestAttribute` annotated parameters for access to request attributes. -* `HttpEntity` parameters for access to the Servlet request HTTP headers and - contents. The request stream will be converted to the entity body using - ``HttpMessageConverter``s. See <>. -* `java.util.Map` / `org.springframework.ui.Model` / `org.springframework.ui.ModelMap` - for enriching the implicit model that is exposed to the web view. -* `org.springframework.web.servlet.mvc.support.RedirectAttributes` to specify the exact - set of attributes to use in case of a redirect and also to add flash attributes - (attributes stored temporarily on the server-side to make them available to the - request after the redirect). See <> and - <>. -* Command or form objects to bind request parameters to bean properties (via setters) - or directly to fields, with customizable type conversion, depending on `@InitBinder` - methods and/or the HandlerAdapter configuration. See the `webBindingInitializer` - property on `RequestMappingHandlerAdapter`. Such command objects along with their - validation results will be exposed as model attributes by default, using the command - class name - e.g. model attribute "orderAddress" for a command object of type - "some.package.OrderAddress". The `ModelAttribute` annotation can be used on a method - argument to customize the model attribute name used. -* `org.springframework.validation.Errors` / - `org.springframework.validation.BindingResult` validation results for a preceding - command or form object (the immediately preceding method argument). -* `org.springframework.web.bind.support.SessionStatus` status handle for marking form - processing as complete, which triggers the cleanup of session attributes that have - been indicated by the `@SessionAttributes` annotation at the handler type level. -* `org.springframework.web.util.UriComponentsBuilder` a builder for preparing a URL - relative to the current request's host, port, scheme, context path, and the literal - part of the servlet mapping. +[cols="1,2", options="header"] +|=== +|Controller method argument|Description -The `Errors` or `BindingResult` parameters have to follow the model object that is being -bound immediately as the method signature might have more than one model object and -Spring will create a separate `BindingResult` instance for each of them so the following -sample won't work: +|`WebRequest`, `NativeWebRequest` +|Generic access to request parameters, request & session attributes, without direct +use of the Servlet API. -.Invalid ordering of BindingResult and @ModelAttribute -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @PostMapping - public String processSubmit(**@ModelAttribute("pet") Pet pet**, Model model, **BindingResult result**) { ... } ----- +|`javax.servlet.ServletRequest`, `javax.servlet.ServletResponse` +|Choose any specific request or response type -- e.g. `ServletRequest`, `HttpServletRequest`, +or Spring's `MultipartRequest`, `MultipartHttpServletRequest`. -Note, that there is a `Model` parameter in between `Pet` and `BindingResult`. To get -this working you have to reorder the parameters as follows: +|`javax.servlet.http.HttpSession` +|Enforces the presence of a session. As a consequence, such an argument is never `null`. + +**Note:** Session access is not thread-safe. Consider setting the +``RequestMappingHandlerAdapter``'s "synchronizeOnSession" flag to "true" if multiple +requests are allowed to access a session concurrently. -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @PostMapping - public String processSubmit(**@ModelAttribute("pet") Pet pet**, **BindingResult result**, Model model) { ... } ----- +|`javax.servlet.http.PushBuilder` +|Servlet 4.0 push builder API for programmatic HTTP/2 resource pushes. -[NOTE] -==== -JDK 1.8's `java.util.Optional` is supported as a method parameter type with annotations -that have a `required` attribute (e.g. `@RequestParam`, `@RequestHeader`, etc). The use -of `java.util.Optional` in those cases is equivalent to having `required=false`. -==== +|`java.security.Principal` +|Currently authenticated user; possibly a specific `Principal` implementation class if known. +|`HttpMethod` +|The HTTP method of the request. + +|`java.util.Locale` +|The current request locale, determined by the most specific `LocaleResolver` available, in +effect, the configured `LocaleResolver`/`LocaleContextResolver`. + +|Java 6+: `java.util.TimeZone` + +Java 8+: `java.time.ZoneId` +|The time zone associated with the current request, as determined by a `LocaleContextResolver`. + +|`java.io.InputStream`, `java.io.Reader` +|For access to the raw request body as exposed by the Servlet API. + +|`java.io.OutputStream`, `java.io.Writer` +|For access to the raw response body as exposed by the Servlet API. + +|`@PathVariable` +|For access to URI template variables. See <>. + +|`@MatrixVariable` +|For access to name-value pairs in URI path segments. See <>. + +|`@RequestParam` +|For access to Servlet request parameters. Parameter values are converted to the declared +method argument type. See <>. + +|`@RequestHeader` +|For access to request headers. Header values are converted to the declared method argument +type. See <>. + +|`@RequestBody` +|For access to the HTTP request body. Body content is converted to the declared method +argument type using ``HttpMessageConverter``s. See <>. + +|`HttpEntity` +|For access to request headers and body. The body is converted with ``HttpMessageConverter``s. +See <>. + +|`@RequestPart` +|For access to a part in a "multipart/form-data" request. +See <> and <>. + +|`java.util.Map`, `org.springframework.ui.Model`, `org.springframework.ui.ModelMap` +|For access and updates of the implicit model that is exposed to the web view. + +|`RedirectAttributes` +|Specify attributes to use in case of a redirect -- i.e. to be appended to the query +string, and/or flash attributes to be stored temporarily until the request after redirect. +See <> and <>. + +|Command or form object (with optional `@ModelAttribute`) +|Command object whose properties to bind to request parameters -- via setters or directly to +fields, with customizable type conversion, depending on `@InitBinder` methods and/or the +HandlerAdapter configuration (see the `webBindingInitializer` property on +`RequestMappingHandlerAdapter`). + +Command objects along with their validation results are exposed as model attributes, by +default using the command class name - e.g. model attribute "orderAddress" for a command +object of type "some.package.OrderAddress". `@ModelAttribute` can be used to customize the +model attribute name. + +|`Errors`, `BindingResult` +|Validation results for the command/form object data binding; this argument must be +declared immediately after the command/form object in the controller method signature. + +|`SessionStatus` +|For marking form processing complete which triggers cleanup of session attributes +declared through a class-level `@SessionAttributes` annotation. + +|`UriComponentsBuilder` +|For preparing a URL relative to the current request's host, port, scheme, context path, and +the literal part of the servlet mapping also taking into account `Forwarded` and +`X-Forwarded-*` headers. + +|`@SessionAttribute` +|For access to any session attribute; in contrast to model attributes stored in the session +as a result of a class-level `@SessionAttributes` declaration. + +|`@RequestAttribute` +|For access to request attributes. +|=== [[mvc-ann-return-types]] -==== Supported method return types -The following are the supported return types: +==== Supported Controller Method Return Values +[.small]#<># -* `ModelAndView` object (Spring MVC), providing a view, model attributes, and - optionally a response status. -* `Rendering` object (Spring WebFlux), providing a view, model attributes, and - optionally a response status. -* `Model` object, with the view name implicitly determined through a - `RequestToViewNameTranslator` and the model implicitly enriched with command objects - and the results of `@ModelAttribute` annotated reference data accessor methods. -* `Map` object for exposing a model, with the view name implicitly determined through - a `RequestToViewNameTranslator` and the model implicitly enriched with command objects - and the results of `@ModelAttribute` annotated reference data accessor methods. -* `View` object, with the model implicitly determined through command objects and - `@ModelAttribute` annotated reference data accessor methods. The handler method may - also programmatically enrich the model by declaring a `Model` argument (see above). -* `String` value that is interpreted as the logical view name, with the model - implicitly determined through command objects and `@ModelAttribute` annotated - reference data accessor methods. The handler method may also programmatically enrich - the model by declaring a `Model` argument (see above). -* `void` if the method handles the response itself (by writing the response content - directly, declaring an argument of type `ServletResponse` / `HttpServletResponse` for - that purpose) or if the view name is supposed to be implicitly determined through a - `RequestToViewNameTranslator` (not declaring a response argument in the handler method - signature). -* If the method is annotated with `@ResponseBody`, the return type is written to the - response HTTP body. The return value will be converted to the declared method argument - type using ``HttpMessageConverter``s. See <>. -* `HttpEntity` or `ResponseEntity` object to provide access to the Servlet - response HTTP headers and contents. The entity body will be converted to the response - stream using ``HttpMessageConverter``s. See <>. -* `HttpHeaders` object to return a response with no body. -* `Callable` async computation in a Spring MVC managed thread. -* `DeferredResult` async result produced later from an application managed, or any thread. -* `ListenableFuture` as an alternative equivalent to using `DeferredResult`. -* `CompletableFuture` or `CompletionStage` as an alternative equivalent to `DeferredResult`. -* `ResponseBodyEmitter` can be returned to write multiple objects to the response - asynchronously; also supported as the body within a `ResponseEntity`. -* `SseEmitter` can be returned to write Server-Sent Events to the response - asynchronously; also supported as the body within a `ResponseEntity`. -* `StreamingResponseBody` can be returned to write to the response OutputStream - asynchronously; also supported as the body within a `ResponseEntity`. -* Reactive types from Reactor 3, RxJava 2, RxJava 1 or others registered through - the configured `ReactiveAdapterRegistry` can be returned as an alternative - equivalent to using `DeferredResult` for single-valued types, or - `ResponseBodyEmitter` and `SseEmitter` for multi-valued reactive types where a streaming - media type (e.g. "text/event-stream", "application/json+stream") is requested. -* Any other return type is considered to be a single model attribute to be exposed to - the view, using the attribute name specified through `@ModelAttribute` at the method - level (or the default attribute name based on the return type class name). The model - is implicitly enriched with command objects and the results of `@ModelAttribute` - annotated reference data accessor methods. +The table below shows supported controller method return values. Reactive types are +supported for all return values, see below for more details. + +[cols="1,2", options="header"] +|=== +|Controller method return value|Description + +|`@ResponseBody` +|The return value is converted through ``HttpMessageConverter``s and written to the +response. See <>. + +|`HttpEntity`, `ResponseEntity` +|The return value specifies the full response including HTTP headers and body be converted +through ``HttpMessageConverter``s and written to the response. See <>. + +|`HttpHeaders` +|For returning a response with headers and no body. + +|`String` +|A view name to be resolved with ``ViewResolver``'s and used together with the implicit +model -- determined through command objects and `@ModelAttribute` methods. The handler +method may also programmatically enrich the model by declaring a `Model` argument (see +above). + +|`View` +|A `View` instance to use for rendering together with the implicit model -- determined +through command objects and `@ModelAttribute` methods. The handler method may also +programmatically enrich the model by declaring a `Model` argument (see above). + +|`java.util.Map`, `org.springframework.ui.Model` +|Attributes to be added to the implicit model with the view name implicitly determined +through a `RequestToViewNameTranslator`. + +|`ModelAndView` object +|The view and model attributes to use, and optionally a response status. + +|`void` +|For use in methods that declare a `ServletResponse` or `OutputStream` argument and write +to the response body; or if the view name is supposed to be implicitly determined through a +`RequestToViewNameTranslator`. + +|`Callable` +|Produce any of the above return values asynchronously in a Spring MVC managed thread. + +|`DeferredResult` +|Produce any of the above return values asynchronously from any thread -- e.g. possibly as a +result of some event or callback. + +|`ListenableFuture`, +`java.util.concurrent.CompletionStage`, +`java.util.concurrent.CompletableFuture` +|Alternative to `DeferredResult` as a convenience for example when an underlying service +returns one of those. + +|`ResponseBodyEmitter`, `SseEmitter` +|Emit a stream of objects asynchronously to be written to the response with +``HttpMessageConverter``'s; also supported as the body of a `ResponseEntity`. + +|`StreamingResponseBody` +|Write to the response `OutputStream` asynchronously; also supported as the body of a +`ResponseEntity`. + +|Reactive types -- Reactor, RxJava, or others via `ReactiveAdapterRegistry` +|Alternative to ``DeferredResult` with multi-value streams (e.g. `Flux`, `Observable`) +collected to a `List`. + +For streaming scenarios -- .e.g. `text/event-stream`, `application/json+stream`, +`SseEmitter` and `ResponseBodyEmitter` are used instead, where `ServletOutputStream` blocking +I/O is performed on a Spring MVC managed thread and back pressure applied against the +completion of each write. + +See <>. + +|Any other return type +|A single model attribute to be added to the implicit model with the view name implicitly +determined through a `RequestToViewNameTranslator`; the attribute name may be specified +through a method-level `@ModelAttribute` or otherwise a name is selected based on the +class name of the return type. +|=== [[mvc-ann-requestparam]] @@ -2327,6 +2150,14 @@ If using the reactive `WebClient` from `spring-webflux`, or another client, or a data store with reactive support, you can return reactive types directly from Spring MVC controller methods. +Spring MVC adapts transparently to the reactive library in use with proper translation +of cardinality -- i.e. how many values are expected. This is done with the help of the +{api-spring-framework}/core/ReactiveAdapterRegistry.html[ReactiveAdapterRegistry] from +`spring-core` which provides pluggable support for reactive and async types. The registry +has built-in support for RxJava but others can be registered. + +Return values are handled as follows: + * If the return type has single-value stream semantics such as Reactor `Mono` or RxJava `Single` it is adapted and equivalent to using `DeferredResult`. * If the return type has multi-value stream semantics such as Reactor `Flux` or @@ -4635,29 +4466,25 @@ override the `createDispatcherServlet` method. [[mvc-config]] -== Configuring Spring MVC -<> and <> explained about Spring -MVC's special beans and the default implementations used by the `DispatcherServlet`. In -this section you'll learn about two additional ways of configuring Spring MVC. Namely -the MVC Java config and the MVC XML namespace. +== MVC Java config, XML namespace +[.small]#<># -The MVC Java config and the MVC namespace provide similar default configuration that -overrides the `DispatcherServlet` defaults. The goal is to spare most applications from -having to create the same configuration and also to provide higher-level constructs for -configuring Spring MVC that serve as a simple starting point and require little or no -prior knowledge of the underlying configuration. +The MVC Java config and the MVC namespace provide default configuration suitable for most +applications along with a configuration API to customize it. -You can choose either the MVC Java config or the MVC namespace depending on your -preference. Also as you will see further below, with the MVC Java config it is easier to -see the underlying configuration as well as to make fine-grained customizations directly -to the created Spring MVC beans. But let's start from the beginning. +For more advanced customizations, not available in the configuration API, see +<> and <>. +You do not need to understand the underlying beans created by the MVC Java config and the +MVC namespace but if you want to learn more, see <> and +<>. [[mvc-config-enable]] -=== Enabling the MVC Java Config or the MVC XML Namespace -To enable MVC Java config add the annotation `@EnableWebMvc` to one of your -`@Configuration` classes: +=== Enable the Configuration +[.small]#<># + +In Java config use the `@EnableWebMvc` annotation: [source,java,indent=0] [subs="verbatim,quotes"] @@ -4665,13 +4492,10 @@ To enable MVC Java config add the annotation `@EnableWebMvc` to one of your @Configuration @EnableWebMvc public class WebConfig { - } ---- -To achieve the same in XML use the `mvc:annotation-driven` element in your -DispatcherServlet context (or in your root context if you have no DispatcherServlet -context defined): +In XML use the `` element: [source,xml,indent=0] [subs="verbatim,quotes"] @@ -4691,84 +4515,17 @@ context defined): ---- -The above registers a `RequestMappingHandlerMapping`, a `RequestMappingHandlerAdapter`, -and an `ExceptionHandlerExceptionResolver` (among others) in support of processing -requests with annotated controller methods using annotations such as `@RequestMapping`, -`@ExceptionHandler`, and others. - -It also enables the following: - -. Spring 3 style type conversion through a <> instance -in addition to the JavaBeans PropertyEditors used for Data Binding. -. Support for <> Number fields using the `@NumberFormat` annotation -through the `ConversionService`. -. Support for <> `Date`, `Calendar`, `Long`, and Joda Time fields using the -`@DateTimeFormat` annotation. -. Support for <> `@Controller` inputs with `@Valid`, if -a JSR-303 Provider is present on the classpath. -. `HttpMessageConverter` support for `@RequestBody` method parameters and `@ResponseBody` -method return values from `@RequestMapping` or `@ExceptionHandler` methods. - -+ - -This is the complete list of HttpMessageConverters set up by mvc:annotation-driven: - -+ - -.. `ByteArrayHttpMessageConverter` converts byte arrays. -.. `StringHttpMessageConverter` converts strings. -.. `ResourceHttpMessageConverter` converts to/from -`org.springframework.core.io.Resource` for all media types. -.. `SourceHttpMessageConverter` converts to/from a `javax.xml.transform.Source`. -.. `FormHttpMessageConverter` converts form data to/from a `MultiValueMap`. -.. `Jaxb2RootElementHttpMessageConverter` converts Java objects to/from XML -- added if -JAXB2 is present and Jackson 2 XML extension is not present on the classpath. -.. `MappingJackson2HttpMessageConverter` converts to/from JSON -- added if Jackson 2 -is present on the classpath. -.. `MappingJackson2XmlHttpMessageConverter` converts to/from XML -- added if -https://github.com/FasterXML/jackson-dataformat-xml[Jackson 2 XML extension] is present -on the classpath. -.. `MappingJackson2SmileHttpMessageConverter` converts to/from Smile (binary JSON) -- added if -https://github.com/FasterXML/jackson-dataformats-binary/tree/master/smile[Jackson 2 Smile extension] -is present on the classpath. -.. `MappingJackson2CborHttpMessageConverter` converts to/from CBOR -- added if -https://github.com/FasterXML/jackson-dataformats-binary/tree/master/cbor[Jackson 2 CBOR extension] -is present on the classpath. -.. `AtomFeedHttpMessageConverter` converts Atom feeds -- added if Rome is present on the -classpath. -.. `RssChannelHttpMessageConverter` converts RSS feeds -- added if Rome is present on -the classpath. - -See <> for more information about how to customize these -default converters. - -[NOTE] -==== -Jackson JSON and XML converters are created using `ObjectMapper` instances created by -{api-spring-framework}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`] -in order to provide a better default configuration. - -This builder customizes Jackson's default properties with the following ones: - -. http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES[`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`] is disabled. -. http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html#DEFAULT_VIEW_INCLUSION[`MapperFeature.DEFAULT_VIEW_INCLUSION`] is disabled. - -It also automatically registers the following well-known modules if they are detected on the classpath: - -. https://github.com/FasterXML/jackson-datatype-jdk7[jackson-datatype-jdk7]: support for Java 7 types like `java.nio.file.Path`. -. https://github.com/FasterXML/jackson-datatype-joda[jackson-datatype-joda]: support for Joda-Time types. -. https://github.com/FasterXML/jackson-datatype-jsr310[jackson-datatype-jsr310]: support for Java 8 Date & Time API types. -. https://github.com/FasterXML/jackson-datatype-jdk8[jackson-datatype-jdk8]: support for other Java 8 types like `Optional`. -==== +The above registers a number of Spring MVC +<> also adapting to dependencies +available on the classpath -- for JSON, XML, etc. [[mvc-config-customize]] -=== Customizing the Provided Configuration -To customize the default configuration in Java you simply implement the -`WebMvcConfigurer` interface or more likely extend the class `WebMvcConfigurer` -and override the methods you need: +=== Configuration Mechanism +[.small]#<># + +In Java config implement `WebMvcConfigurer` interface: [source,java,indent=0] [subs="verbatim,quotes"] @@ -4777,13 +4534,12 @@ and override the methods you need: @EnableWebMvc public class WebConfig implements WebMvcConfigurer { - // Override configuration methods... + // Implement configuration methods... } ---- -To customize the default configuration of `` check what -attributes and sub-elements it supports. You can view the +In XML check attributes and sub-elements of ``. You can view the http://schema.spring.io/mvc/spring-mvc.xsd[Spring MVC XML schema] or use the code completion feature of your IDE to discover what attributes and sub-elements are available. @@ -4791,11 +4547,13 @@ available. [[mvc-config-conversion]] === Conversion and Formatting +[.small]#<># By default formatters for `Number` and `Date` types are installed, including support for the `@NumberFormat` and `@DateTimeFormat` annotations. Full support for the Joda Time -formatting library is also installed if Joda Time is present on the classpath. To -register custom formatters and converters, override the `addFormatters` method: +formatting library is also installed if Joda Time is present on the classpath. + +In Java config, register custom formatters and converters: [source,java,indent=0] [subs="verbatim,quotes"] @@ -4806,14 +4564,13 @@ register custom formatters and converters, override the `addFormatters` method: @Override public void addFormatters(FormatterRegistry registry) { - // Add formatters and/or converters + // ... } } ---- -In the MVC namespace the same defaults apply when `` is added. -To register custom formatters and converters simply supply a `ConversionService`: +In XML, the same: [source,xml,indent=0] [subs="verbatim,quotes"] @@ -4859,37 +4616,17 @@ See <> and the `FormattingConversionServiceFactoryBean` for more information on when to use FormatterRegistrars. ==== + [[mvc-config-validation]] === Validation +[.small]#<># -Spring provides a <> that can be used for validation in all layers -of an application. In Spring MVC you can configure it for use as a global `Validator` instance, to be used -whenever an `@Valid` or `@Validated` controller method argument is encountered, and/or as a local -`Validator` within a controller through an `@InitBinder` method. Global and local validator -instances can be combined to provide composite validation. +By default if <> is present +on the classpath -- e.g. Hibernate Validator, the `LocalValidatorFactoryBean` is registered +as a global <> for use with `@Valid` and `Validated` on +controller method arguments. -Spring also <> Bean Validation -via `LocalValidatorFactoryBean` which adapts the Spring `org.springframework.validation.Validator` -interface to the Bean Validation `javax.validation.Validator` contract. This class can be -plugged into Spring MVC as a global validator as described next. - -By default use of `@EnableWebMvc` or `` automatically registers Bean -Validation support in Spring MVC through the `LocalValidatorFactoryBean` when a Bean Validation -provider such as Hibernate Validator is detected on the classpath. - -[NOTE] -==== -Sometimes it's convenient to have a `LocalValidatorFactoryBean` injected into a controller -or another class. The easiest way to do that is to declare your own `@Bean` and also mark it -with `@Primary` in order to avoid a conflict with the one provided with the MVC Java config. - -If you prefer to use the one from the MVC Java config, you'll need to override the -`mvcValidator` method from `WebMvcConfigurationSupport` and declare the method to explicitly -return `LocalValidatorFactory` rather than `Validator`. See <> -for information on how to switch to extend the provided configuration. -==== - -Alternatively you can configure your own global `Validator` instance: +In Java config, you can customize the global `Validator` instance: [source,java,indent=0] [subs="verbatim,quotes"] @@ -4900,13 +4637,13 @@ Alternatively you can configure your own global `Validator` instance: @Override public Validator getValidator(); { - // return "global" validator + // ... } } ---- -and in XML: +In XML, the same: [source,xml,indent=0] [subs="verbatim,quotes"] @@ -4926,7 +4663,7 @@ and in XML: ---- -To combine global with local validation, simply add one or more local validator(s): +Note that you can also register ``Validator``'s locally: [source,java,indent=0] [subs="verbatim,quotes"] @@ -4942,19 +4679,18 @@ To combine global with local validation, simply add one or more local validator( } ---- -With this minimal configuration any time an `@Valid` or `@Validated` method argument is encountered, it -will be validated by the configured validators. Any validation violations will automatically -be exposed as errors in the `BindingResult` accessible as a method argument and also renderable -in Spring MVC HTML views. +[TIP] +==== +If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and +mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config. +==== [[mvc-config-interceptors]] === Interceptors -You can configure `HandlerInterceptors` or `WebRequestInterceptors` to be applied to all -incoming requests or restricted to specific URL path patterns. -An example of registering interceptors in Java: +In Java config, register interceptors to apply to incoming requests: [source,java,indent=0] [subs="verbatim"] @@ -4973,7 +4709,7 @@ An example of registering interceptors in Java: } ---- -And in XML use the `` element: +In XML, the same: [source,xml,indent=0] [subs="verbatim"] @@ -4995,24 +4731,22 @@ And in XML use the `` element: [[mvc-config-content-negotiation]] -=== Content Negotiation -You can configure how Spring MVC determines the requested media types from the request. -The available options are to check a query parameter, the URL path for a file extension, -the "Accept" header, use a fixed list, or a custom strategy. +=== Requested Content Types +[.small]#<># -By default for backwards compatibility the path extension in the request URI is checked -first and the "Accept" header is checked second. However if you must use URL-based content -type resolution, we highly recommend using the query parameter strategy over the path -extension since the latter can cause issues with URI variables, path parameters, and also -in combination with URI decoding. +You can configure how Spring MVC determines the requested media types from the request -- +e.g. `Accept` header, URL path extension, query parameter, etc. -The MVC Java config and the MVC namespace register `json`, `xml`, `rss`, `atom` by -default if corresponding dependencies are on the classpath. Additional -path extension-to-media type mappings may also be registered explicitly and that -also has the effect of whitelisting them as safe extensions for the purpose of RFD -attack detection (see <> for more detail). +By default the URL path extension is checked first -- with `json`, `xml`, `rss`, and `atom` +registered as known extensions depending on classpath dependencies, and the "Accept" header +is checked second. -Below is an example of customizing content negotiation options through the MVC Java config: +Consider changing those defaults to `Accept` header only and if you must use URL-based +content type resolution consider the query parameter strategy over the path extensions. See +<> and <> for +more details. + +In Java config, customize requested content type resolution: [source,java,indent=0] [subs="verbatim,quotes"] @@ -5028,9 +4762,7 @@ Below is an example of customizing content negotiation options through the MVC J } ---- -In the MVC namespace, the `` element has a -`content-negotiation-manager` attribute, which expects a `ContentNegotiationManager` -that in turn can be created with a `ContentNegotiationManagerFactoryBean`: +In XML, the same: [source,xml,indent=0] [subs="verbatim,quotes"] @@ -5047,26 +4779,100 @@ that in turn can be created with a `ContentNegotiationManagerFactoryBean`: ---- -If not using the MVC Java config or the MVC namespace, you'll need to create an instance -of `ContentNegotiationManager` and use it to configure `RequestMappingHandlerMapping` -for request mapping purposes, and `RequestMappingHandlerAdapter` and -`ExceptionHandlerExceptionResolver` for content negotiation purposes. -Note that `ContentNegotiatingViewResolver` now can also be configured with a -`ContentNegotiationManager`, so you can use one shared instance throughout Spring MVC. +[[mvc-config-message-converters]] +=== Message Converters +[.small]#<># -In more advanced cases, it may be useful to configure multiple -`ContentNegotiationManager` instances that in turn may contain custom -`ContentNegotiationStrategy` implementations. For example you could configure -`ExceptionHandlerExceptionResolver` with a `ContentNegotiationManager` that always -resolves the requested media type to `"application/json"`. Or you may want to plug a -custom strategy that has some logic to select a default content type (e.g. either XML or -JSON) if no content types were requested. +Customization of `HttpMessageConverter` can be achieved in Java config by overriding +{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`] +if you want to replace the default converters created by Spring MVC, or by overriding +{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#extendMessageConverters-java.util.List-[`extendMessageConverters()`] +if you just want to customize them or add additional converters to the default ones. +Below is an example that adds Jackson JSON and XML converters with a customized +`ObjectMapper` instead of default ones: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebMvc + public class WebConfiguration implements WebMvcConfigurer { + + @Override + public void configureMessageConverters(List> converters) { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() + .indentOutput(true) + .dateFormat(new SimpleDateFormat("yyyy-MM-dd")) + .modulesToInstall(new ParameterNamesModule()); + converters.add(new MappingJackson2HttpMessageConverter(builder.build())); + converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build())); + } + + } +---- + +In this example, +{api-spring-framework}/http/converter/json/Jackson2ObjectMapperBuilder.html[Jackson2ObjectMapperBuilder] +is used to create a common configuration for both `MappingJackson2HttpMessageConverter` and +`MappingJackson2XmlHttpMessageConverter` with indentation enabled, a customized date format +and the registration of +https://github.com/FasterXML/jackson-module-parameter-names[jackson-module-parameter-names] +that adds support for accessing parameter names (feature added in Java 8). + +This builder customizes Jackson's default properties with the following ones: + +. http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES[`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`] is disabled. +. http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html#DEFAULT_VIEW_INCLUSION[`MapperFeature.DEFAULT_VIEW_INCLUSION`] is disabled. + +It also automatically registers the following well-known modules if they are detected on the classpath: + +. https://github.com/FasterXML/jackson-datatype-jdk7[jackson-datatype-jdk7]: support for Java 7 types like `java.nio.file.Path`. +. https://github.com/FasterXML/jackson-datatype-joda[jackson-datatype-joda]: support for Joda-Time types. +. https://github.com/FasterXML/jackson-datatype-jsr310[jackson-datatype-jsr310]: support for Java 8 Date & Time API types. +. https://github.com/FasterXML/jackson-datatype-jdk8[jackson-datatype-jdk8]: support for other Java 8 types like `Optional`. + +[NOTE] +==== +Enabling indentation with Jackson XML support requires +http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.codehaus.woodstox%22%20AND%20a%3A%22woodstox-core-asl%22[`woodstox-core-asl`] +dependency in addition to http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22jackson-dataformat-xml%22[`jackson-dataformat-xml`] one. +==== + +Other interesting Jackson modules are available: + +. https://github.com/zalando/jackson-datatype-money[jackson-datatype-money]: support for `javax.money` types (unofficial module) +. https://github.com/FasterXML/jackson-datatype-hibernate[jackson-datatype-hibernate]: support for Hibernate specific types and properties (including lazy-loading aspects) + +It is also possible to do the same in XML: + +[source,xml,indent=0] +[subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + +---- [[mvc-config-view-controller]] === View Controllers + This is a shortcut for defining a `ParameterizableViewController` that immediately forwards to a view when invoked. Use it in static cases when there is no Java controller logic to execute before the view generates the response. @@ -5099,6 +4905,8 @@ And the same in XML use the `` element: [[mvc-config-view-resolvers]] === View Resolvers +[.small]#<># + The MVC config simplifies the registration of view resolvers. The following is a Java config example that configures content negotiation view @@ -5188,125 +4996,62 @@ In Java config simply add the respective "Configurer" bean: [[mvc-config-static-resources]] -=== Serving of Resources -This option allows static resource requests following a particular URL pattern to be -served by a `ResourceHttpRequestHandler` from any of a list of `Resource` locations. -This provides a convenient way to serve static resources from locations other than the -web application root, including locations on the classpath. The `cache-period` property -may be used to set far future expiration headers (1 year is the recommendation of -optimization tools such as Page Speed and YSlow) so that they will be more efficiently -utilized by the client. The handler also properly evaluates the `Last-Modified` header -(if present) so that a `304` status code will be returned as appropriate, avoiding -unnecessary overhead for resources that are already cached by the client. For example, -to serve resource requests with a URL pattern of `/resources/{asterisk}{asterisk}` from a -`public-resources` directory within the web application root you would use: +=== Static Resources +[.small]#<># + +This option provides a convenient way to serve static resources from a list of +{api-spring-framework}/core/io/Resource.html[Resource]-based locations. + +In the example below, given a request that starts with `"/resources"`, the relative path is +used to find and serve static resources relative to "/public" under the web application +root or on the classpath under `"/static"`. The resources are served with a 1-year future +expiration to ensure maximum use of the browser cache and a reduction in HTTP requests +made by the browser. The `Last-Modified` header is also evaluated and if present a `304` +status code is returned. + +In Java config: [source,java,indent=0] [subs="verbatim"] ---- @Configuration @EnableWebMvc - public class WebConfig implements WebMvcConfigurer { - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/"); - } - - } ----- - -And the same in XML: - -[source,xml,indent=0] -[subs="verbatim"] ----- - ----- - -To serve these resources with a 1-year future expiration to ensure maximum use of the -browser cache and a reduction in HTTP requests made by the browser: - -[source,java,indent=0] -[subs="verbatim"] ----- - @Configuration - @EnableWebMvc - public class WebConfig implements WebMvcConfigurer { - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926); - } - - } ----- - -And in XML: - -[source,xml,indent=0] -[subs="verbatim"] ----- - ----- - -For more details, see <>. - - -The `mapping` attribute must be an Ant pattern that can be used by -`SimpleUrlHandlerMapping`, and the `location` attribute must specify one or more valid -resource directory locations. Multiple resource locations may be specified using a -comma-separated list of values. The locations specified will be checked in the specified -order for the presence of the resource for any given request. For example, to enable the -serving of resources from both the web application root and from a known path of -`/META-INF/public-web-resources/` in any jar on the classpath use: - -[source,java,indent=0] -[subs="verbatim"] ----- - @EnableWebMvc - @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") - .addResourceLocations("/", "classpath:/META-INF/public-web-resources/"); + .addResourceLocations("/public", "classpath:/static/") + .setCachePeriod(31556926); } } ---- -And in XML: +In XML: [source,xml,indent=0] [subs="verbatim,quotes"] ---- - + ---- -When serving resources that may change when a new version of the application is -deployed it is recommended that you incorporate a version string into the mapping -pattern used to request the resources so that you may force clients to request the -newly deployed version of your application's resources. Support for versioned URLs is -built into the framework and can be enabled by configuring a resource chain -on the resource handler. The chain consists of one more `ResourceResolver` -instances followed by one or more `ResourceTransformer` instances. Together they -can provide arbitrary resolution and transformation of resources. +See also +<>. -The built-in `VersionResourceResolver` can be configured with different strategies. -For example a `FixedVersionStrategy` can use a property, a date, or other as the version. -A `ContentVersionStrategy` uses an MD5 hash computed from the content of the resource -(known as "fingerprinting" URLs). Note that the `VersionResourceResolver` will automatically -use the resolved version strings as HTTP ETag header values when serving resources. +The resource handler also supports a chain of +{api-spring-framework}/web/servlet/resource/ResourceResolver.html[ResourceResolver]'s and +{api-spring-framework}/web/servlet/resource/ResourceTransformer.html[ResourceResolver]'s. +which can be used to create a toolchain for working with optimized resources. -`ContentVersionStrategy` is a good default choice to use except in cases where -it cannot be used (e.g. with JavaScript module loaders). You can configure -different version strategies against different patterns as shown below. Keep in mind -also that computing content-based versions is expensive and therefore resource chain -caching should be enabled in production. +The `VersionResourceResolver` can be used for versioned resource URLs based on an MD5 hash +computed from the content, a fixed application version, or other. A +`ContentVersionStrategy` (MD5 hash) is a good choice with some notable exceptions such as +JavaScript resources used with a module loader. -Java config example; +For example in Java config; [source,java,indent=0] [subs="verbatim"] @@ -5318,20 +5063,20 @@ Java config example; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") - .addResourceLocations("/public-resources/") - .resourceChain(true).addResolver( - new VersionResourceResolver().addContentVersionStrategy("/**")); + .addResourceLocations("/public/") + .resourceChain(true) + .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**")); } } ---- -XML example: +In XML, the same: [source,xml,indent=0] [subs="verbatim"] ---- - + @@ -5343,24 +5088,22 @@ XML example: ---- -In order for the above to work the application must also -render URLs with versions. The easiest way to do that is to configure the -`ResourceUrlEncodingFilter` which wraps the response and overrides its `encodeURL` method. -This will work in JSPs, FreeMarker, and any other view technology that calls the response -`encodeURL` method. Alternatively, an application can also inject and use directly the -`ResourceUrlProvider` bean, which is automatically declared with the MVC Java config and -the MVC namespace. +You can use `ResourceUrlProvider` to rewrite URLs and apply the full chain of resolvers and +transformers -- e.g. to insert versions. The MVC config provides a `ResourceUrlProvider` +bean so it can be injected into others. You can also make the rewrite transparent with the +`ResourceUrlEncodingFilter` for Thymeleaf, JSPs, FreeMarker, and others with URL tags that +rely on `HttpServletResponse#encodeURL`. -Webjars are also supported with `WebJarsResourceResolver`, which is automatically registered -when the `"org.webjars:webjars-locator"` library is on classpath. This resolver allows -the resource chain to resolve version agnostic libraries from HTTP GET requests -`"GET /jquery/jquery.min.js"` will return resource `"/jquery/1.2.0/jquery.min.js"`. -It also works by rewriting resource URLs in templates -`