Migrate to Asciidoctor Tabs
This commit is contained in:
@@ -84,8 +84,11 @@ annotation enables cross-origin requests on annotated controller methods, as the
|
||||
following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
@@ -103,8 +106,10 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
@@ -122,6 +127,7 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
By default, `@CrossOrigin` allows:
|
||||
@@ -142,8 +148,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
|
||||
The following example specifies a certain domain and sets `maxAge` to an hour:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
|
||||
@RestController
|
||||
@@ -161,8 +170,10 @@ The following example specifies a certain domain and sets `maxAge` to an hour:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@CrossOrigin("https://domain2.com", maxAge = 3600)
|
||||
@RestController
|
||||
@@ -180,14 +191,18 @@ The following example specifies a certain domain and sets `maxAge` to an hour:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
You can use `@CrossOrigin` at both the class and the method level,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600) // <1>
|
||||
@RestController
|
||||
@@ -206,6 +221,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
|
||||
@@ -261,8 +277,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
|
||||
To enable CORS in the WebFlux Java configuration, you can use the `CorsRegistry` callback,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -282,8 +301,10 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -302,6 +323,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -321,8 +343,11 @@ CORS.
|
||||
To configure the filter, you can declare a `CorsWebFilter` bean and pass a
|
||||
`CorsConfigurationSource` to its constructor, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
CorsWebFilter corsFilter() {
|
||||
@@ -343,8 +368,10 @@ To configure the filter, you can declare a `CorsWebFilter` bean and pass a
|
||||
return new CorsWebFilter(source);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
fun corsFilter(): CorsWebFilter {
|
||||
@@ -365,3 +392,4 @@ To configure the filter, you can declare a `CorsWebFilter` bean and pass a
|
||||
return CorsWebFilter(source)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -30,8 +30,11 @@ difference that router functions provide not just data, but also behavior.
|
||||
`RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
||||
@@ -64,6 +67,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create router using `route()`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -133,80 +137,113 @@ while access to the body is provided through the `body` methods.
|
||||
|
||||
The following example extracts the request body to a `Mono<String>`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<String> string = request.bodyToMono(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val string = request.awaitBody<String>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example extracts the body to a `Flux<Person>` (or a `Flow<Person>` in Kotlin),
|
||||
where `Person` objects are decoded from some serialized form, such as JSON or XML:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Person> people = request.bodyToFlux(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val people = request.bodyToFlow<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
The preceding examples are shortcuts that use the more general `ServerRequest.body(BodyExtractor)`,
|
||||
which accepts the `BodyExtractor` functional strategy interface. The utility class
|
||||
`BodyExtractors` provides access to a number of instances. For example, the preceding examples can
|
||||
also be written as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
|
||||
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
|
||||
val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to access form data:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<MultiValueMap<String, String>> map = request.formData();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val map = request.awaitFormData()
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to access multipart data as a map:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<MultiValueMap<String, Part>> map = request.multipartData();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val map = request.awaitMultipartData()
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to access multipart data, one at a time, in streaming fashion:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
|
||||
allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
@@ -232,8 +269,9 @@ allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
}));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parts = request.bodyToFlux<PartEvent>()
|
||||
allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
@@ -258,6 +296,7 @@ allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
@@ -269,47 +308,65 @@ a `build` method to create it. You can use the builder to set the response statu
|
||||
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
|
||||
content:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person: Person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val location: URI = ...
|
||||
ServerResponse.created(location).build()
|
||||
----
|
||||
======
|
||||
|
||||
Depending on the codec used, it is possible to pass hint parameters to customize how the
|
||||
body is serialized or deserialized. For example, to specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON view]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-fn-handler-classes]]
|
||||
@@ -318,17 +375,23 @@ ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::cla
|
||||
We can write a handler function as a lambda, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HandlerFunction<ServerResponse> helloWorld =
|
||||
request -> ServerResponse.ok().bodyValue("Hello World");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
That is convenient, but in an application we need multiple functions, and multiple inline
|
||||
@@ -338,8 +401,11 @@ has a similar role as `@Controller` in an annotation-based application.
|
||||
For example, the following class exposes a reactive `Person` repository:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
@@ -370,6 +436,7 @@ public class PersonHandler {
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
@@ -422,8 +489,11 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado
|
||||
apply validation to the request body. For example, given a custom Spring
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class PersonHandler {
|
||||
|
||||
@@ -445,6 +515,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Pers
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
@@ -515,15 +586,20 @@ and so on.
|
||||
The following example uses a request predicate to create a constraint based on the `Accept`
|
||||
header:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
|
||||
request -> ServerResponse.ok().bodyValue("Hello World")).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = coRouter {
|
||||
GET("/hello-world", accept(TEXT_PLAIN)) {
|
||||
@@ -531,6 +607,7 @@ header:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can compose multiple request predicates together by using:
|
||||
|
||||
@@ -568,8 +645,11 @@ There are also other ways to compose multiple router functions together:
|
||||
The following example shows the composition of four routes:
|
||||
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
||||
@@ -586,6 +666,7 @@ RouterFunction<ServerResponse> route = route()
|
||||
.add(otherRoute) // <4>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
@@ -630,8 +711,11 @@ this duplication by using a type-level `@RequestMapping` annotation that maps to
|
||||
router function builder. For instance, the last few lines of the example above can be
|
||||
improved in the following way by using nested routes:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", builder -> builder // <1>
|
||||
@@ -640,6 +724,7 @@ RouterFunction<ServerResponse> route = route()
|
||||
.POST(handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> Note that second parameter of `path` is a consumer that takes the router builder.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -660,8 +745,11 @@ the `nest` method on the builder.
|
||||
The above still contains some duplication in the form of the shared `Accept`-header predicate.
|
||||
We can further improve by using the `nest` method together with `accept`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
@@ -671,8 +759,10 @@ We can further improve by using the `nest` method together with `accept`:
|
||||
.POST(handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = coRouter {
|
||||
"/person".nest {
|
||||
@@ -684,6 +774,7 @@ We can further improve by using the `nest` method together with `accept`:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-fn-running]]
|
||||
@@ -721,8 +812,11 @@ starter.
|
||||
The following example shows a WebFlux Java configuration (see
|
||||
xref:web/webflux/dispatcher-handler.adoc[DispatcherHandler] for how to run it):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -756,8 +850,10 @@ xref:web/webflux/dispatcher-handler.adoc[DispatcherHandler] for how to run it):
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -788,6 +884,7 @@ xref:web/webflux/dispatcher-handler.adoc[DispatcherHandler] for how to run it):
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -803,8 +900,11 @@ The filter will apply to all routes that are built by the builder.
|
||||
This means that filters defined in nested routes do not apply to "top-level" routes.
|
||||
For instance, consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
@@ -818,6 +918,7 @@ For instance, consider the following example:
|
||||
.after((request, response) -> logResponse(response)) // <2>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
|
||||
@@ -853,8 +954,11 @@ Now we can add a simple security filter to our route, assuming that we have a `S
|
||||
can determine whether a particular path is allowed.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
SecurityManager securityManager = ...
|
||||
|
||||
@@ -874,8 +978,10 @@ The following example shows how to do so:
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val securityManager: SecurityManager = ...
|
||||
|
||||
@@ -895,6 +1001,7 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
|
||||
We only let the handler function be run when access is allowed.
|
||||
|
||||
@@ -47,8 +47,11 @@ integration for using Spring WebFlux with FreeMarker templates.
|
||||
|
||||
The following example shows how to configure FreeMarker as a view technology:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -69,8 +72,10 @@ The following example shows how to configure FreeMarker as a view technology:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -88,6 +93,7 @@ The following example shows how to configure FreeMarker as a view technology:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer`,
|
||||
shown in the preceding example. Given the preceding configuration, if your controller
|
||||
@@ -106,8 +112,11 @@ properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property
|
||||
a `java.util.Properties` object, and the `freemarkerVariables` property requires a
|
||||
`java.util.Map`. The following example shows how to use a `FreeMarkerConfigurer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -127,8 +136,10 @@ a `java.util.Properties` object, and the `freemarkerVariables` property requires
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -143,6 +154,7 @@ a `java.util.Properties` object, and the `freemarkerVariables` property requires
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
See the FreeMarker documentation for details of settings and variables as they apply to
|
||||
the `Configuration` object.
|
||||
@@ -245,8 +257,11 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -268,8 +283,10 @@ The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -288,6 +305,7 @@ The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The `render` function is called with the following parameters:
|
||||
|
||||
@@ -307,8 +325,11 @@ https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
|
||||
browser facilities not available in the server-side script engine.
|
||||
The following example shows how to set a custom render function:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -330,8 +351,10 @@ The following example shows how to set a custom render function:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -350,6 +373,7 @@ The following example shows how to set a custom render function:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
|
||||
script engines with templating libraries not designed for concurrency, such as Handlebars or
|
||||
|
||||
@@ -5,8 +5,11 @@ You can add attributes to a request. This is convenient if you want to pass info
|
||||
through the filter chain and influence the behavior of filters for a given request.
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.filter((request, next) -> {
|
||||
@@ -22,8 +25,10 @@ For example:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.builder()
|
||||
.filter { request, _ ->
|
||||
@@ -37,6 +42,7 @@ For example:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
Note that you can configure a `defaultRequest` callback globally at the
|
||||
`WebClient.Builder` level which lets you insert attributes into all requests,
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
The request body can be encoded from any asynchronous type handled by `ReactiveAdapterRegistry`,
|
||||
like `Mono` or Kotlin Coroutines `Deferred` as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> personMono = ... ;
|
||||
|
||||
@@ -16,8 +19,10 @@ like `Mono` or Kotlin Coroutines `Deferred` as the following example shows:
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val personDeferred: Deferred<Person> = ...
|
||||
|
||||
@@ -28,11 +33,15 @@ like `Mono` or Kotlin Coroutines `Deferred` as the following example shows:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
You can also have a stream of objects be encoded, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Person> personFlux = ... ;
|
||||
|
||||
@@ -43,8 +52,10 @@ You can also have a stream of objects be encoded, as the following example shows
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val people: Flow<Person> = ...
|
||||
|
||||
@@ -55,12 +66,16 @@ You can also have a stream of objects be encoded, as the following example shows
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you have the actual value, you can use the `bodyValue` shortcut method,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Person person = ... ;
|
||||
|
||||
@@ -71,8 +86,10 @@ as the following example shows:
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person: Person = ...
|
||||
|
||||
@@ -83,6 +100,7 @@ as the following example shows:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -93,8 +111,11 @@ To send form data, you can provide a `MultiValueMap<String, String>` as the body
|
||||
content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
`FormHttpMessageWriter`. The following example shows how to use `MultiValueMap<String, String>`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultiValueMap<String, String> formData = ... ;
|
||||
|
||||
@@ -104,8 +125,10 @@ content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val formData: MultiValueMap<String, String> = ...
|
||||
|
||||
@@ -115,11 +138,15 @@ content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
You can also supply form data in-line by using `BodyInserters`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.BodyInserters.*;
|
||||
|
||||
@@ -129,8 +156,10 @@ You can also supply form data in-line by using `BodyInserters`, as the following
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.function.BodyInserters.*
|
||||
|
||||
@@ -140,6 +169,7 @@ You can also supply form data in-line by using `BodyInserters`, as the following
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -151,8 +181,11 @@ either `Object` instances that represent part content or `HttpEntity` instances
|
||||
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
|
||||
multipart request. The following example shows how to create a `MultiValueMap<String, ?>`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultipartBodyBuilder builder = new MultipartBodyBuilder();
|
||||
builder.part("fieldPart", "fieldValue");
|
||||
@@ -162,8 +195,10 @@ multipart request. The following example shows how to create a `MultiValueMap<St
|
||||
|
||||
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val builder = MultipartBodyBuilder().apply {
|
||||
part("fieldPart", "fieldValue")
|
||||
@@ -174,6 +209,7 @@ multipart request. The following example shows how to create a `MultiValueMap<St
|
||||
|
||||
val parts = builder.build()
|
||||
----
|
||||
======
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part. The content
|
||||
type is determined automatically based on the `HttpMessageWriter` chosen to serialize it
|
||||
@@ -184,8 +220,11 @@ builder `part` methods.
|
||||
Once a `MultiValueMap` is prepared, the easiest way to pass it to the `WebClient` is
|
||||
through the `body` method, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultipartBodyBuilder builder = ...;
|
||||
|
||||
@@ -195,8 +234,10 @@ through the `body` method, as the following example shows:
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val builder: MultipartBodyBuilder = ...
|
||||
|
||||
@@ -206,6 +247,7 @@ through the `body` method, as the following example shows:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, which could also
|
||||
represent regular form data (that is, `application/x-www-form-urlencoded`), you need not
|
||||
@@ -215,8 +257,11 @@ set the `Content-Type` to `multipart/form-data`. This is always the case when us
|
||||
As an alternative to `MultipartBodyBuilder`, you can also provide multipart content,
|
||||
inline-style, through the built-in `BodyInserters`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.BodyInserters.*;
|
||||
|
||||
@@ -226,8 +271,10 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.function.BodyInserters.*
|
||||
|
||||
@@ -237,6 +284,7 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
[[partevent]]
|
||||
=== `PartEvent`
|
||||
@@ -252,8 +300,11 @@ the `WebClient`.
|
||||
|
||||
For instance, this sample will POST a multipart form containing a form field and a file.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Resource resource = ...
|
||||
Mono<String> result = webClient
|
||||
@@ -266,8 +317,10 @@ Mono<String> result = webClient
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
var resource: Resource = ...
|
||||
var result: Mono<String> = webClient
|
||||
@@ -282,6 +335,7 @@ var result: Mono<String> = webClient
|
||||
.retrieve()
|
||||
.bodyToMono()
|
||||
----
|
||||
======
|
||||
|
||||
On the server side, `PartEvent` objects that are received via `@RequestBody` or
|
||||
`ServerRequest::bodyToFlux(PartEvent.class)` can be relayed to another service
|
||||
|
||||
@@ -21,26 +21,35 @@ You can also use `WebClient.builder()` with further options:
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.codecs(configurer -> ... )
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val webClient = WebClient.builder()
|
||||
.codecs { configurer -> ... }
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
Once built, a `WebClient` is immutable. However, you can clone it and build a
|
||||
modified copy as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client1 = WebClient.builder()
|
||||
.filter(filterA).filter(filterB).build();
|
||||
@@ -52,8 +61,10 @@ modified copy as follows:
|
||||
|
||||
// client2 has filterA, filterB, filterC, filterD
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client1 = WebClient.builder()
|
||||
.filter(filterA).filter(filterB).build()
|
||||
@@ -65,6 +76,7 @@ modified copy as follows:
|
||||
|
||||
// client2 has filterA, filterB, filterC, filterD
|
||||
----
|
||||
======
|
||||
|
||||
[[webflux-client-builder-maxinmemorysize]]
|
||||
== MaxInMemorySize
|
||||
@@ -79,20 +91,26 @@ org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on m
|
||||
|
||||
To change the limit for default codecs, use the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient webClient = WebClient.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val webClient = WebClient.builder()
|
||||
.codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) }
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -101,8 +119,11 @@ To change the limit for default codecs, use the following:
|
||||
|
||||
To customize Reactor Netty settings, provide a pre-configured `HttpClient`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);
|
||||
|
||||
@@ -110,8 +131,10 @@ To customize Reactor Netty settings, provide a pre-configured `HttpClient`:
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient.create().secure { ... }
|
||||
|
||||
@@ -119,6 +142,7 @@ To customize Reactor Netty settings, provide a pre-configured `HttpClient`:
|
||||
.clientConnector(ReactorClientHttpConnector(httpClient))
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-client-builder-reactor-resources]]
|
||||
@@ -137,20 +161,26 @@ Netty global resources are shut down when the Spring `ApplicationContext` is clo
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public ReactorResourceFactory reactorResourceFactory() {
|
||||
return new ReactorResourceFactory();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
fun reactorResourceFactory() = ReactorResourceFactory()
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
You can also choose not to participate in the global Reactor Netty resources. However,
|
||||
@@ -158,8 +188,11 @@ in this mode, the burden is on you to ensure that all Reactor Netty client and s
|
||||
instances use shared resources, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public ReactorResourceFactory resourceFactory() {
|
||||
@@ -181,6 +214,7 @@ instances use shared resources, as the following example shows:
|
||||
return WebClient.builder().clientConnector(connector).build(); // <3>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create resources independent of global ones.
|
||||
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
|
||||
<3> Plug the connector into the `WebClient.Builder`.
|
||||
@@ -216,8 +250,11 @@ instances use shared resources, as the following example shows:
|
||||
|
||||
To configure a connection timeout:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import io.netty.channel.ChannelOption;
|
||||
|
||||
@@ -228,8 +265,10 @@ To configure a connection timeout:
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import io.netty.channel.ChannelOption
|
||||
|
||||
@@ -240,11 +279,15 @@ To configure a connection timeout:
|
||||
.clientConnector(ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
|
||||
To configure a read or write timeout:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
@@ -257,8 +300,10 @@ To configure a read or write timeout:
|
||||
// Create WebClient...
|
||||
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler
|
||||
@@ -271,30 +316,40 @@ To configure a read or write timeout:
|
||||
|
||||
// Create WebClient...
|
||||
----
|
||||
======
|
||||
|
||||
To configure a response timeout for all requests:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.responseTimeout(Duration.ofSeconds(2));
|
||||
|
||||
// Create WebClient...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient.create()
|
||||
.responseTimeout(Duration.ofSeconds(2));
|
||||
|
||||
// Create WebClient...
|
||||
----
|
||||
======
|
||||
|
||||
To configure a response timeout for a specific request:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient.create().get()
|
||||
.uri("https://example.org/path")
|
||||
@@ -305,8 +360,10 @@ To configure a response timeout for a specific request:
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
WebClient.create().get()
|
||||
.uri("https://example.org/path")
|
||||
@@ -317,6 +374,7 @@ To configure a response timeout for a specific request:
|
||||
.retrieve()
|
||||
.bodyToMono(String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -325,8 +383,11 @@ To configure a response timeout for a specific request:
|
||||
|
||||
The following example shows how to customize the JDK `HttpClient`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
@@ -339,8 +400,9 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
@@ -351,6 +413,7 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
|
||||
val webClient = WebClient.builder().clientConnector(connector).build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -360,8 +423,11 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
The following example shows how to customize Jetty `HttpClient` settings:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.setCookieStore(...);
|
||||
@@ -370,8 +436,10 @@ The following example shows how to customize Jetty `HttpClient` settings:
|
||||
.clientConnector(new JettyClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient()
|
||||
httpClient.cookieStore = ...
|
||||
@@ -380,6 +448,7 @@ The following example shows how to customize Jetty `HttpClient` settings:
|
||||
.clientConnector(JettyClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
|
||||
@@ -391,8 +460,11 @@ declaring a Spring-managed bean of type `JettyResourceFactory`, as the following
|
||||
shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public JettyResourceFactory resourceFactory() {
|
||||
@@ -411,6 +483,7 @@ shows:
|
||||
return WebClient.builder().clientConnector(connector).build(); <2>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Use the `JettyClientHttpConnector` constructor with resource factory.
|
||||
<2> Plug the connector into the `WebClient.Builder`.
|
||||
|
||||
@@ -442,8 +515,11 @@ shows:
|
||||
|
||||
The following example shows how to customize Apache HttpComponents `HttpClient` settings:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
|
||||
clientBuilder.setDefaultRequestConfig(...);
|
||||
@@ -453,8 +529,10 @@ The following example shows how to customize Apache HttpComponents `HttpClient`
|
||||
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = HttpAsyncClients.custom().apply {
|
||||
setDefaultRequestConfig(...)
|
||||
@@ -462,5 +540,6 @@ The following example shows how to customize Apache HttpComponents `HttpClient`
|
||||
val connector = HttpComponentsClientHttpConnector(client)
|
||||
val webClient = WebClient.builder().clientConnector(connector).build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@ e.g. via `concatMap`, then you'll need to use the Reactor `Context`.
|
||||
The Reactor `Context` needs to be populated at the end of a reactive chain in order to
|
||||
apply to all operations. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.filter((request, next) ->
|
||||
@@ -28,6 +31,7 @@ apply to all operations. For example:
|
||||
})
|
||||
.contextWrite(context -> context.put("foo", ...));
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ The `exchangeToMono()` and `exchangeToFlux()` methods (or `awaitExchange { }` an
|
||||
are useful for more advanced cases that require more control, such as to decode the response differently
|
||||
depending on the response status:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> entityMono = client.get()
|
||||
.uri("/persons/1")
|
||||
@@ -21,8 +24,10 @@ depending on the response status:
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val entity = client.get()
|
||||
.uri("/persons/1")
|
||||
@@ -36,6 +41,7 @@ val entity = client.get()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
When using the above, after the returned `Mono` or `Flux` completes, the response body
|
||||
is checked and if not consumed it is released to prevent memory and connection leaks.
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
|
||||
in order to intercept and modify requests, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.filter((request, next) -> {
|
||||
@@ -18,8 +21,10 @@ in order to intercept and modify requests, as the following example shows:
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.builder()
|
||||
.filter { request, next ->
|
||||
@@ -32,12 +37,16 @@ in order to intercept and modify requests, as the following example shows:
|
||||
}
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
This can be used for cross-cutting concerns, such as authentication. The following example uses
|
||||
a filter for basic authentication through a static factory method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
||||
|
||||
@@ -45,8 +54,10 @@ a filter for basic authentication through a static factory method:
|
||||
.filter(basicAuthentication("user", "password"))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication
|
||||
|
||||
@@ -54,12 +65,16 @@ a filter for basic authentication through a static factory method:
|
||||
.filter(basicAuthentication("user", "password"))
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
Filters can be added or removed by mutating an existing `WebClient` instance, resulting
|
||||
in a new `WebClient` instance that does not affect the original one. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
||||
|
||||
@@ -69,13 +84,16 @@ in a new `WebClient` instance that does not affect the original one. For example
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = webClient.mutate()
|
||||
.filters { it.add(0, basicAuthentication("user", "password")) }
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
`WebClient` is a thin facade around the chain of filters followed by an
|
||||
`ExchangeFunction`. It provides a workflow to make requests, to encode to and from higher
|
||||
@@ -85,8 +103,11 @@ its content or to otherwise propagate it downstream to the `WebClient` which wil
|
||||
the same. Below is a filter that handles the `UNAUTHORIZED` status code but ensures that
|
||||
any response content, whether expected or not, is released:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public ExchangeFilterFunction renewTokenFilter() {
|
||||
return (request, next) -> next.exchange(request).flatMap(response -> {
|
||||
@@ -103,8 +124,10 @@ any response content, whether expected or not, is released:
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
fun renewTokenFilter(): ExchangeFilterFunction? {
|
||||
return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction ->
|
||||
@@ -123,6 +146,7 @@ any response content, whether expected or not, is released:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
The `retrieve()` method can be used to declare how to extract the response. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.create("https://example.org");
|
||||
|
||||
@@ -13,8 +16,10 @@ The `retrieve()` method can be used to declare how to extract the response. For
|
||||
.retrieve()
|
||||
.toEntity(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.create("https://example.org")
|
||||
|
||||
@@ -23,11 +28,15 @@ The `retrieve()` method can be used to declare how to extract the response. For
|
||||
.retrieve()
|
||||
.toEntity<Person>().awaitSingle()
|
||||
----
|
||||
======
|
||||
|
||||
Or to get only the body:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.create("https://example.org");
|
||||
|
||||
@@ -36,8 +45,10 @@ Or to get only the body:
|
||||
.retrieve()
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.create("https://example.org")
|
||||
|
||||
@@ -46,32 +57,42 @@ Or to get only the body:
|
||||
.retrieve()
|
||||
.awaitBody<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
To get a stream of decoded objects:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Quote> result = client.get()
|
||||
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlux(Quote.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val result = client.get()
|
||||
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlow<Quote>()
|
||||
----
|
||||
======
|
||||
|
||||
By default, 4xx or 5xx responses result in an `WebClientResponseException`, including
|
||||
sub-classes for specific HTTP status codes. To customize the handling of error
|
||||
responses, use `onStatus` handlers as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
@@ -80,8 +101,10 @@ responses, use `onStatus` handlers as follows:
|
||||
.onStatus(HttpStatus::is5xxServerError, response -> ...)
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
@@ -90,6 +113,7 @@ responses, use `onStatus` handlers as follows:
|
||||
.onStatus(HttpStatus::is5xxServerError) { ... }
|
||||
.awaitBody<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
`WebClient` can be used in synchronous style by blocking at the end for the result:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Person person = client.get().uri("/person/{id}", i).retrieve()
|
||||
.bodyToMono(Person.class)
|
||||
@@ -15,8 +18,10 @@
|
||||
.collectList()
|
||||
.block();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person = runBlocking {
|
||||
client.get().uri("/person/{id}", i).retrieve()
|
||||
@@ -29,12 +34,16 @@
|
||||
.toList()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
However if multiple calls need to be made, it's more efficient to avoid blocking on each
|
||||
response individually, and instead wait for the combined result:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
|
||||
.retrieve().bodyToMono(Person.class);
|
||||
@@ -50,8 +59,10 @@ response individually, and instead wait for the combined result:
|
||||
})
|
||||
.block();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val data = runBlocking {
|
||||
val personDeferred = async {
|
||||
@@ -67,6 +78,7 @@ response individually, and instead wait for the combined result:
|
||||
mapOf("person" to personDeferred.await(), "hobbies" to hobbiesDeferred.await())
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The above is merely one example. There are lots of other patterns and operators for putting
|
||||
together a reactive pipeline that makes many remote calls, potentially some nested,
|
||||
|
||||
@@ -23,8 +23,11 @@ server-side applications that handle WebSocket messages.
|
||||
To create a WebSocket server, you can first create a `WebSocketHandler`.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.WebSocketSession;
|
||||
@@ -37,8 +40,10 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler
|
||||
import org.springframework.web.reactive.socket.WebSocketSession
|
||||
@@ -50,11 +55,15 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Then you can map it to a URL:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig {
|
||||
@@ -69,8 +78,10 @@ Then you can map it to a URL:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig {
|
||||
@@ -84,13 +95,17 @@ Then you can map it to a URL:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If using the xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux Config] there is nothing
|
||||
further to do, or otherwise if not using the WebFlux config you'll need to declare a
|
||||
`WebSocketHandlerAdapter` as shown below:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig {
|
||||
@@ -103,8 +118,10 @@ further to do, or otherwise if not using the WebFlux config you'll need to decla
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig {
|
||||
@@ -115,6 +132,7 @@ further to do, or otherwise if not using the WebFlux config you'll need to decla
|
||||
fun handlerAdapter() = WebSocketHandlerAdapter()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -155,8 +173,11 @@ receives a cancellation signal.
|
||||
The most basic implementation of a handler is one that handles the inbound stream. The
|
||||
following example shows such an implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
class ExampleHandler implements WebSocketHandler {
|
||||
|
||||
@@ -173,6 +194,7 @@ following example shows such an implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Access the stream of inbound messages.
|
||||
<2> Do something with each message.
|
||||
<3> Perform nested asynchronous operations that use the message content.
|
||||
@@ -208,8 +230,11 @@ xref:core/databuffer-codec.adoc[Data Buffers and Codecs].
|
||||
|
||||
The following implementation combines the inbound and outbound streams:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
class ExampleHandler implements WebSocketHandler {
|
||||
|
||||
@@ -229,6 +254,7 @@ The following implementation combines the inbound and outbound streams:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Handle the inbound message stream.
|
||||
<2> Create the outbound message, producing a combined flow.
|
||||
<3> Return a `Mono<Void>` that does not complete while we continue to receive.
|
||||
@@ -261,8 +287,11 @@ The following implementation combines the inbound and outbound streams:
|
||||
Inbound and outbound streams can be independent and be joined only for completion,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
class ExampleHandler implements WebSocketHandler {
|
||||
|
||||
@@ -285,6 +314,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Handle inbound message stream.
|
||||
<2> Send outgoing messages.
|
||||
<3> Join the streams and return a `Mono<Void>` that completes when either stream ends.
|
||||
@@ -359,8 +389,11 @@ such properties as shown in the corresponding section of the
|
||||
xref:web/webflux/config.adoc#webflux-config-websocket-service[WebFlux Config], or otherwise if
|
||||
not using the WebFlux config, use the below:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig {
|
||||
@@ -378,8 +411,10 @@ not using the WebFlux config, use the below:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig {
|
||||
@@ -397,6 +432,7 @@ not using the WebFlux config, use the below:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Check the upgrade strategy for your server to see what options are available. Currently,
|
||||
only Tomcat and Jetty expose such options.
|
||||
@@ -429,8 +465,11 @@ API to suspend receiving messages for back pressure.
|
||||
To start a WebSocket session, you can create an instance of the client and use its `execute`
|
||||
methods:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebSocketClient client = new ReactorNettyWebSocketClient();
|
||||
|
||||
@@ -440,8 +479,10 @@ methods:
|
||||
.doOnNext(System.out::println)
|
||||
.then());
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = ReactorNettyWebSocketClient()
|
||||
|
||||
@@ -452,6 +493,7 @@ methods:
|
||||
.then()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Some clients, such as Jetty, implement `Lifecycle` and need to be stopped and started
|
||||
before you can use them. All clients have constructor options related to configuration
|
||||
|
||||
@@ -30,8 +30,11 @@ While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all
|
||||
directives for the `Cache-Control` response header, the `CacheControl` type takes a
|
||||
use case-oriented approach that focuses on the common scenarios, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// Cache for an hour - "Cache-Control: max-age=3600"
|
||||
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
|
||||
@@ -45,8 +48,9 @@ use case-oriented approach that focuses on the common scenarios, as the followin
|
||||
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// Cache for an hour - "Cache-Control: max-age=3600"
|
||||
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)
|
||||
@@ -60,6 +64,7 @@ use case-oriented approach that focuses on the common scenarios, as the followin
|
||||
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()
|
||||
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -73,8 +78,11 @@ against conditional request headers. A controller can add an `ETag` and `Cache-C
|
||||
settings to a `ResponseEntity`, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/book/{id}")
|
||||
public ResponseEntity<Book> showBook(@PathVariable Long id) {
|
||||
@@ -90,8 +98,9 @@ settings to a `ResponseEntity`, as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/book/{id}")
|
||||
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {
|
||||
@@ -106,6 +115,7 @@ settings to a `ResponseEntity`, as the following example shows:
|
||||
.body(book)
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
|
||||
@@ -116,8 +126,11 @@ You can also make the check against conditional request headers in the controlle
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RequestMapping
|
||||
public String myHandleMethod(ServerWebExchange exchange, Model model) {
|
||||
@@ -132,6 +145,7 @@ as the following example shows:
|
||||
return "myViewName";
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Application-specific calculation.
|
||||
<2> Response has been set to 304 (NOT_MODIFIED). No further processing.
|
||||
<3> Continue with request processing.
|
||||
|
||||
@@ -22,8 +22,11 @@ xref:web/webflux/config.adoc#webflux-config-advanced-java[Advanced Configuration
|
||||
|
||||
You can use the `@EnableWebFlux` annotation in your Java config, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -31,13 +34,15 @@ You can use the `@EnableWebFlux` annotation in your Java config, as the followin
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig
|
||||
----
|
||||
======
|
||||
|
||||
The preceding example registers a number of Spring WebFlux
|
||||
xref:web/webflux/dispatcher-handler.adoc#webflux-special-bean-types[infrastructure beans] and adapts to dependencies
|
||||
@@ -52,8 +57,11 @@ available on the classpath -- for JSON, XML, and others.
|
||||
In your Java configuration, you can implement the `WebFluxConfigurer` interface,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -63,8 +71,9 @@ as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -73,6 +82,7 @@ class WebConfig : WebFluxConfigurer {
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -85,8 +95,11 @@ for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
|
||||
|
||||
To register custom formatters and converters in Java config, use the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -99,8 +112,10 @@ To register custom formatters and converters in Java config, use the following:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -111,14 +126,18 @@ To register custom formatters and converters in Java config, use the following:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
By default Spring WebFlux considers the request Locale when parsing and formatting date
|
||||
values. This works for forms where dates are represented as Strings with "input" form
|
||||
fields. For "date" and "time" form fields, however, browsers use a fixed format defined
|
||||
in the HTML spec. For such cases date and time formatting can be customized as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -132,8 +151,10 @@ in the HTML spec. For such cases date and time formatting can be customized as f
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -146,6 +167,7 @@ in the HTML spec. For such cases date and time formatting can be customized as f
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: See xref:core/validation/format.adoc#format-FormatterRegistrar-SPI[`FormatterRegistrar` SPI]
|
||||
and the `FormattingConversionServiceFactoryBean` for more information on when to
|
||||
@@ -165,8 +187,11 @@ is registered as a global xref:core/validation/validator.adoc[validator] for use
|
||||
In your Java configuration, you can customize the global `Validator` instance,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -179,8 +204,10 @@ as the following example shows:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -192,12 +219,16 @@ as the following example shows:
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note that you can also register `Validator` implementations locally,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class MyController {
|
||||
@@ -209,8 +240,10 @@ as the following example shows:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class MyController {
|
||||
@@ -221,6 +254,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
|
||||
@@ -238,8 +272,11 @@ but you can also enable a query parameter-based strategy.
|
||||
|
||||
The following example shows how to customize the requested content type resolution:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -251,8 +288,10 @@ The following example shows how to customize the requested content type resoluti
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -263,6 +302,7 @@ The following example shows how to customize the requested content type resoluti
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -272,8 +312,11 @@ The following example shows how to customize the requested content type resoluti
|
||||
|
||||
The following example shows how to customize how the request and response body are read and written:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -285,8 +328,10 @@ The following example shows how to customize how the request and response body a
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -297,6 +342,7 @@ The following example shows how to customize how the request and response body a
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
`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.
|
||||
@@ -323,8 +369,11 @@ It also automatically registers the following well-known modules if they are det
|
||||
|
||||
The following example shows how to configure view resolution:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -336,8 +385,10 @@ The following example shows how to configure view resolution:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -348,13 +399,17 @@ The following example shows how to configure view resolution:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The `ViewResolverRegistry` has shortcuts for view technologies with which the Spring Framework
|
||||
integrates. The following example uses FreeMarker (which also requires configuring the
|
||||
underlying FreeMarker view technology):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -376,8 +431,10 @@ underlying FreeMarker view technology):
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -395,11 +452,15 @@ underlying FreeMarker view technology):
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can also plug in any `ViewResolver` implementation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -413,8 +474,10 @@ You can also plug in any `ViewResolver` implementation, as the following example
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -426,14 +489,18 @@ You can also plug in any `ViewResolver` implementation, as the following example
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To support xref:web/webflux/dispatcher-handler.adoc#webflux-multiple-representations[Content Negotiation] and rendering other formats
|
||||
through view resolution (besides HTML), you can configure one or more default views based
|
||||
on the `HttpMessageWriterView` implementation, which accepts any of the available
|
||||
xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`. The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -451,8 +518,10 @@ xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`.
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -469,6 +538,7 @@ xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`.
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
See xref:web/webflux-view.adoc[View Technologies] for more on the view technologies that are integrated with Spring WebFlux.
|
||||
|
||||
@@ -488,8 +558,11 @@ and a reduction in HTTP requests made by the browser. The `Last-Modified` header
|
||||
evaluated and, if present, a `304` status code is returned. The following listing shows
|
||||
the example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -504,8 +577,10 @@ the example:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -518,6 +593,7 @@ the example:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
See also xref:web/webflux/caching.adoc#webflux-caching-static-resources[HTTP caching support for static resources].
|
||||
|
||||
@@ -533,8 +609,11 @@ JavaScript resources used with a module loader).
|
||||
|
||||
The following example shows how to use `VersionResourceResolver` in your Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -550,8 +629,10 @@ The following example shows how to use `VersionResourceResolver` in your Java co
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -566,6 +647,7 @@ The following example shows how to use `VersionResourceResolver` in your Java co
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can use `ResourceUrlProvider` to rewrite URLs and apply the full chain of resolvers and
|
||||
transformers (for example, to insert versions). The WebFlux configuration provides a `ResourceUrlProvider`
|
||||
@@ -606,8 +688,11 @@ You can customize options related to path matching. For details on the individua
|
||||
{api-spring-framework}/web/reactive/config/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc.
|
||||
The following example shows how to use `PathMatchConfigurer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -621,8 +706,10 @@ The following example shows how to use `PathMatchConfigurer`:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -636,6 +723,7 @@ The following example shows how to use `PathMatchConfigurer`:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -664,8 +752,11 @@ In some cases it may be necessary to create the `WebSocketHandlerAdapter` bean w
|
||||
provided `WebSocketService` service which allows configuring WebSocket server properties.
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -679,8 +770,10 @@ For example:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -695,6 +788,7 @@ For example:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -713,8 +807,11 @@ For advanced mode, you can remove `@EnableWebFlux` and extend directly from
|
||||
`DelegatingWebFluxConfiguration` instead of implementing `WebFluxConfigurer`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
public class WebConfig extends DelegatingWebFluxConfiguration {
|
||||
@@ -722,8 +819,10 @@ as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig : DelegatingWebFluxConfiguration {
|
||||
@@ -731,6 +830,7 @@ as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can keep existing methods in `WebConfig`, but you can now also override bean declarations
|
||||
from the base class and still have any number of other `WebMvcConfigurer` implementations on
|
||||
|
||||
@@ -10,8 +10,11 @@ do not have to extend base classes nor implement specific interfaces.
|
||||
|
||||
The following listing shows a basic example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
public class HelloController {
|
||||
@@ -22,8 +25,10 @@ The following listing shows a basic example:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
class HelloController {
|
||||
@@ -32,6 +37,7 @@ The following listing shows a basic example:
|
||||
fun handle() = "Hello WebFlux"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, the method returns a `String` to be written to the response body.
|
||||
|
||||
|
||||
@@ -25,8 +25,11 @@ By default, `@ControllerAdvice` methods apply to every request (that is, all con
|
||||
but you can narrow that down to a subset of controllers by using attributes on the
|
||||
annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// Target all Controllers annotated with @RestController
|
||||
@ControllerAdvice(annotations = RestController.class)
|
||||
@@ -41,8 +44,9 @@ annotation, as the following example shows:
|
||||
public class ExampleAdvice3 {}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// Target all Controllers annotated with @RestController
|
||||
@ControllerAdvice(annotations = [RestController::class])
|
||||
@@ -56,6 +60,7 @@ annotation, as the following example shows:
|
||||
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
|
||||
public class ExampleAdvice3 {}
|
||||
----
|
||||
======
|
||||
|
||||
The selectors in the preceding example are evaluated at runtime and may negatively impact
|
||||
performance if used extensively. See the
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
`@ExceptionHandler` methods to handle exceptions from controller methods. The following
|
||||
example includes such a handler method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
@@ -21,6 +24,7 @@ example includes such a handler method:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Declaring an `@ExceptionHandler`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -22,8 +22,11 @@ with a `WebDataBinder` argument, for registrations, and a `void` return value.
|
||||
The following example uses the `@InitBinder` annotation:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class FormController {
|
||||
@@ -38,6 +41,7 @@ The following example uses the `@InitBinder` annotation:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@InitBinder` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -64,8 +68,11 @@ Alternatively, when using a `Formatter`-based setup through a shared
|
||||
controller-specific `Formatter` instances, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class FormController {
|
||||
@@ -78,6 +85,7 @@ controller-specific `Formatter` instances, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Adding a custom formatter (a `DateFormatter`, in this case).
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -15,14 +15,18 @@ JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
|
||||
|
||||
The following code sample demonstrates how to get the cookie value:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
public void handle(@CookieValue("JSESSIONID") String cookie) { // <1>
|
||||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the cookie value.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -7,21 +7,27 @@
|
||||
container object that exposes request headers and the body. The following example uses an
|
||||
`HttpEntity`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(HttpEntity<Account> entity) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(entity: HttpEntity<Account>) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -13,8 +13,11 @@ which allows rendering only a subset of all fields in an `Object`. To use it wit
|
||||
`@ResponseBody` or `ResponseEntity` controller methods, you can use Jackson's
|
||||
`@JsonView` annotation to activate a serialization view class, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
public class UserController {
|
||||
@@ -54,8 +57,9 @@ which allows rendering only a subset of all fields in an `Object`. To use it wit
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
class UserController {
|
||||
@@ -75,6 +79,7 @@ which allows rendering only a subset of all fields in an `Object`. To use it wit
|
||||
interface WithPasswordView : WithoutPasswordView
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@JsonView` allows an array of view classes but you can only specify only one per
|
||||
controller method. Use a composite interface if you need to activate multiple views.
|
||||
|
||||
@@ -19,8 +19,11 @@ to mask variable content. That said, if you want to access matrix variables from
|
||||
controller method, you need to add a URI variable to the path segment where matrix
|
||||
variables are expected. The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /pets/42;q=11;r=22
|
||||
|
||||
@@ -31,8 +34,10 @@ variables are expected. The following example shows how to do so:
|
||||
// q == 11
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /pets/42;q=11;r=22
|
||||
|
||||
@@ -43,14 +48,18 @@ variables are expected. The following example shows how to do so:
|
||||
// q == 11
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Given that all path segments can contain matrix variables, you may sometimes need to
|
||||
disambiguate which path variable the matrix variable is expected to be in,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /owners/42;q=11/pets/21;q=22
|
||||
|
||||
@@ -63,8 +72,10 @@ as the following example shows:
|
||||
// q2 == 22
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/owners/{ownerId}/pets/{petId}")
|
||||
fun findPet(
|
||||
@@ -75,12 +86,16 @@ as the following example shows:
|
||||
// q2 == 22
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can define a matrix variable may be defined as optional and specify a default value
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /pets/42
|
||||
|
||||
@@ -90,8 +105,10 @@ as the following example shows:
|
||||
// q == 1
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /pets/42
|
||||
|
||||
@@ -101,11 +118,15 @@ as the following example shows:
|
||||
// q == 1
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To get all matrix variables, use a `MultiValueMap`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
|
||||
|
||||
@@ -118,8 +139,10 @@ To get all matrix variables, use a `MultiValueMap`, as the following example sho
|
||||
// petMatrixVars: ["q" : 22, "s" : 23]
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
|
||||
|
||||
@@ -132,5 +155,6 @@ To get all matrix variables, use a `MultiValueMap`, as the following example sho
|
||||
// petMatrixVars: ["q" : 22, "s" : 23]
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -9,12 +9,16 @@ the values of query parameters and form fields whose names match to field names.
|
||||
referred to as data binding, and it saves you from having to deal with parsing and
|
||||
converting individual query parameters and form fields. The following example binds an instance of `Pet`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@ModelAttribute Pet pet) { } // <1>
|
||||
----
|
||||
======
|
||||
<1> Bind an instance of `Pet`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -45,8 +49,11 @@ Data binding can result in errors. By default, a `WebExchangeBindException` is r
|
||||
to check for such errors in the controller method, you can add a `BindingResult` argument
|
||||
immediately next to the `@ModelAttribute`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { <1>
|
||||
@@ -56,6 +63,7 @@ immediately next to the `@ModelAttribute`, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Adding a `BindingResult`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -76,8 +84,11 @@ You can automatically apply validation after data binding by adding the
|
||||
xref:core/validation/beanvalidation.adoc[Bean Validation] and
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following example uses the `@Valid` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1>
|
||||
@@ -87,6 +98,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following ex
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -110,8 +122,11 @@ argument, you must declare the `@ModelAttribute` argument before it without a re
|
||||
type wrapper, as shown earlier. Alternatively, you can handle any errors through the
|
||||
reactive type, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
|
||||
@@ -124,8 +139,10 @@ reactive type, as the following example shows:
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@Valid @ModelAttribute("pet") petMono: Mono<Pet>): Mono<String> {
|
||||
@@ -138,6 +155,7 @@ reactive type, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note that use of `@ModelAttribute` is optional -- for example, to set its attributes.
|
||||
By default, any argument that is not a simple value type (as determined by
|
||||
|
||||
@@ -9,8 +9,11 @@ is through data binding to a xref:web/webflux/controller/ann-methods/modelattrib
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
class MyForm {
|
||||
|
||||
@@ -32,8 +35,10 @@ as the following example shows:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyForm(
|
||||
val name: String,
|
||||
@@ -49,6 +54,7 @@ as the following example shows:
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
You can also submit multipart requests from non-browser clients in a RESTful service
|
||||
@@ -77,8 +83,11 @@ Content-Transfer-Encoding: 8bit
|
||||
You can access individual parts with `@RequestPart`, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@RequestPart("meta-data") Part metadata, // <1>
|
||||
@@ -86,6 +95,7 @@ You can access individual parts with `@RequestPart`, as the following example sh
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
<2> Using `@RequestPart` to get the file.
|
||||
|
||||
@@ -107,14 +117,18 @@ To deserialize the raw part content (for example, to JSON -- similar to `@Reques
|
||||
you can declare a concrete target `Object`, instead of `Part`, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@RequestPart("meta-data") MetaData metadata) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -136,8 +150,11 @@ in the controller method by declaring the argument with an async wrapper and the
|
||||
error related operators:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
|
||||
@@ -145,28 +162,34 @@ error related operators:
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestBody`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -196,8 +219,11 @@ when uploading. If the file is large enough to be split across multiple buffers,
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { <1>
|
||||
@@ -224,6 +250,7 @@ For example:
|
||||
}));
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestBody`.
|
||||
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
|
||||
followed by additional events belonging to subsequent parts.
|
||||
|
||||
@@ -7,14 +7,18 @@ Similarly to `@SessionAttribute`, you can use the `@RequestAttribute` annotation
|
||||
access pre-existing request attributes created earlier (for example, by a `WebFilter`),
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String handle(@RequestAttribute Client client) { <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestAttribute`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -7,8 +7,11 @@ You can use the `@RequestBody` annotation to have the request body read and dese
|
||||
`Object` through an xref:web/webflux/reactive-spring.adoc#webflux-codecs[HttpMessageReader].
|
||||
The following example uses a `@RequestBody` argument:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@RequestBody Account account) {
|
||||
@@ -16,34 +19,42 @@ The following example uses a `@RequestBody` argument:
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@RequestBody account: Account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Unlike Spring MVC, in WebFlux, the `@RequestBody` method argument supports reactive types
|
||||
and fully non-blocking reading and (client-to-server) streaming.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@RequestBody Mono<Account> account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@RequestBody accounts: Flow<Account>) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can use the xref:web/webflux/config.adoc#webflux-config-message-codecs[HTTP message codecs] option of the xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux Config] to
|
||||
configure or customize message readers.
|
||||
@@ -55,21 +66,27 @@ The exception contains a `BindingResult` with error details and can be handled i
|
||||
controller method by declaring the argument with an async wrapper and then using error
|
||||
related operators:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@Valid @RequestBody Mono<Account> account) {
|
||||
// use one of the onError* operators...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@Valid @RequestBody account: Mono<Account>) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -21,8 +21,11 @@ Keep-Alive 300
|
||||
|
||||
The following example gets the value of the `Accept-Encoding` and `Keep-Alive` headers:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
public void handle(
|
||||
@@ -31,6 +34,7 @@ The following example gets the value of the `Accept-Encoding` and `Keep-Alive` h
|
||||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the value of the `Accept-Encoding` header.
|
||||
<2> Get the value of the `Keep-Alive` header.
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
You can use the `@RequestParam` annotation to bind query parameters to a method argument in a
|
||||
controller. The following code snippet shows the usage:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/pets")
|
||||
@@ -25,6 +28,7 @@ controller. The following code snippet shows the usage:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestParam`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -7,8 +7,11 @@ You can use the `@ResponseBody` annotation on a method to have the return serial
|
||||
to the response body through an xref:web/webflux/reactive-spring.adoc#webflux-codecs[HttpMessageWriter]. The following
|
||||
example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ResponseBody
|
||||
@@ -16,8 +19,10 @@ example shows how to do so:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ResponseBody
|
||||
@@ -25,6 +30,7 @@ example shows how to do so:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
`@ResponseBody` is also supported at the class level, in which case it is inherited by
|
||||
all controller methods. This is the effect of `@RestController`, which is nothing more
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
|
||||
`ResponseEntity` is like xref:web/webflux/controller/ann-methods/responsebody.adoc[`@ResponseBody`] but with status and headers. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/something")
|
||||
public ResponseEntity<String> handle() {
|
||||
@@ -15,8 +18,10 @@
|
||||
return ResponseEntity.ok().eTag(etag).body(body);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/something")
|
||||
fun handle(): ResponseEntity<String> {
|
||||
@@ -25,6 +30,7 @@
|
||||
return ResponseEntity.ok().eTag(etag).build(body)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
WebFlux supports using a single value xref:web-reactive.adoc#webflux-reactive-libraries[reactive type] to
|
||||
produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive types
|
||||
|
||||
@@ -7,14 +7,18 @@ If you need access to pre-existing session attributes that are managed globally
|
||||
(that is, outside the controller -- for example, by a filter) and may or may not be present,
|
||||
you can use the `@SessionAttribute` annotation on a method parameter, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String handle(@SessionAttribute User user) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@SessionAttribute`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -11,8 +11,11 @@ requests to access.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") <1>
|
||||
@@ -20,6 +23,7 @@ Consider the following example:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -38,8 +42,11 @@ it is automatically promoted to and saved in the `WebSession`. It remains there
|
||||
another controller method uses a `SessionStatus` method argument to clear the storage,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
@@ -58,6 +65,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
<2> Using a `SessionStatus` variable.
|
||||
|
||||
|
||||
@@ -24,8 +24,11 @@ related to the request body).
|
||||
|
||||
The following example uses a `@ModelAttribute` method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ModelAttribute
|
||||
public void populateModel(@RequestParam String number, Model model) {
|
||||
@@ -33,8 +36,10 @@ The following example uses a `@ModelAttribute` method:
|
||||
// add more ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ModelAttribute
|
||||
fun populateModel(@RequestParam number: String, model: Model) {
|
||||
@@ -42,25 +47,32 @@ The following example uses a `@ModelAttribute` method:
|
||||
// add more ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example adds one attribute only:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ModelAttribute
|
||||
public Account addAccount(@RequestParam String number) {
|
||||
return accountRepository.findAccount(number);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ModelAttribute
|
||||
fun addAccount(@RequestParam number: String): Account {
|
||||
return accountRepository.findAccount(number);
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: When a name is not explicitly specified, a default name is chosen based on the type,
|
||||
as explained in the javadoc for {api-spring-framework}/core/Conventions.html[`Conventions`].
|
||||
@@ -73,8 +85,11 @@ attributes can be transparently resolved (and the model updated) to their actual
|
||||
at the time of `@RequestMapping` invocation, provided a `@ModelAttribute` argument is
|
||||
declared without a wrapper, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ModelAttribute
|
||||
public void addAccount(@RequestParam String number) {
|
||||
@@ -87,8 +102,10 @@ declared without a wrapper, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.ui.set
|
||||
|
||||
@@ -103,6 +120,7 @@ declared without a wrapper, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
In addition, any model attributes that have a reactive type wrapper are resolved to their
|
||||
@@ -115,8 +133,11 @@ controllers, unless the return value is a `String` that would otherwise be inter
|
||||
as a view name. `@ModelAttribute` can also help to customize the model attribute name,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ModelAttribute("myAccount")
|
||||
@@ -125,8 +146,10 @@ as the following example shows:
|
||||
return account;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ModelAttribute("myAccount")
|
||||
@@ -135,6 +158,7 @@ as the following example shows:
|
||||
return account
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ using `@RequestMapping`, which, by default, matches to all HTTP methods. At the
|
||||
|
||||
The following example uses type and method level mappings:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/persons")
|
||||
@@ -42,8 +45,10 @@ The following example uses type and method level mappings:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/persons")
|
||||
@@ -61,6 +66,7 @@ The following example uses type and method level mappings:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-ann-requestmapping-uri-templates]]
|
||||
@@ -106,29 +112,38 @@ You can map requests by using glob patterns and wildcards:
|
||||
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/owners/{ownerId}/pets/{petId}")
|
||||
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/owners/{ownerId}/pets/{petId}")
|
||||
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
You can declare URI variables at the class and method levels, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/owners/{ownerId}") // <1>
|
||||
@@ -140,6 +155,7 @@ You can declare URI variables at the class and method levels, as the following e
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Class-level URI mapping.
|
||||
<2> Method-level URI mapping.
|
||||
|
||||
@@ -179,22 +195,28 @@ syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5.jar`,
|
||||
extracts the name, version, and file extension:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
|
||||
public void handle(@PathVariable String version, @PathVariable String ext) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
|
||||
fun handle(@PathVariable version: String, @PathVariable ext: String) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
@@ -234,22 +256,28 @@ sorted last instead. If two patterns are both catch-all, the longer is chosen.
|
||||
You can narrow the request mapping based on the `Content-Type` of the request,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping(path = "/pets", consumes = "application/json")
|
||||
public void addPet(@RequestBody Pet pet) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/pets", consumes = ["application/json"])
|
||||
fun addPet(@RequestBody pet: Pet) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The consumes attribute also supports negation expressions -- for example, `!text/plain` means any
|
||||
content type other than `text/plain`.
|
||||
@@ -269,8 +297,11 @@ TIP: `MediaType` provides constants for commonly used media types -- for example
|
||||
You can narrow the request mapping based on the `Accept` request header and the list of
|
||||
content types that a controller method produces, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path = "/pets/{petId}", produces = "application/json")
|
||||
@ResponseBody
|
||||
@@ -278,8 +309,10 @@ content types that a controller method produces, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/pets/{petId}", produces = ["application/json"])
|
||||
@ResponseBody
|
||||
@@ -287,6 +320,7 @@ content types that a controller method produces, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The media type can specify a character set. Negated expressions are supported -- for example,
|
||||
`!text/plain` means any content type other than `text/plain`.
|
||||
@@ -307,14 +341,18 @@ You can narrow request mappings based on query parameter conditions. You can tes
|
||||
presence of a query parameter (`myParam`), for its absence (`!myParam`), or for a
|
||||
specific value (`myParam=myValue`). The following examples tests for a parameter with a value:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") // <1>
|
||||
public void findPet(@PathVariable String petId) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Check that `myParam` equals `myValue`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -329,14 +367,18 @@ specific value (`myParam=myValue`). The following examples tests for a parameter
|
||||
|
||||
You can also use the same with request header conditions, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") // <1>
|
||||
public void findPet(@PathVariable String petId) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Check that `myHeader` equals `myValue`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -401,8 +443,11 @@ You can programmatically register Handler methods, which can be used for dynamic
|
||||
registrations or for advanced cases, such as different instances of the same handler
|
||||
under different URLs. The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfig {
|
||||
@@ -421,6 +466,7 @@ under different URLs. The following example shows how to do so:
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Inject target handlers and the handler mapping for controllers.
|
||||
<2> Prepare the request mapping metadata.
|
||||
<3> Get the handler method.
|
||||
|
||||
@@ -12,8 +12,11 @@ a web component.
|
||||
To enable auto-detection of such `@Controller` beans, you can add component scanning to
|
||||
your Java configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web") // <1>
|
||||
@@ -22,6 +25,7 @@ your Java configuration, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Scan the `org.example.web` package.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -25,18 +25,24 @@ Spring configuration in a WebFlux application typically contains:
|
||||
The configuration is given to `WebHttpHandlerBuilder` to build the processing chain,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext context = ...
|
||||
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val context: ApplicationContext = ...
|
||||
val handler = WebHttpHandlerBuilder.applicationContext(context).build()
|
||||
----
|
||||
======
|
||||
|
||||
The resulting `HttpHandler` is ready for use with a xref:web/webflux/reactive-spring.adoc#webflux-httphandler[server adapter].
|
||||
|
||||
|
||||
@@ -84,42 +84,57 @@ https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-the-Spr
|
||||
The code snippets below show using the `HttpHandler` adapters with each server API:
|
||||
|
||||
*Reactor Netty*
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpHandler handler = ...
|
||||
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
|
||||
HttpServer.create().host(host).port(port).handle(adapter).bindNow();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val handler: HttpHandler = ...
|
||||
val adapter = ReactorHttpHandlerAdapter(handler)
|
||||
HttpServer.create().host(host).port(port).handle(adapter).bindNow()
|
||||
----
|
||||
======
|
||||
|
||||
*Undertow*
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpHandler handler = ...
|
||||
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
|
||||
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
|
||||
server.start();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val handler: HttpHandler = ...
|
||||
val adapter = UndertowHttpHandlerAdapter(handler)
|
||||
val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build()
|
||||
server.start()
|
||||
----
|
||||
======
|
||||
|
||||
*Tomcat*
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpHandler handler = ...
|
||||
Servlet servlet = new TomcatHttpHandlerAdapter(handler);
|
||||
@@ -133,8 +148,10 @@ The code snippets below show using the `HttpHandler` adapters with each server A
|
||||
server.setPort(port);
|
||||
server.start();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val handler: HttpHandler = ...
|
||||
val servlet = TomcatHttpHandlerAdapter(handler)
|
||||
@@ -148,11 +165,15 @@ The code snippets below show using the `HttpHandler` adapters with each server A
|
||||
server.setPort(port)
|
||||
server.start()
|
||||
----
|
||||
======
|
||||
|
||||
*Jetty*
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpHandler handler = ...
|
||||
Servlet servlet = new JettyHttpHandlerAdapter(handler);
|
||||
@@ -168,8 +189,10 @@ The code snippets below show using the `HttpHandler` adapters with each server A
|
||||
server.addConnector(connector);
|
||||
server.start();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val handler: HttpHandler = ...
|
||||
val servlet = JettyHttpHandlerAdapter(handler)
|
||||
@@ -185,6 +208,7 @@ The code snippets below show using the `HttpHandler` adapters with each server A
|
||||
server.addConnector(connector)
|
||||
server.start()
|
||||
----
|
||||
======
|
||||
|
||||
*Servlet Container*
|
||||
|
||||
@@ -277,16 +301,22 @@ Spring ApplicationContext, or that can be registered directly with it:
|
||||
|
||||
`ServerWebExchange` exposes the following method for accessing form data:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<MultiValueMap<String, String>> getFormData();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,Kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
suspend fun getFormData(): MultiValueMap<String, String>
|
||||
----
|
||||
======
|
||||
|
||||
The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse form data
|
||||
(`application/x-www-form-urlencoded`) into a `MultiValueMap`. By default,
|
||||
@@ -300,16 +330,22 @@ The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse
|
||||
|
||||
`ServerWebExchange` exposes the following method for accessing multipart data:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<MultiValueMap<String, Part>> getMultipartData();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,Kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
suspend fun getMultipartData(): MultiValueMap<String, Part>
|
||||
----
|
||||
======
|
||||
|
||||
The `DefaultServerWebExchange` uses the configured
|
||||
`HttpMessageReader<MultiValueMap<String, Part>>` to parse `multipart/form-data`,
|
||||
@@ -621,8 +657,11 @@ headers are masked by default and you must explicitly enable their logging in fu
|
||||
|
||||
The following example shows how to do so for server-side requests:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -634,8 +673,10 @@ The following example shows how to do so for server-side requests:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@@ -646,11 +687,15 @@ The following example shows how to do so for server-side requests:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to do so for client-side requests:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Consumer<ClientCodecConfigurer> consumer = configurer ->
|
||||
configurer.defaultCodecs().enableLoggingRequestDetails(true);
|
||||
@@ -659,8 +704,10 @@ The following example shows how to do so for client-side requests:
|
||||
.exchangeStrategies(strategies -> strategies.codecs(consumer))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }
|
||||
|
||||
@@ -668,6 +715,7 @@ The following example shows how to do so for client-side requests:
|
||||
.exchangeStrategies({ strategies -> strategies.codecs(consumer) })
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-logging-appenders]]
|
||||
@@ -693,8 +741,11 @@ or xref:web/webflux/reactive-spring.adoc#webflux-logging-sensitive-data[logging
|
||||
|
||||
The following example shows how to do so for client-side requests:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient webClient = WebClient.builder()
|
||||
.codecs(configurer -> {
|
||||
@@ -703,8 +754,10 @@ The following example shows how to do so for client-side requests:
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val webClient = WebClient.builder()
|
||||
.codecs({ configurer ->
|
||||
@@ -713,4 +766,5 @@ The following example shows how to do so for client-side requests:
|
||||
})
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -83,8 +83,11 @@ The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
|
||||
annotation enables cross-origin requests on annotated controller methods,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
@@ -102,8 +105,10 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
@@ -121,6 +126,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
By default, `@CrossOrigin` allows:
|
||||
|
||||
@@ -139,8 +145,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
|
||||
`@CrossOrigin` is supported at the class level, too, and is inherited by all methods,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
|
||||
@RestController
|
||||
@@ -158,8 +167,10 @@ public class AccountController {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600)
|
||||
@RestController
|
||||
@@ -176,12 +187,16 @@ public class AccountController {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can use `@CrossOrigin` at both the class level and the method level,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600)
|
||||
@RestController
|
||||
@@ -200,8 +215,10 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600)
|
||||
@RestController
|
||||
@@ -220,6 +237,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -257,8 +275,11 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
|
||||
To enable CORS in the MVC Java config, you can use the `CorsRegistry` callback,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -278,8 +299,10 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -298,6 +321,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -341,8 +365,11 @@ CORS.
|
||||
To configure the filter, pass a `CorsConfigurationSource` to its constructor, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
||||
@@ -359,8 +386,10 @@ following example shows:
|
||||
|
||||
CorsFilter filter = new CorsFilter(source);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val config = CorsConfiguration()
|
||||
|
||||
@@ -377,3 +406,4 @@ following example shows:
|
||||
|
||||
val filter = CorsFilter(source)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -30,8 +30,11 @@ difference that router functions provide not just data, but also behavior.
|
||||
`RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.*;
|
||||
@@ -64,6 +67,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create router using `route()`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -125,44 +129,62 @@ while access to the body is provided through the `body` methods.
|
||||
|
||||
The following example extracts the request body to a `String`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
String string = request.body(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val string = request.body<String>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example extracts the body to a `List<Person>`,
|
||||
where `Person` objects are decoded from a serialized form, such as JSON or XML:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val people = request.body<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to access parameters:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultiValueMap<String, String> params = request.params();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val map = request.params()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-response]]
|
||||
@@ -173,69 +195,94 @@ a `build` method to create it. You can use the builder to set the response statu
|
||||
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
|
||||
content:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Person person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person: Person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val location: URI = ...
|
||||
ServerResponse.created(location).build()
|
||||
----
|
||||
======
|
||||
|
||||
You can also use an asynchronous result as the body, in the form of a `CompletableFuture`,
|
||||
`Publisher`, or any other type supported by the `ReactiveAdapterRegistry`. For instance:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person = webClient.get().retrieve().awaitBody<Person>()
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
|
||||
----
|
||||
======
|
||||
|
||||
If not just the body, but also the status or headers are based on an asynchronous type,
|
||||
you can use the static `async` method on `ServerResponse`, which
|
||||
accepts `CompletableFuture<ServerResponse>`, `Publisher<ServerResponse>`, or
|
||||
any other asynchronous type supported by the `ReactiveAdapterRegistry`. For instance:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
|
||||
.map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
|
||||
ServerResponse.async(asyncResponse);
|
||||
----
|
||||
======
|
||||
|
||||
https://www.w3.org/TR/eventsource/[Server-Sent Events] can be provided via the
|
||||
static `sse` method on `ServerResponse`. The builder provided by that method
|
||||
allows you to send Strings, or other objects as JSON. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public RouterFunction<ServerResponse> sse() {
|
||||
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
|
||||
@@ -258,8 +305,10 @@ allows you to send Strings, or other objects as JSON. For example:
|
||||
// and done at some point
|
||||
sseBuilder.complete();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
fun sse(): RouterFunction<ServerResponse> = router {
|
||||
GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
|
||||
@@ -282,6 +331,7 @@ allows you to send Strings, or other objects as JSON. For example:
|
||||
// and done at some point
|
||||
sseBuilder.complete()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -291,18 +341,24 @@ allows you to send Strings, or other objects as JSON. For example:
|
||||
We can write a handler function as a lambda, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HandlerFunction<ServerResponse> helloWorld =
|
||||
request -> ServerResponse.ok().body("Hello World");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val helloWorld: (ServerRequest) -> ServerResponse =
|
||||
{ ServerResponse.ok().body("Hello World") }
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
That is convenient, but in an application we need multiple functions, and multiple inline
|
||||
@@ -312,8 +368,11 @@ has a similar role as `@Controller` in an annotation-based application.
|
||||
For example, the following class exposes a reactive `Person` repository:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
@@ -350,6 +409,7 @@ public class PersonHandler {
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
@@ -397,8 +457,11 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado
|
||||
apply validation to the request body. For example, given a custom Spring
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class PersonHandler {
|
||||
|
||||
@@ -422,6 +485,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Pers
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
@@ -492,15 +556,20 @@ and so on.
|
||||
The following example uses a request predicate to create a constraint based on the `Accept`
|
||||
header:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
|
||||
request -> ServerResponse.ok().body("Hello World")).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
||||
@@ -510,6 +579,7 @@ header:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can compose multiple request predicates together by using:
|
||||
|
||||
@@ -547,8 +617,11 @@ There are also other ways to compose multiple router functions together:
|
||||
The following example shows the composition of four routes:
|
||||
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.*;
|
||||
@@ -565,6 +638,7 @@ The following example shows the composition of four routes:
|
||||
.add(otherRoute) // <4>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
@@ -611,8 +685,11 @@ When using annotations, you would remove this duplication by using a type-level
|
||||
In WebMvc.fn, path predicates can be shared through the `path` method on the router function builder.
|
||||
For instance, the last few lines of the example above can be improved in the following way by using nested routes:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", builder -> builder // <1>
|
||||
@@ -621,6 +698,7 @@ RouterFunction<ServerResponse> route = route()
|
||||
.POST(handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> Note that second parameter of `path` is a consumer that takes the router builder.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -643,8 +721,11 @@ the `nest` method on the builder.
|
||||
The above still contains some duplication in the form of the shared `Accept`-header predicate.
|
||||
We can further improve by using the `nest` method together with `accept`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
@@ -654,8 +735,10 @@ We can further improve by using the `nest` method together with `accept`:
|
||||
.POST(handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
||||
@@ -669,6 +752,7 @@ We can further improve by using the `nest` method together with `accept`:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-running]]
|
||||
@@ -693,8 +777,11 @@ starter.
|
||||
|
||||
The following example shows a WebFlux Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableMvc
|
||||
@@ -728,8 +815,10 @@ The following example shows a WebFlux Java configuration:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableMvc
|
||||
@@ -760,6 +849,7 @@ The following example shows a WebFlux Java configuration:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -775,8 +865,11 @@ The filter will apply to all routes that are built by the builder.
|
||||
This means that filters defined in nested routes do not apply to "top-level" routes.
|
||||
For instance, consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
@@ -790,6 +883,7 @@ For instance, consider the following example:
|
||||
.after((request, response) -> logResponse(response)) // <2>
|
||||
.build();
|
||||
----
|
||||
======
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
|
||||
@@ -827,8 +921,11 @@ Now we can add a simple security filter to our route, assuming that we have a `S
|
||||
can determine whether a particular path is allowed.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
SecurityManager securityManager = ...
|
||||
|
||||
@@ -848,8 +945,10 @@ The following example shows how to do so:
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.servlet.function.router
|
||||
|
||||
@@ -871,6 +970,7 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
|
||||
We only let the handler function be run when access is allowed.
|
||||
|
||||
@@ -32,8 +32,11 @@ A simple PDF view for a word list could extend
|
||||
`org.springframework.web.servlet.view.document.AbstractPdfView` and implement the
|
||||
`buildPdfDocument()` method, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class PdfWordList extends AbstractPdfView {
|
||||
|
||||
@@ -47,8 +50,10 @@ A simple PDF view for a word list could extend
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PdfWordList : AbstractPdfView() {
|
||||
|
||||
@@ -62,6 +67,7 @@ A simple PDF view for a word list could extend
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
A controller can return such a view either from an external view definition
|
||||
(referencing it by name) or as a `View` instance from the handler method.
|
||||
|
||||
@@ -10,8 +10,11 @@ package `org.springframework.web.servlet.view.feed`.
|
||||
optionally override the `buildFeedMetadata()` method (the default implementation is
|
||||
empty). The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class SampleContentAtomView extends AbstractAtomFeedView {
|
||||
|
||||
@@ -28,8 +31,10 @@ empty). The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class SampleContentAtomView : AbstractAtomFeedView() {
|
||||
|
||||
@@ -44,11 +49,15 @@ empty). The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Similar requirements apply for implementing `AbstractRssFeedView`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class SampleContentRssView extends AbstractRssFeedView {
|
||||
|
||||
@@ -65,8 +74,10 @@ Similar requirements apply for implementing `AbstractRssFeedView`, as the follow
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class SampleContentRssView : AbstractRssFeedView() {
|
||||
|
||||
@@ -81,6 +92,7 @@ Similar requirements apply for implementing `AbstractRssFeedView`, as the follow
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@ integration for using Spring MVC with FreeMarker templates.
|
||||
|
||||
The following example shows how to configure FreeMarker as a view technology:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -37,8 +40,10 @@ The following example shows how to configure FreeMarker as a view technology:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -56,6 +61,7 @@ The following example shows how to configure FreeMarker as a view technology:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to configure the same in XML:
|
||||
|
||||
@@ -364,8 +370,11 @@ and a default value in the form backing object, the HTML resembles the following
|
||||
If your application expects to handle cities by internal codes (for example), you can create the map of
|
||||
codes with suitable keys, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
|
||||
Map<String, String> cityMap = new LinkedHashMap<>();
|
||||
@@ -378,8 +387,10 @@ codes with suitable keys, as the following example shows:
|
||||
return model;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
protected fun referenceData(request: HttpServletRequest): Map<String, *> {
|
||||
val cityMap = linkedMapOf(
|
||||
@@ -390,6 +401,7 @@ codes with suitable keys, as the following example shows:
|
||||
return hashMapOf("cityMap" to cityMap)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The code now produces output where the radio values are the relevant codes, but the
|
||||
user still sees the more user-friendly city names, as follows:
|
||||
|
||||
@@ -15,8 +15,11 @@ NOTE: The Groovy Markup Template engine requires Groovy 2.3.1+.
|
||||
|
||||
The following example shows how to configure the Groovy Markup Template Engine:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -37,8 +40,10 @@ The following example shows how to configure the Groovy Markup Template Engine:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -56,6 +61,7 @@ The following example shows how to configure the Groovy Markup Template Engine:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to configure the same in XML:
|
||||
|
||||
|
||||
@@ -185,8 +185,11 @@ This tag renders an HTML `input` tag with the `type` set to `checkbox`.
|
||||
Assume that our `User` has preferences such as newsletter subscription and a list of
|
||||
hobbies. The following example shows the `Preferences` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class Preferences {
|
||||
|
||||
@@ -219,8 +222,10 @@ hobbies. The following example shows the `Preferences` class:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class Preferences(
|
||||
var receiveNewsletter: Boolean,
|
||||
@@ -228,6 +233,7 @@ hobbies. The following example shows the `Preferences` class:
|
||||
var favouriteWord: String
|
||||
)
|
||||
----
|
||||
======
|
||||
|
||||
The corresponding `form.jsp` could then resemble the following:
|
||||
|
||||
@@ -582,8 +588,11 @@ Assume that we want to display all error messages for the `firstName` and `lastN
|
||||
fields once we submit the form. We have a validator for instances of the `User` class
|
||||
called `UserValidator`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class UserValidator implements Validator {
|
||||
|
||||
@@ -597,8 +606,10 @@ called `UserValidator`, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class UserValidator : Validator {
|
||||
|
||||
@@ -612,6 +623,7 @@ called `UserValidator`, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The `form.jsp` could be as follows:
|
||||
|
||||
@@ -785,8 +797,11 @@ web.xml, as the following example shows:
|
||||
|
||||
The following example shows the corresponding `@Controller` method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RequestMapping(method = RequestMethod.DELETE)
|
||||
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
|
||||
@@ -794,8 +809,10 @@ The following example shows the corresponding `@Controller` method:
|
||||
return "redirect:/owners/" + ownerId;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RequestMapping(method = [RequestMethod.DELETE])
|
||||
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
|
||||
@@ -803,6 +820,7 @@ The following example shows the corresponding `@Controller` method:
|
||||
return "redirect:/owners/$ownerId"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[mvc-view-jsp-formtaglib-html5]]
|
||||
=== HTML5 Tags
|
||||
|
||||
@@ -53,8 +53,11 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -76,8 +79,10 @@ The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -96,6 +101,7 @@ The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the same arrangement in XML:
|
||||
|
||||
@@ -114,8 +120,11 @@ The following example shows the same arrangement in XML:
|
||||
|
||||
The controller would look no different for the Java and XML configurations, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class SampleController {
|
||||
@@ -128,8 +137,10 @@ The controller would look no different for the Java and XML configurations, as t
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class SampleController {
|
||||
@@ -142,6 +153,7 @@ The controller would look no different for the Java and XML configurations, as t
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the Mustache template:
|
||||
|
||||
@@ -176,8 +188,11 @@ browser facilities that are not available in the server-side script engine.
|
||||
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -199,8 +214,10 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -219,6 +236,7 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
|
||||
script engines with templating libraries not designed for concurrency, such as Handlebars or
|
||||
|
||||
@@ -22,8 +22,11 @@ Configuration is standard for a simple Spring web application: The MVC configura
|
||||
has to define an `XsltViewResolver` bean and regular MVC annotation configuration.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@EnableWebMvc
|
||||
@ComponentScan
|
||||
@@ -39,8 +42,10 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@EnableWebMvc
|
||||
@ComponentScan
|
||||
@@ -54,6 +59,7 @@ The following example shows how to do so:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[mvc-view-xslt-controllercode]]
|
||||
@@ -64,8 +70,11 @@ We also need a Controller that encapsulates our word-generation logic.
|
||||
The controller logic is encapsulated in a `@Controller` class, with the
|
||||
handler method being defined as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class XsltController {
|
||||
@@ -88,8 +97,10 @@ handler method being defined as follows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.ui.set
|
||||
|
||||
@@ -114,6 +125,7 @@ handler method being defined as follows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
So far, we have only created a DOM document and added it to the Model map. Note that you
|
||||
can also load an XML file as a `Resource` and use it instead of a custom DOM document.
|
||||
|
||||
@@ -21,8 +21,11 @@ Once the asynchronous request processing feature is xref:web/webmvc/mvc-ann-asyn
|
||||
in the Servlet container, controller methods can wrap any supported controller method
|
||||
return value with `DeferredResult`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/quotes")
|
||||
@ResponseBody
|
||||
@@ -35,8 +38,10 @@ return value with `DeferredResult`, as the following example shows:
|
||||
// From some other thread...
|
||||
deferredResult.setResult(result);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/quotes")
|
||||
@ResponseBody
|
||||
@@ -49,6 +54,7 @@ return value with `DeferredResult`, as the following example shows:
|
||||
// From some other thread...
|
||||
deferredResult.setResult(result)
|
||||
----
|
||||
======
|
||||
|
||||
The controller can produce the return value asynchronously, from a different thread -- for
|
||||
example, in response to an external event (JMS message), a scheduled task, or other event.
|
||||
@@ -61,16 +67,21 @@ example, in response to an external event (JMS message), a scheduled task, or ot
|
||||
A controller can wrap any supported return value with `java.util.concurrent.Callable`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping
|
||||
public Callable<String> processUpload(final MultipartFile file) {
|
||||
return () -> "someView";
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping
|
||||
fun processUpload(file: MultipartFile) = Callable<String> {
|
||||
@@ -78,6 +89,7 @@ as the following example shows:
|
||||
"someView"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The return value can then be obtained by running the given task through the
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `TaskExecutor`.
|
||||
@@ -211,8 +223,11 @@ each object is serialized with an
|
||||
xref:integration/rest-clients.adoc#rest-message-conversion[`HttpMessageConverter`] and written to the
|
||||
response, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/events")
|
||||
public ResponseBodyEmitter handle() {
|
||||
@@ -230,8 +245,10 @@ response, as the following example shows:
|
||||
// and done at some point
|
||||
emitter.complete();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/events")
|
||||
fun handle() = ResponseBodyEmitter().apply {
|
||||
@@ -247,6 +264,7 @@ response, as the following example shows:
|
||||
// and done at some point
|
||||
emitter.complete()
|
||||
----
|
||||
======
|
||||
|
||||
You can also use `ResponseBodyEmitter` as the body in a `ResponseEntity`, letting you
|
||||
customize the status and headers of the response.
|
||||
@@ -267,8 +285,11 @@ https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from t
|
||||
are formatted according to the W3C SSE specification. To produce an SSE
|
||||
stream from a controller, return `SseEmitter`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter handle() {
|
||||
@@ -286,8 +307,10 @@ stream from a controller, return `SseEmitter`, as the following example shows:
|
||||
// and done at some point
|
||||
emitter.complete();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
|
||||
fun handle() = SseEmitter().apply {
|
||||
@@ -303,6 +326,7 @@ stream from a controller, return `SseEmitter`, as the following example shows:
|
||||
// and done at some point
|
||||
emitter.complete()
|
||||
----
|
||||
======
|
||||
|
||||
While SSE is the main option for streaming into browsers, note that Internet Explorer
|
||||
does not support Server-Sent Events. Consider using Spring's
|
||||
@@ -320,8 +344,11 @@ Sometimes, it is useful to bypass message conversion and stream directly to the
|
||||
`OutputStream` (for example, for a file download). You can use the `StreamingResponseBody`
|
||||
return value type to do so, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/download")
|
||||
public StreamingResponseBody handle() {
|
||||
@@ -333,14 +360,17 @@ return value type to do so, as the following example shows:
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/download")
|
||||
fun handle() = StreamingResponseBody {
|
||||
// write...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can use `StreamingResponseBody` as the body in a `ResponseEntity` to
|
||||
customize the status and headers of the response.
|
||||
|
||||
@@ -32,8 +32,11 @@ While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all
|
||||
directives for the `Cache-Control` response header, the `CacheControl` type takes a
|
||||
use case-oriented approach that focuses on the common scenarios:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// Cache for an hour - "Cache-Control: max-age=3600"
|
||||
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
|
||||
@@ -46,8 +49,10 @@ use case-oriented approach that focuses on the common scenarios:
|
||||
// "Cache-Control: max-age=864000, public, no-transform"
|
||||
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// Cache for an hour - "Cache-Control: max-age=3600"
|
||||
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)
|
||||
@@ -60,6 +65,7 @@ use case-oriented approach that focuses on the common scenarios:
|
||||
// "Cache-Control: max-age=864000, public, no-transform"
|
||||
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()
|
||||
----
|
||||
======
|
||||
|
||||
`WebContentGenerator` also accepts a simpler `cachePeriod` property (defined in seconds) that
|
||||
works as follows:
|
||||
@@ -81,8 +87,11 @@ against conditional request headers. A controller can add an `ETag` header and `
|
||||
settings to a `ResponseEntity`, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/book/{id}")
|
||||
public ResponseEntity<Book> showBook(@PathVariable Long id) {
|
||||
@@ -97,8 +106,10 @@ settings to a `ResponseEntity`, as the following example shows:
|
||||
.body(book);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/book/{id}")
|
||||
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {
|
||||
@@ -113,6 +124,7 @@ settings to a `ResponseEntity`, as the following example shows:
|
||||
.body(book)
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
|
||||
@@ -123,8 +135,11 @@ You can also make the check against conditional request headers in the controlle
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RequestMapping
|
||||
public String myHandleMethod(WebRequest request, Model model) {
|
||||
@@ -139,6 +154,7 @@ as the following example shows:
|
||||
return "myViewName";
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Application-specific calculation.
|
||||
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
|
||||
<3> Continue with the request processing.
|
||||
|
||||
@@ -12,8 +12,11 @@ For advanced mode, you can remove `@EnableWebMvc` and extend directly from
|
||||
`DelegatingWebMvcConfiguration` instead of implementing `WebMvcConfigurer`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
public class WebConfig extends DelegatingWebMvcConfiguration {
|
||||
@@ -21,8 +24,10 @@ as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig : DelegatingWebMvcConfiguration() {
|
||||
@@ -30,6 +35,7 @@ as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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` implementations on
|
||||
|
||||
@@ -5,8 +5,11 @@ The MVC namespace does not have an advanced mode. If you need to customize a pro
|
||||
a bean that you cannot change otherwise, you can use the `BeanPostProcessor` lifecycle
|
||||
hook of the Spring `ApplicationContext`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Component
|
||||
public class MyPostProcessor implements BeanPostProcessor {
|
||||
@@ -16,8 +19,10 @@ hook of the Spring `ApplicationContext`, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Component
|
||||
class MyPostProcessor : BeanPostProcessor {
|
||||
@@ -27,6 +32,7 @@ hook of the Spring `ApplicationContext`, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Note that you need to declare `MyPostProcessor` as a bean, either explicitly in XML or
|
||||
|
||||
@@ -16,8 +16,11 @@ more details.
|
||||
In Java configuration, you can customize requested content type resolution, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -30,8 +33,10 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -43,6 +48,7 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
@@ -8,8 +8,11 @@ for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
|
||||
|
||||
To register custom formatters and converters in Java config, use the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -21,8 +24,10 @@ To register custom formatters and converters in Java config, use the following:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -33,6 +38,7 @@ To register custom formatters and converters in Java config, use the following:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To do the same in XML config, use the following:
|
||||
|
||||
@@ -78,8 +84,11 @@ values. This works for forms where dates are represented as Strings with "input"
|
||||
fields. For "date" and "time" form fields, however, browsers use a fixed format defined
|
||||
in the HTML spec. For such cases date and time formatting can be customized as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -93,8 +102,10 @@ in the HTML spec. For such cases date and time formatting can be customized as f
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -107,6 +118,7 @@ in the HTML spec. For such cases date and time formatting can be customized as f
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: See xref:core/validation/format.adoc#format-FormatterRegistrar-SPI[the `FormatterRegistrar` SPI]
|
||||
and the `FormattingConversionServiceFactoryBean` for more information on when to use
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
In Java configuration, you can implement the `WebMvcConfigurer` interface, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -16,8 +19,10 @@ following example shows:
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -26,6 +31,7 @@ following example shows:
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
In XML, you can check attributes and sub-elements of `<mvc:annotation-driven/>`. You can
|
||||
|
||||
@@ -15,8 +15,11 @@ lower than that of the `DefaultServletHttpRequestHandler`, which is `Integer.MAX
|
||||
|
||||
The following example shows how to enable the feature by using the default setup:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -28,8 +31,10 @@ The following example shows how to enable the feature by using the default setup
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -40,6 +45,7 @@ The following example shows how to enable the feature by using the default setup
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
@@ -57,8 +63,11 @@ If the default Servlet has been custom-configured with a different name, or if a
|
||||
different Servlet container is being used where the default Servlet name is unknown,
|
||||
then you must explicitly provide the default Servlet's name, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -70,8 +79,10 @@ then you must explicitly provide the default Servlet's name, as the following ex
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -82,6 +93,7 @@ then you must explicitly provide the default Servlet's name, as the following ex
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
@@ -6,21 +6,27 @@
|
||||
In Java configuration, you can use the `@EnableWebMvc` annotation to enable MVC
|
||||
configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig
|
||||
----
|
||||
======
|
||||
|
||||
In XML configuration, you can use the `<mvc:annotation-driven>` element to enable MVC
|
||||
configuration, as the following example shows:
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
In Java configuration, you can register interceptors to apply to incoming requests, as
|
||||
the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -18,8 +21,10 @@ the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -31,6 +36,7 @@ the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
|
||||
@@ -12,8 +12,11 @@ You can customize `HttpMessageConverter` in Java configuration by overriding
|
||||
The following example adds XML and Jackson JSON converters with a customized
|
||||
`ObjectMapper` instead of the default ones:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -30,8 +33,10 @@ The following example adds XML and Jackson JSON converters with a customized
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -45,6 +50,7 @@ The following example adds XML and Jackson JSON converters with a customized
|
||||
converters.add(MappingJackson2HttpMessageConverter(builder.build()))
|
||||
converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()))
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example,
|
||||
{api-spring-framework}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`]
|
||||
|
||||
@@ -9,8 +9,11 @@ For details on the individual options, see the
|
||||
|
||||
The following example shows how to customize path matching in Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -26,8 +29,10 @@ The following example shows how to customize path matching in Java configuration
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -42,6 +47,7 @@ The following example shows how to customize path matching in Java configuration
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to customize path matching in XML configuration:
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@ so that HTTP conditional requests are supported with `"Last-Modified"` headers.
|
||||
|
||||
The following listing shows how to do so with Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -30,8 +33,10 @@ The following listing shows how to do so with Java configuration:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -44,6 +49,7 @@ The following listing shows how to do so with Java configuration:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
@@ -69,8 +75,11 @@ JavaScript resources used with a module loader.
|
||||
|
||||
The following example shows how to use `VersionResourceResolver` in Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -85,8 +94,10 @@ The following example shows how to use `VersionResourceResolver` in Java configu
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -100,6 +111,7 @@ The following example shows how to use `VersionResourceResolver` in Java configu
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
|
||||
@@ -11,8 +11,11 @@ registered as a global xref:core/validation/validator.adoc[Validator] for use wi
|
||||
In Java configuration, you can customize the global `Validator` instance, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -24,8 +27,10 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -36,6 +41,7 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
@@ -59,8 +65,11 @@ The following example shows how to achieve the same configuration in XML:
|
||||
Note that you can also register `Validator` implementations locally, as the following
|
||||
example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class MyController {
|
||||
@@ -71,8 +80,10 @@ example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class MyController {
|
||||
@@ -83,6 +94,7 @@ example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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 configuration.
|
||||
|
||||
@@ -7,8 +7,11 @@ logic to run before the view generates the response.
|
||||
|
||||
The following example of Java configuration forwards a request for `/` to a view called `home`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -20,8 +23,10 @@ The following example of Java configuration forwards a request for `/` to a view
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -32,6 +37,7 @@ The following example of Java configuration forwards a request for `/` to a view
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example achieves the same thing as the preceding example, but with XML, by
|
||||
using the `<mvc:view-controller>` element:
|
||||
|
||||
@@ -8,8 +8,11 @@ The MVC configuration simplifies the registration of view resolvers.
|
||||
The following Java configuration example configures content negotiation view
|
||||
resolution by using JSP and Jackson as a default `View` for JSON rendering:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -22,8 +25,10 @@ resolution by using JSP and Jackson as a default `View` for JSON rendering:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -35,6 +40,7 @@ resolution by using JSP and Jackson as a default `View` for JSON rendering:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
@@ -75,8 +81,11 @@ The MVC namespace provides dedicated elements. The following example works with
|
||||
In Java configuration, you can add the respective `Configurer` bean,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -96,8 +105,10 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@@ -114,6 +125,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@ exception handling, and more. Annotated controllers have flexible method signatu
|
||||
do not have to extend base classes nor implement specific interfaces.
|
||||
The following example shows a controller defined by annotations:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class HelloController {
|
||||
@@ -22,8 +25,10 @@ The following example shows a controller defined by annotations:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.ui.set
|
||||
|
||||
@@ -37,6 +42,7 @@ The following example shows a controller defined by annotations:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, the method accepts a `Model` and returns a view name as a `String`,
|
||||
but many other options exist and are explained later in this chapter.
|
||||
|
||||
@@ -23,8 +23,11 @@ By contrast, global `@ModelAttribute` and `@InitBinder` methods are applied _bef
|
||||
The `@ControllerAdvice` annotation has attributes that let you narrow the set of controllers
|
||||
and handlers that they apply to. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// Target all Controllers annotated with @RestController
|
||||
@ControllerAdvice(annotations = RestController.class)
|
||||
@@ -38,8 +41,10 @@ and handlers that they apply to. For example:
|
||||
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
|
||||
public class ExampleAdvice3 {}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// Target all Controllers annotated with @RestController
|
||||
@ControllerAdvice(annotations = [RestController::class])
|
||||
@@ -53,6 +58,7 @@ and handlers that they apply to. For example:
|
||||
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
|
||||
class ExampleAdvice3
|
||||
----
|
||||
======
|
||||
|
||||
The selectors in the preceding example are evaluated at runtime and may negatively impact
|
||||
performance if used extensively. See the
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
`@Controller` and xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] classes can have
|
||||
`@ExceptionHandler` methods to handle exceptions from controller methods, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
@@ -20,8 +23,10 @@
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class SimpleController {
|
||||
@@ -34,6 +39,7 @@
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The exception may match against a top-level exception being propagated (e.g. a direct
|
||||
`IOException` being thrown) or against a nested cause within a wrapper exception (e.g.
|
||||
@@ -48,42 +54,54 @@ is used to sort exceptions based on their depth from the thrown exception type.
|
||||
Alternatively, the annotation declaration may narrow the exception types to match,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ExceptionHandler({FileSystemException.class, RemoteException.class})
|
||||
public ResponseEntity<String> handle(IOException ex) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExceptionHandler(FileSystemException::class, RemoteException::class)
|
||||
fun handle(ex: IOException): ResponseEntity<String> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can even use a list of specific exception types with a very generic argument signature,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ExceptionHandler({FileSystemException.class, RemoteException.class})
|
||||
public ResponseEntity<String> handle(Exception ex) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExceptionHandler(FileSystemException::class, RemoteException::class)
|
||||
fun handle(ex: Exception): ResponseEntity<String> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
||||
@@ -21,8 +21,11 @@ do, except for `@ModelAttribute` (command object) arguments. Typically, they are
|
||||
with a `WebDataBinder` argument (for registrations) and a `void` return value.
|
||||
The following listing shows an example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class FormController {
|
||||
@@ -37,6 +40,7 @@ The following listing shows an example:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Defining an `@InitBinder` method.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -61,8 +65,11 @@ Alternatively, when you use a `Formatter`-based setup through a shared
|
||||
`FormattingConversionService`, you can re-use the same approach and register
|
||||
controller-specific `Formatter` implementations, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class FormController {
|
||||
@@ -75,6 +82,7 @@ controller-specific `Formatter` implementations, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Defining an `@InitBinder` method on a custom formatter.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -15,14 +15,18 @@ JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
|
||||
|
||||
The following example shows how to get the cookie value:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
public void handle(@CookieValue("JSESSIONID") String cookie) { <1>
|
||||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the value of the `JSESSIONID` cookie.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -6,22 +6,28 @@
|
||||
`HttpEntity` is more or less identical to using xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[`@RequestBody`] but is based on a
|
||||
container object that exposes request headers and body. The following listing shows an example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(HttpEntity<Account> entity) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(entity: HttpEntity<Account>) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,8 +13,11 @@ which allow rendering only a subset of all fields in an `Object`. To use it with
|
||||
`@ResponseBody` or `ResponseEntity` controller methods, you can use Jackson's
|
||||
`@JsonView` annotation to activate a serialization view class, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
public class UserController {
|
||||
@@ -53,8 +56,10 @@ which allow rendering only a subset of all fields in an `Object`. To use it with
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
class UserController {
|
||||
@@ -72,6 +77,7 @@ which allow rendering only a subset of all fields in an `Object`. To use it with
|
||||
interface WithPasswordView : WithoutPasswordView
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@JsonView` allows an array of view classes, but you can specify only one per
|
||||
controller method. If you need to activate multiple views, you can use a composite interface.
|
||||
@@ -79,8 +85,11 @@ controller method. If you need to activate multiple views, you can use a composi
|
||||
If you want to do the above programmatically, instead of declaring an `@JsonView` annotation,
|
||||
wrap the return value with `MappingJacksonValue` and use it to supply the serialization view:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
public class UserController {
|
||||
@@ -94,8 +103,10 @@ wrap the return value with `MappingJacksonValue` and use it to supply the serial
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
class UserController {
|
||||
@@ -108,12 +119,16 @@ wrap the return value with `MappingJacksonValue` and use it to supply the serial
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
For controllers that rely on view resolution, you can add the serialization view class
|
||||
to the model, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class UserController extends AbstractController {
|
||||
@@ -126,8 +141,10 @@ to the model, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class UserController : AbstractController() {
|
||||
@@ -140,6 +157,7 @@ to the model, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,8 +18,11 @@ method must use a URI variable to mask that variable content and ensure the requ
|
||||
be matched successfully independent of matrix variable order and presence.
|
||||
The following example uses a matrix variable:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /pets/42;q=11;r=22
|
||||
|
||||
@@ -30,8 +33,10 @@ The following example uses a matrix variable:
|
||||
// q == 11
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /pets/42;q=11;r=22
|
||||
|
||||
@@ -42,13 +47,17 @@ The following example uses a matrix variable:
|
||||
// q == 11
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Given that all path segments may contain matrix variables, you may sometimes need to
|
||||
disambiguate which path variable the matrix variable is expected to be in.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /owners/42;q=11/pets/21;q=22
|
||||
|
||||
@@ -61,8 +70,10 @@ The following example shows how to do so:
|
||||
// q2 == 22
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /owners/42;q=11/pets/21;q=22
|
||||
|
||||
@@ -75,12 +86,16 @@ The following example shows how to do so:
|
||||
// q2 == 22
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
A matrix variable may be defined as optional and a default value specified, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /pets/42
|
||||
|
||||
@@ -90,8 +105,10 @@ following example shows:
|
||||
// q == 1
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /pets/42
|
||||
|
||||
@@ -101,11 +118,15 @@ following example shows:
|
||||
// q == 1
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To get all matrix variables, you can use a `MultiValueMap`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
|
||||
|
||||
@@ -118,8 +139,10 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa
|
||||
// petMatrixVars: ["q" : 22, "s" : 23]
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
|
||||
|
||||
@@ -132,6 +155,7 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa
|
||||
// petMatrixVars: ["q" : 22, "s" : 23]
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note that you need to enable the use of matrix variables. In the MVC Java configuration,
|
||||
you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through
|
||||
|
||||
@@ -9,14 +9,18 @@ values from HTTP Servlet request parameters whose names match to field names. Th
|
||||
to as data binding, and it saves you from having to deal with parsing and converting individual
|
||||
query parameters and form fields. The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@ModelAttribute Pet pet) { // <1>
|
||||
// method logic...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Bind an instance of `Pet`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -51,14 +55,18 @@ In the following example, the model attribute name is `account` which matches th
|
||||
path variable `account`, and there is a registered `Converter<String, Account>` which
|
||||
could load the `Account` from a data store:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PutMapping("/accounts/{account}")
|
||||
public String save(@ModelAttribute("account") Account account) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Bind an instance of `Account` using an explicit attribute name.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -82,8 +90,11 @@ Data binding can result in errors. By default, a `BindException` is raised. Howe
|
||||
for such errors in the controller method, you can add a `BindingResult` argument immediately next
|
||||
to the `@ModelAttribute`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { // <1>
|
||||
@@ -93,6 +104,7 @@ to the `@ModelAttribute`, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Adding a `BindingResult` next to the `@ModelAttribute`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -112,8 +124,11 @@ In some cases, you may want access to a model attribute without data binding. Fo
|
||||
cases, you can inject the `Model` into the controller and access it directly or,
|
||||
alternatively, set `@ModelAttribute(binding=false)`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ModelAttribute
|
||||
public AccountForm setUpForm() {
|
||||
@@ -131,6 +146,7 @@ alternatively, set `@ModelAttribute(binding=false)`, as the following example sh
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Setting `@ModelAttribute(binding=false)`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -159,8 +175,11 @@ You can automatically apply validation after data binding by adding the
|
||||
(xref:core/validation/beanvalidation.adoc[Bean Validation] and
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1>
|
||||
@@ -170,6 +189,7 @@ xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following ex
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Validate the `Pet` instance.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -8,8 +8,11 @@ requests with `multipart/form-data` is parsed and accessible as regular request
|
||||
parameters. The following example accesses one regular form field and one uploaded
|
||||
file:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class FileUploadController {
|
||||
@@ -27,8 +30,10 @@ file:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class FileUploadController {
|
||||
@@ -46,6 +51,7 @@ file:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Declaring the argument type as a `List<MultipartFile>` allows for resolving multiple
|
||||
files for the same parameter name.
|
||||
@@ -62,8 +68,11 @@ xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[command
|
||||
and file from the preceding example could be fields on a form object,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
class MyForm {
|
||||
|
||||
@@ -88,8 +97,10 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyForm(val name: String, val file: MultipartFile, ...)
|
||||
|
||||
@@ -107,6 +118,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Multipart requests can also be submitted from non-browser clients in a RESTful service
|
||||
@@ -137,8 +149,11 @@ probably want it deserialized from JSON (similar to `@RequestBody`). Use the
|
||||
`@RequestPart` annotation to access a multipart after converting it with an
|
||||
xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@RequestPart("meta-data") MetaData metadata,
|
||||
@@ -146,8 +161,10 @@ xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter]
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestPart("meta-data") metadata: MetaData,
|
||||
@@ -155,6 +172,7 @@ xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter]
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can use `@RequestPart` in combination with `jakarta.validation.Valid` or use Spring's
|
||||
`@Validated` annotation, both of which cause Standard Bean Validation to be applied.
|
||||
@@ -163,8 +181,11 @@ into a 400 (BAD_REQUEST) response. Alternatively, you can handle validation erro
|
||||
within the controller through an `Errors` or `BindingResult` argument,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
|
||||
@@ -172,8 +193,10 @@ as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData,
|
||||
@@ -181,6 +204,7 @@ as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,8 +26,11 @@ Note that URI template variables from the present request are automatically made
|
||||
available when expanding a redirect URL, and you don't need to explicitly add them
|
||||
through `Model` or `RedirectAttributes`. The following example shows how to define a redirect:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/files/{path}")
|
||||
public String upload(...) {
|
||||
@@ -35,8 +38,10 @@ through `Model` or `RedirectAttributes`. The following example shows how to defi
|
||||
return "redirect:files/{path}";
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/files/{path}")
|
||||
fun upload(...): String {
|
||||
@@ -44,6 +49,7 @@ through `Model` or `RedirectAttributes`. The following example shows how to defi
|
||||
return "redirect:files/{path}"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Another way of passing data to the redirect target is by using flash attributes. Unlike
|
||||
other redirect attributes, flash attributes are saved in the HTTP session (and, hence, do
|
||||
|
||||
@@ -7,14 +7,18 @@ Similar to `@SessionAttribute`, you can use the `@RequestAttribute` annotations
|
||||
access pre-existing request attributes created earlier (for example, by a Servlet `Filter`
|
||||
or `HandlerInterceptor`):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String handle(@RequestAttribute Client client) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@RequestAttribute` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -7,22 +7,28 @@ You can use the `@RequestBody` annotation to have the request body read and dese
|
||||
`Object` through an xref:integration/rest-clients.adoc#rest-message-conversion[`HttpMessageConverter`].
|
||||
The following example uses a `@RequestBody` argument:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@RequestBody Account account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@RequestBody account: Account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
You can use the xref:web/webmvc/mvc-config/message-converters.adoc[Message Converters] option of the xref:web/webmvc/mvc-config.adoc[MVC Config] to
|
||||
@@ -35,21 +41,27 @@ into a 400 (BAD_REQUEST) response. Alternatively, you can handle validation erro
|
||||
within the controller through an `Errors` or `BindingResult` argument,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@Valid @RequestBody Account account, BindingResult result) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@Valid @RequestBody account: Account, result: BindingResult) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -21,8 +21,11 @@ Keep-Alive 300
|
||||
|
||||
The following example gets the value of the `Accept-Encoding` and `Keep-Alive` headers:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/demo")
|
||||
public void handle(
|
||||
@@ -31,6 +34,7 @@ The following example gets the value of the `Accept-Encoding` and `Keep-Alive` h
|
||||
//...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Get the value of the `Accept-Encoding` header.
|
||||
<2> Get the value of the `Keep-Alive` header.
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@ query parameters or form data) to a method argument in a controller.
|
||||
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/pets")
|
||||
@@ -28,6 +31,7 @@ The following example shows how to do so:
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using `@RequestParam` to bind `petId`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -8,8 +8,11 @@ to the response body through an
|
||||
xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter].
|
||||
The following listing shows an example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ResponseBody
|
||||
@@ -17,8 +20,10 @@ The following listing shows an example:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ResponseBody
|
||||
@@ -26,6 +31,7 @@ The following listing shows an example:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
`@ResponseBody` is also supported at the class level, in which case it is inherited by
|
||||
all controller methods. This is the effect of `@RestController`, which is nothing more
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
|
||||
`ResponseEntity` is like xref:web/webmvc/mvc-controller/ann-methods/responsebody.adoc[`@ResponseBody`] but with status and headers. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/something")
|
||||
public ResponseEntity<String> handle() {
|
||||
@@ -15,8 +18,10 @@
|
||||
return ResponseEntity.ok().eTag(etag).body(body);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/something")
|
||||
fun handle(): ResponseEntity<String> {
|
||||
@@ -25,6 +30,7 @@
|
||||
return ResponseEntity.ok().eTag(etag).build(body)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type]
|
||||
to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive
|
||||
|
||||
@@ -8,14 +8,18 @@ If you need access to pre-existing session attributes that are managed globally
|
||||
you can use the `@SessionAttribute` annotation on a method parameter,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RequestMapping("/")
|
||||
public String handle(@SessionAttribute User user) { <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using a `@SessionAttribute` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
|
||||
@@ -11,8 +11,11 @@ requests to access.
|
||||
|
||||
The following example uses the `@SessionAttributes` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
@@ -20,6 +23,7 @@ The following example uses the `@SessionAttributes` annotation:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using the `@SessionAttributes` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -38,8 +42,11 @@ it is automatically promoted to and saved in the HTTP Servlet session. It remain
|
||||
until another controller method uses a `SessionStatus` method argument to clear the
|
||||
storage, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@SessionAttributes("pet") // <1>
|
||||
@@ -57,6 +64,7 @@ storage, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Storing the `Pet` value in the Servlet session.
|
||||
<2> Clearing the `Pet` value from the Servlet session.
|
||||
|
||||
|
||||
@@ -24,8 +24,11 @@ related to the request body.
|
||||
|
||||
The following example shows a `@ModelAttribute` method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ModelAttribute
|
||||
public void populateModel(@RequestParam String number, Model model) {
|
||||
@@ -33,8 +36,10 @@ The following example shows a `@ModelAttribute` method:
|
||||
// add more ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ModelAttribute
|
||||
fun populateModel(@RequestParam number: String, model: Model) {
|
||||
@@ -42,25 +47,32 @@ The following example shows a `@ModelAttribute` method:
|
||||
// add more ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example adds only one attribute:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ModelAttribute
|
||||
public Account addAccount(@RequestParam String number) {
|
||||
return accountRepository.findAccount(number);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ModelAttribute
|
||||
fun addAccount(@RequestParam number: String): Account {
|
||||
return accountRepository.findAccount(number)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
NOTE: When a name is not explicitly specified, a default name is chosen based on the `Object`
|
||||
@@ -74,8 +86,11 @@ attribute. This is typically not required, as it is the default behavior in HTML
|
||||
unless the return value is a `String` that would otherwise be interpreted as a view name.
|
||||
`@ModelAttribute` can also customize the model attribute name, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ModelAttribute("myAccount")
|
||||
@@ -84,8 +99,10 @@ unless the return value is a `String` that would otherwise be interpreted as a v
|
||||
return account;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/accounts/{id}")
|
||||
@ModelAttribute("myAccount")
|
||||
@@ -94,6 +111,7 @@ unless the return value is a `String` that would otherwise be interpreted as a v
|
||||
return account
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ A `@RequestMapping` is still needed at the class level to express shared mapping
|
||||
|
||||
The following example has type and method level mappings:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/persons")
|
||||
@@ -42,8 +45,10 @@ The following example has type and method level mappings:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/persons")
|
||||
@@ -61,6 +66,7 @@ The following example has type and method level mappings:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -102,28 +108,37 @@ Some example patterns:
|
||||
|
||||
Captured URI variables can be accessed with `@PathVariable`. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/owners/{ownerId}/pets/{petId}")
|
||||
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/owners/{ownerId}/pets/{petId}")
|
||||
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
You can declare URI variables at the class and method levels, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/owners/{ownerId}")
|
||||
@@ -135,8 +150,10 @@ You can declare URI variables at the class and method levels, as the following e
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/owners/{ownerId}")
|
||||
@@ -148,6 +165,7 @@ You can declare URI variables at the class and method levels, as the following e
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
URI variables are automatically converted to the appropriate type, or `TypeMismatchException`
|
||||
is raised. Simple types (`int`, `long`, `Date`, and so on) are supported by default and you can
|
||||
@@ -162,22 +180,28 @@ The syntax `{varName:regex}` declares a URI variable with a regular expression t
|
||||
syntax of `{varName:regex}`. For example, given URL `"/spring-web-3.0.5.jar"`, the following method
|
||||
extracts the name, version, and file extension:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
|
||||
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
|
||||
fun handle(@PathVariable name: String, @PathVariable version: String, @PathVariable ext: String) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
@@ -274,14 +298,18 @@ recommendations related to RFD.
|
||||
You can narrow the request mapping based on the `Content-Type` of the request,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@PostMapping(path = "/pets", consumes = "application/json") // <1>
|
||||
public void addPet(@RequestBody Pet pet) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using a `consumes` attribute to narrow the mapping by the content type.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -312,8 +340,11 @@ TIP: `MediaType` provides constants for commonly used media types, such as
|
||||
You can narrow the request mapping based on the `Accept` request header and the list of
|
||||
content types that a controller method produces, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path = "/pets/{petId}", produces = "application/json") // <1>
|
||||
@ResponseBody
|
||||
@@ -321,6 +352,7 @@ content types that a controller method produces, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Using a `produces` attribute to narrow the mapping by the content type.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -353,14 +385,18 @@ You can narrow request mappings based on request parameter conditions. You can t
|
||||
presence of a request parameter (`myParam`), for the absence of one (`!myParam`), or for a
|
||||
specific value (`myParam=myValue`). The following example shows how to test for a specific value:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") // <1>
|
||||
public void findPet(@PathVariable String petId) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Testing whether `myParam` equals `myValue`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -375,14 +411,18 @@ specific value (`myParam=myValue`). The following example shows how to test for
|
||||
|
||||
You can also use the same with request header conditions, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") // <1>
|
||||
public void findPet(@PathVariable String petId) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Testing whether `myHeader` equals `myValue`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -455,8 +495,11 @@ You can programmatically register handler methods, which you can use for dynamic
|
||||
registrations or for advanced cases, such as different instances of the same handler
|
||||
under different URLs. The following example registers a handler method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfig {
|
||||
@@ -474,6 +517,7 @@ under different URLs. The following example registers a handler method:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Inject the target handler and the handler mapping for controllers.
|
||||
<2> Prepare the request mapping meta data.
|
||||
<3> Get the handler method.
|
||||
|
||||
@@ -12,8 +12,11 @@ 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, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web")
|
||||
@@ -22,8 +25,10 @@ your Java configuration, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web")
|
||||
@@ -32,6 +37,7 @@ your Java configuration, as the following example shows:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
|
||||
@@ -18,8 +18,11 @@ The following example of the Java configuration registers and initializes
|
||||
the `DispatcherServlet`, which is auto-detected by the Servlet container
|
||||
(see xref:web/webmvc/mvc-servlet/container-config.adoc[Servlet Config]):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyWebApplicationInitializer implements WebApplicationInitializer {
|
||||
|
||||
@@ -38,8 +41,10 @@ the `DispatcherServlet`, which is auto-detected by the Servlet container
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyWebApplicationInitializer : WebApplicationInitializer {
|
||||
|
||||
@@ -57,6 +62,7 @@ the `DispatcherServlet`, which is auto-detected by the Servlet container
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In addition to using the ServletContext API directly, you can also extend
|
||||
`AbstractAnnotationConfigDispatcherServletInitializer` and override specific methods
|
||||
|
||||
@@ -5,8 +5,11 @@ In a Servlet environment, you have the option of configuring the Servlet contain
|
||||
programmatically as an alternative or in combination with a `web.xml` file.
|
||||
The following example registers a `DispatcherServlet`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
|
||||
@@ -23,8 +26,10 @@ The following example registers a `DispatcherServlet`:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.WebApplicationInitializer
|
||||
|
||||
@@ -40,6 +45,7 @@ The following example registers a `DispatcherServlet`:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
`WebApplicationInitializer` is an interface provided by Spring MVC that ensures your
|
||||
@@ -52,8 +58,11 @@ location of the `DispatcherServlet` configuration.
|
||||
This is recommended for applications that use Java-based Spring configuration, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@@ -73,8 +82,10 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
|
||||
|
||||
@@ -91,12 +102,16 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you use XML-based Spring configuration, you should extend directly from
|
||||
`AbstractDispatcherServletInitializer`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
|
||||
|
||||
@@ -118,8 +133,10 @@ If you use XML-based Spring configuration, you should extend directly from
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
|
||||
|
||||
@@ -138,13 +155,17 @@ If you use XML-based Spring configuration, you should extend directly from
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
`AbstractDispatcherServletInitializer` also provides a convenient way to add `Filter`
|
||||
instances and have them be automatically mapped to the `DispatcherServlet`, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
|
||||
|
||||
@@ -157,8 +178,10 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
|
||||
|
||||
@@ -169,6 +192,7 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Each filter is added with a default name based on its concrete type and automatically
|
||||
mapped to the `DispatcherServlet`.
|
||||
|
||||
@@ -24,8 +24,11 @@ image::mvc-context-hierarchy.png[]
|
||||
|
||||
The following example configures a `WebApplicationContext` hierarchy:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@@ -45,8 +48,10 @@ The following example configures a `WebApplicationContext` hierarchy:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
|
||||
|
||||
@@ -63,6 +68,7 @@ The following example configures a `WebApplicationContext` hierarchy:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
TIP: If an application context hierarchy is not required, applications can return all
|
||||
configuration through `getRootConfigClasses()` and `null` from `getServletConfigClasses()`.
|
||||
|
||||
@@ -74,8 +74,11 @@ Servlet container makes an ERROR dispatch within the container to the configured
|
||||
to a `@Controller`, which could be implemented to return an error view name with a model
|
||||
or to render a JSON response, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RestController
|
||||
public class ErrorController {
|
||||
@@ -89,8 +92,10 @@ or to render a JSON response, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
class ErrorController {
|
||||
@@ -104,6 +109,7 @@ or to render a JSON response, as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
TIP: The Servlet API does not provide a way to create error page mappings in Java. You can,
|
||||
however, use both a `WebApplicationInitializer` and a minimal `web.xml`.
|
||||
|
||||
@@ -25,8 +25,11 @@ through the `enableLoggingRequestDetails` property on `DispatcherServlet`.
|
||||
|
||||
The following example shows how to do so by using Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyInitializer
|
||||
extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
@@ -53,8 +56,10 @@ public class MyInitializer
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
|
||||
|
||||
@@ -75,6 +80,7 @@ public class MyInitializer
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,8 +28,11 @@ To do so:
|
||||
|
||||
The following example shows how to set a `MultipartConfigElement` on the Servlet registration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@@ -44,8 +47,10 @@ The following example shows how to set a `MultipartConfigElement` on the Servlet
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
|
||||
|
||||
@@ -59,6 +64,7 @@ The following example shows how to set a `MultipartConfigElement` on the Servlet
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Once the Servlet multipart configuration is in place, you can add a bean of type
|
||||
`StandardServletMultipartResolver` with a name of `multipartResolver`.
|
||||
|
||||
@@ -15,8 +15,11 @@ include::partial$web/web-uris.adoc[leveloffset=+1]
|
||||
You can use `ServletUriComponentsBuilder` to create URIs relative to the current request,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpServletRequest request = ...
|
||||
|
||||
@@ -26,8 +29,10 @@ as the following example shows:
|
||||
.replaceQueryParam("accountId", "{id}")
|
||||
.build("123");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val request: HttpServletRequest = ...
|
||||
|
||||
@@ -37,11 +42,15 @@ as the following example shows:
|
||||
.replaceQueryParam("accountId", "{id}")
|
||||
.build("123")
|
||||
----
|
||||
======
|
||||
|
||||
You can create URIs relative to the context path, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpServletRequest request = ...
|
||||
|
||||
@@ -52,8 +61,10 @@ You can create URIs relative to the context path, as the following example shows
|
||||
.build()
|
||||
.toUri();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val request: HttpServletRequest = ...
|
||||
|
||||
@@ -64,12 +75,16 @@ You can create URIs relative to the context path, as the following example shows
|
||||
.build()
|
||||
.toUri()
|
||||
----
|
||||
======
|
||||
|
||||
You can create URIs relative to a Servlet (for example, `/main/{asterisk}`),
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpServletRequest request = ...
|
||||
|
||||
@@ -80,8 +95,10 @@ as the following example shows:
|
||||
.build()
|
||||
.toUri();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val request: HttpServletRequest = ...
|
||||
|
||||
@@ -92,6 +109,7 @@ as the following example shows:
|
||||
.build()
|
||||
.toUri()
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As of 5.1, `ServletUriComponentsBuilder` ignores information from the `Forwarded` and
|
||||
`X-Forwarded-*` headers, which specify the client-originated address. Consider using the
|
||||
@@ -106,8 +124,11 @@ such headers.
|
||||
Spring MVC provides a mechanism to prepare links to controller methods. For example,
|
||||
the following MVC controller allows for link creation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/hotels/{hotel}")
|
||||
@@ -119,8 +140,10 @@ the following MVC controller allows for link creation:
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/hotels/{hotel}")
|
||||
@@ -132,25 +155,32 @@ the following MVC controller allows for link creation:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can prepare a link by referring to the method by name, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
UriComponents uriComponents = MvcUriComponentsBuilder
|
||||
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
|
||||
|
||||
URI uri = uriComponents.encode().toUri();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uriComponents = MvcUriComponentsBuilder
|
||||
.fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42)
|
||||
|
||||
val uri = uriComponents.encode().toUri()
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, we provide actual method argument values (in this case, the long value: `21`)
|
||||
to be used as a path variable and inserted into the URL. Furthermore, we provide the
|
||||
@@ -163,22 +193,28 @@ There are additional ways to use `MvcUriComponentsBuilder`. For example, you can
|
||||
akin to mock testing through proxies to avoid referring to the controller method by name, as the following example shows
|
||||
(the example assumes static import of `MvcUriComponentsBuilder.on`):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
UriComponents uriComponents = MvcUriComponentsBuilder
|
||||
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
|
||||
|
||||
URI uri = uriComponents.encode().toUri();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uriComponents = MvcUriComponentsBuilder
|
||||
.fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)
|
||||
|
||||
val uri = uriComponents.encode().toUri()
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Controller method signatures are limited in their design when they are supposed to be usable for
|
||||
link creation with `fromMethodCall`. Aside from needing a proper parameter signature,
|
||||
@@ -200,8 +236,11 @@ For such cases, you can use the static `fromXxx` overloaded methods that accept
|
||||
with a base URL and then use the instance-based `withXxx` methods. For example, the
|
||||
following listing uses `withMethodCall`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
|
||||
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
|
||||
@@ -209,8 +248,10 @@ following listing uses `withMethodCall`:
|
||||
|
||||
URI uri = uriComponents.encode().toUri();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en")
|
||||
val builder = MvcUriComponentsBuilder.relativeTo(base)
|
||||
@@ -218,6 +259,7 @@ following listing uses `withMethodCall`:
|
||||
|
||||
val uri = uriComponents.encode().toUri()
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As of 5.1, `MvcUriComponentsBuilder` ignores information from the `Forwarded` and
|
||||
`X-Forwarded-*` headers, which specify the client-originated address. Consider using the
|
||||
@@ -234,8 +276,11 @@ by referring to the implicitly or explicitly assigned name for each request mapp
|
||||
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@RequestMapping("/people/{id}/addresses")
|
||||
public class PersonAddressController {
|
||||
@@ -244,8 +289,10 @@ Consider the following example:
|
||||
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RequestMapping("/people/{id}/addresses")
|
||||
class PersonAddressController {
|
||||
@@ -254,6 +301,7 @@ Consider the following example:
|
||||
fun getAddress(@PathVariable country: String): HttpEntity<PersonAddress> { ... }
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Given the preceding controller, you can prepare a link from a JSP, as follows:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user