Render reference documentation with Asciidoctor
This commit removes docbook from the documentation toolchain and instead makes use of asciidoctor to render the reference documentation in HTML and PDF formats. The main Gradle build has been refactored with the documentation tasks and sniffer tasks extracted to their own gradle file in the "gradle" folder. A new asciidoctor Spring theme is also used to render the HTML5 backend. Issue: SPR-14997
This commit is contained in:
244
src/docs/asciidoc/web/web-cors.adoc
Normal file
244
src/docs/asciidoc/web/web-cors.adoc
Normal file
@@ -0,0 +1,244 @@
|
||||
[[cors]]
|
||||
= CORS Support
|
||||
|
||||
== Introduction
|
||||
|
||||
For security reasons, browsers prohibit AJAX calls to resources residing outside the
|
||||
current origin. For example, as you're checking your bank account in one tab, you
|
||||
could have the evil.com website open in another tab. The scripts from evil.com should not
|
||||
be able to make AJAX requests to your bank API (e.g., withdrawing money from your account!)
|
||||
using your credentials.
|
||||
|
||||
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing]
|
||||
(CORS) is a http://www.w3.org/TR/cors/[W3C specification] implemented by
|
||||
http://caniuse.com/#feat=cors[most browsers] that allows you to specify in a flexible
|
||||
way what kind of cross domain requests are authorized, instead of using some less secured
|
||||
and less powerful hacks like IFRAME or JSONP.
|
||||
|
||||
As of Spring Framework 4.2, CORS is supported out of the box. CORS requests
|
||||
(https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java#L906[including preflight ones with an `OPTIONS` method])
|
||||
are automatically dispatched to the various registered ``HandlerMapping``s. They handle
|
||||
CORS preflight requests and intercept CORS simple and actual requests thanks to a
|
||||
{api-spring-framework}/web/cors/CorsProcessor.html[CorsProcessor]
|
||||
implementation (https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java[DefaultCorsProcessor]
|
||||
by default) in order to add the relevant CORS response headers (like `Access-Control-Allow-Origin`)
|
||||
based on the CORS configuration you have provided.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Since CORS requests are automatically dispatched, you *do not need* to change the
|
||||
`DispatcherServlet` `dispatchOptionsRequest` init parameter value; using its default value
|
||||
(`false`) is the recommended approach.
|
||||
====
|
||||
|
||||
== Controller method CORS configuration
|
||||
|
||||
You can add an
|
||||
{api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
|
||||
annotation to your `@RequestMapping` annotated handler method in order to enable CORS on
|
||||
it. By default `@CrossOrigin` allows all origins and the HTTP methods specified in the
|
||||
`@RequestMapping` annotation:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin
|
||||
@RequestMapping("/{id}")
|
||||
public Account retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
|
||||
public void remove(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
It is also possible to enable CORS for the whole controller:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@RequestMapping("/{id}")
|
||||
public Account retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
|
||||
public void remove(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In the above example CORS support is enabled for both the `retrieve()` and the `remove()`
|
||||
handler methods, and you can also see how you can customize the CORS configuration using
|
||||
`@CrossOrigin` attributes.
|
||||
|
||||
You can even use both controller-level and method-level CORS configurations; Spring will
|
||||
then combine attributes from both annotations to create merged CORS configuration.
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600)
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin("http://domain2.com")
|
||||
@RequestMapping("/{id}")
|
||||
public Account retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
|
||||
public void remove(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Global CORS configuration
|
||||
|
||||
In addition to fine-grained, annotation-based configuration you'll probably want to
|
||||
define some global CORS configuration as well. This is similar to using filters but can
|
||||
be declared within Spring MVC and combined with fine-grained `@CrossOrigin` configuration.
|
||||
By default all origins and `GET`, `HEAD`, and `POST` methods are allowed.
|
||||
|
||||
=== JavaConfig
|
||||
|
||||
Enabling CORS for the whole application is as simple as:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
You can easily change any properties, as well as only apply this CORS configuration to a
|
||||
specific path pattern:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("http://domain2.com")
|
||||
.allowedMethods("PUT", "DELETE")
|
||||
.allowedHeaders("header1", "header2", "header3")
|
||||
.exposedHeaders("header1", "header2")
|
||||
.allowCredentials(false).maxAge(3600);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== XML namespace
|
||||
|
||||
The following minimal XML configuration enables CORS for the `/**` path pattern with
|
||||
the same default properties as with the aforementioned JavaConfig examples:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<mvc:cors>
|
||||
<mvc:mapping path="/**" />
|
||||
</mvc:cors>
|
||||
----
|
||||
|
||||
It is also possible to declare several CORS mappings with customized properties:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<mvc:cors>
|
||||
|
||||
<mvc:mapping path="/api/**"
|
||||
allowed-origins="http://domain1.com, http://domain2.com"
|
||||
allowed-methods="GET, PUT"
|
||||
allowed-headers="header1, header2, header3"
|
||||
exposed-headers="header1, header2" allow-credentials="false"
|
||||
max-age="123" />
|
||||
|
||||
<mvc:mapping path="/resources/**"
|
||||
allowed-origins="http://domain1.com" />
|
||||
|
||||
</mvc:cors>
|
||||
----
|
||||
|
||||
== Advanced Customization
|
||||
|
||||
{api-spring-framework}/web/cors/CorsConfiguration.html[CorsConfiguration]
|
||||
allows you to specify how the CORS requests should be processed: allowed origins, headers, methods, etc.
|
||||
It can be provided in various ways:
|
||||
|
||||
* {api-spring-framework}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfiguration-java.util.Map-[`AbstractHandlerMapping#setCorsConfiguration()`]
|
||||
allows to specify a `Map` with several {api-spring-framework}/web/cors/CorsConfiguration.html[CorsConfiguration]
|
||||
instances mapped to path patterns like `/api/**`.
|
||||
* Subclasses can provide their own `CorsConfiguration` by overriding the
|
||||
`AbstractHandlerMapping#getCorsConfiguration(Object, HttpServletRequest)` method.
|
||||
* Handlers can implement the {api-spring-framework}/web/cors/CorsConfigurationSource.html[`CorsConfigurationSource`]
|
||||
interface (like https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java[`ResourceHttpRequestHandler`]
|
||||
now does) in order to provide a {api-spring-framework}/web/cors/CorsConfiguration.html[CorsConfiguration]
|
||||
instance for each request.
|
||||
|
||||
== Filter based CORS support
|
||||
|
||||
In order to support CORS with filter-based security frameworks like
|
||||
http://projects.spring.io/spring-security/[Spring Security], or
|
||||
with other libraries that do not support natively CORS, Spring Framework also
|
||||
provides a http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/filter/CorsFilter.html[`CorsFilter`].
|
||||
Instead of using `@CrossOrigin` or `WebMvcConfigurer#addCorsMappings(CorsRegistry)`, you
|
||||
need to register a custom filter defined like bellow:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
public class MyCorsFilter extends CorsFilter {
|
||||
|
||||
public MyCorsFilter() {
|
||||
super(configurationSource());
|
||||
}
|
||||
|
||||
private static UrlBasedCorsConfigurationSource configurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedOrigin("http://domain1.com");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
You need to ensure that `CorsFilter` is ordered before the other filters, see
|
||||
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework#filter-based-cors-support[this blog post]
|
||||
about how to configure Spring Boot accordingly.
|
||||
244
src/docs/asciidoc/web/web-flux-functional.adoc
Normal file
244
src/docs/asciidoc/web/web-flux-functional.adoc
Normal file
@@ -0,0 +1,244 @@
|
||||
==== Functional Programming Model
|
||||
|
||||
NOTE: This section is to be merged into `web-flux.adoc`.
|
||||
|
||||
===== HandlerFunctions
|
||||
|
||||
Incoming HTTP requests are handled by a **`HandlerFunction`**, which is essentially a function that
|
||||
takes a `ServerRequest` and returns a `Mono<ServerResponse>`. The annotation counterpart to a
|
||||
handler function would be a method with `@RequestMapping`.
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK-8 friendly access
|
||||
to the underlying HTTP messages. Both are fully reactive by
|
||||
building on top of Reactor: the request expose the body as `Flux` or `Mono`; the response accepts
|
||||
any http://www.reactive-streams.org[Reactive Streams] `Publisher` as body.
|
||||
|
||||
`ServerRequest` gives access to various HTTP request elements:
|
||||
the method, URI, query parameters, and -- through the separate `ServerRequest.Headers` interface
|
||||
-- the headers. Access to the body is provided through the `body` methods. For instance, this is
|
||||
how to extract the request body into a `Mono<String>`:
|
||||
|
||||
Mono<String> string = request.bodyToMono(String.class);
|
||||
|
||||
And here is how to extract the body into a `Flux`, where `Person` is a class that can be
|
||||
deserialised from the contents of the body (i.e. `Person` is supported by Jackson if the body
|
||||
contains JSON, or JAXB if XML).
|
||||
|
||||
Flux<Person> people = request.bodyToFlux(Person.class);
|
||||
|
||||
The two methods above (`bodyToMono` and `bodyToFlux`) are, in fact, convenience methods that use the
|
||||
generic `ServerRequest.body(BodyExtractor)` method. `BodyExtractor` is
|
||||
a functional strategy interface that allows you to write your own extraction logic, but common
|
||||
`BodyExtractor` instances can be found in the `BodyExtractors` utility class. So, the above
|
||||
examples can be replaced with:
|
||||
|
||||
Mono<String> string = request.body(BodyExtractors.toMono(String.class);
|
||||
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);
|
||||
|
||||
Similarly, `ServerResponse` provides access to the HTTP response. Since it is immutable, you create
|
||||
a `ServerResponse` with a builder. The builder allows you to set the response status, add response
|
||||
headers, and provide a body. For instance, this is how to create a response with a 200 OK status,
|
||||
a JSON content-type, and a body:
|
||||
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
|
||||
|
||||
And here is how to build a response with a 201 Created status, Location header, and empty body:
|
||||
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
|
||||
|
||||
Putting these together allows us to create a `HandlerFunction`. For instance, here is an example
|
||||
of a simple "Hello World" handler lambda, that returns a response with a 200 status and a body
|
||||
based on a String:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
HandlerFunction<ServerResponse> helloWorld =
|
||||
request -> ServerResponse.ok().body(fromObject("Hello World"));
|
||||
----
|
||||
|
||||
Writing handler functions as lambda's, as we do above, is convenient, but perhaps lacks in
|
||||
readability and becomes less maintainable when dealing with multiple functions. Therefore, it is
|
||||
recommended to group related handler functions into a handler or controller class. For example,
|
||||
here is a class that exposes a reactive `Person` repository:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
|
||||
public class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> listPeople(ServerRequest request) { // <1>
|
||||
Flux<Person> people = repository.allPeople();
|
||||
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) { // <2>
|
||||
Mono<Person> person = request.bodyToMono(Person.class);
|
||||
return ServerResponse.ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> getPerson(ServerRequest request) { // <3>
|
||||
int personId = Integer.valueOf(request.pathVariable("id"));
|
||||
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
|
||||
Mono<Person> personMono = this.repository.getPerson(personId);
|
||||
return personMono
|
||||
.then(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
|
||||
.otherwiseIfEmpty(notFound);
|
||||
}
|
||||
}
|
||||
----
|
||||
<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.
|
||||
Note that `PersonRepository.savePerson(Person)` returns `Mono<Void>`: an empty Mono that emits
|
||||
a completion signal when the person has been read from the request and stored. So we use the
|
||||
`build(Publisher<Void>)` method to send a response when that completion signal is received, i.e.
|
||||
when the `Person` has been saved.
|
||||
<3> `getPerson` is a handler function that returns a single person, identified via the path
|
||||
variable `id`. We retrieve that `Person` via the repository, and create a JSON response if it is
|
||||
found. If it is not found, we use `otherwiseIfEmpty(Mono<T>)` to return a 404 Not Found response.
|
||||
|
||||
===== RouterFunctions
|
||||
|
||||
Incoming requests are routed to handler functions with a **`RouterFunction`**, which is a function
|
||||
that takes a `ServerRequest`, and returns a `Mono<HandlerFunction>`. If a request matches a
|
||||
particular route, a handler function is returned; otherwise it returns an empty `Mono`. The
|
||||
`RouterFunction` has a similar purpose as the `@RequestMapping` annotation in `@Controller` classes.
|
||||
|
||||
Typically, you do not write router functions yourself, but rather use
|
||||
`RouterFunctions.route(RequestPredicate, HandlerFunction)` to
|
||||
create one using a request predicate and handler function. If the predicate applies, the request is
|
||||
routed to the given handler function; otherwise no routing is performed, resulting in a
|
||||
404 Not Found response.
|
||||
Though you can write your own `RequestPredicate`, you do not have to: the `RequestPredicates`
|
||||
utility class offers commonly used predicates, such matching based on path, HTTP method,
|
||||
content-type, etc.
|
||||
Using `route`, we can route to our "Hello World" handler function:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
RouterFunction<ServerResponse> helloWorldRoute =
|
||||
RouterFunctions.route(RequestPredicates.path("/hello-world"),
|
||||
request -> Response.ok().body(fromObject("Hello World")));
|
||||
----
|
||||
|
||||
Two router functions can be composed into a new router function that routes to either handler
|
||||
function: if the predicate of the first route does not match, the second is evaluated.
|
||||
Composed router functions are evaluated in order, so it makes sense to put specific functions
|
||||
before generic ones.
|
||||
You can compose two router functions by calling `RouterFunction.and(RouterFunction)`, or by calling
|
||||
`RouterFunction.andRoute(RequestPredicate, HandlerFunction)`, which is a convenient combination
|
||||
of `RouterFunction.and()` with `RouterFunctions.route()`.
|
||||
|
||||
Given the `PersonHandler` we showed above, we can now define a router function that routes to the
|
||||
respective handler functions.
|
||||
We use https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html[method-references]
|
||||
to refer to the handler functions:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
||||
|
||||
PersonRepository repository = ...
|
||||
PersonHandler handler = new PersonHandler(repository);
|
||||
|
||||
RouterFunction<ServerResponse> personRoute =
|
||||
route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
|
||||
.andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
|
||||
.andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);
|
||||
----
|
||||
|
||||
Besides router functions, you can also compose request predicates, by calling
|
||||
`RequestPredicate.and(RequestPredicate)` or `RequestPredicate.or(RequestPredicate)`.
|
||||
These work as expected: for `and` the resulting predicate matches if *both* given predicates match;
|
||||
`or` matches if *either* predicate does.
|
||||
Most of the predicates found in `RequestPredicates` are compositions.
|
||||
For instance, `RequestPredicates.GET(String)` is a composition of
|
||||
`RequestPredicates.method(HttpMethod)` and `RequestPredicates.path(String)`.
|
||||
|
||||
====== Running a Server
|
||||
|
||||
Now there is just one piece of the puzzle missing: running a router function in an HTTP server.
|
||||
You can convert a router function into a `HttpHandler` by using
|
||||
`RouterFunctions.toHttpHandler(RouterFunction)`.
|
||||
The `HttpHandler` allows you to run on a wide variety of reactive runtimes: Reactor Netty,
|
||||
RxNetty, Servlet 3.1+, and Undertow.
|
||||
Here is how we run a router function in Reactor Netty, for instance:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
RouterFunction<ServerResponse> route = ...
|
||||
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
|
||||
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
|
||||
HttpServer server = HttpServer.create(HOST, PORT);
|
||||
server.newHandler(adapter).block();
|
||||
----
|
||||
|
||||
For Tomcat it looks like this:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
RouterFunction<ServerResponse> route = ...
|
||||
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
|
||||
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
|
||||
Tomcat server = new Tomcat();
|
||||
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
|
||||
Tomcat.addServlet(rootContext, "servlet", servlet);
|
||||
rootContext.addServletMapping("/", "servlet");
|
||||
tomcatServer.start();
|
||||
----
|
||||
|
||||
|
||||
|
||||
TODO: DispatcherHandler
|
||||
|
||||
===== HandlerFilterFunction
|
||||
|
||||
Routes mapped by a router function can be filtered by calling
|
||||
`RouterFunction.filter(HandlerFilterFunction)`, where `HandlerFilterFunction` is essentially a
|
||||
function that takes a `ServerRequest` and `HandlerFunction`, and returns a `ServerResponse`.
|
||||
The handler function parameter represents the next element in the chain: this is typically the
|
||||
`HandlerFunction` that is routed to, but can also be another `FilterFunction` if multiple filters
|
||||
are applied.
|
||||
With annotations, similar functionality can be achieved using `@ControllerAdvice` and/or a `ServletFilter`.
|
||||
Let's add a simple security filter to our route, assuming that we have a `SecurityManager` that
|
||||
can determine whether a particular path is allowed:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||
|
||||
SecurityManager securityManager = ...
|
||||
RouterFunction<ServerResponse> route = ...
|
||||
|
||||
RouterFunction<ServerResponse> filteredRoute =
|
||||
route.filter(request, next) -> {
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
return next.handle(request);
|
||||
}
|
||||
else {
|
||||
return ServerResponse.status(UNAUTHORIZED).build();
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
You can see in this example that invoking the `next.handle(ServerRequest)` is optional: we only
|
||||
allow the handler function to be executed when access is allowed.
|
||||
393
src/docs/asciidoc/web/web-flux.adoc
Normal file
393
src/docs/asciidoc/web/web-flux.adoc
Normal file
@@ -0,0 +1,393 @@
|
||||
[[web-reactive]]
|
||||
= WebFlux framework
|
||||
|
||||
This section provides basic information on the reactive programming
|
||||
support for Web applications in Spring Framework 5.
|
||||
|
||||
|
||||
[[web-reactive-intro]]
|
||||
== Introduction
|
||||
|
||||
|
||||
[[web-reactive-programming]]
|
||||
=== What is Reactive Programming?
|
||||
|
||||
In plain terms reactive programming is about non-blocking applications that are asynchronous
|
||||
and event-driven and require a small number of threads to scale vertically (i.e. within the
|
||||
JVM) rather than horizontally (i.e. through clustering).
|
||||
|
||||
A key aspect of reactive applications is the concept of backpressure which is
|
||||
a mechanism to ensure producers don't overwhelm consumers. For example in a pipeline
|
||||
of reactive components extending from the database to the HTTP response when the
|
||||
HTTP connection is too slow the data repository can also slow down or stop completely
|
||||
until network capacity frees up.
|
||||
|
||||
Reactive programming also leads to a major shift from imperative to declarative async
|
||||
composition of logic. It is comparable to writing blocking code vs using the
|
||||
`CompletableFuture` from Java 8 to compose follow-up actions via lambda expressions.
|
||||
|
||||
For a longer introduction check the blog series
|
||||
https://spring.io/blog/2016/06/07/notes-on-reactive-programming-part-i-the-reactive-landscape["Notes on Reactive Programming"]
|
||||
by Dave Syer.
|
||||
|
||||
|
||||
[[web-reactive-api]]
|
||||
=== Reactive API and Building Blocks
|
||||
|
||||
Spring Framework 5 embraces
|
||||
https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams[Reactive Streams]
|
||||
as the contract for communicating backpressure across async components and
|
||||
libraries. Reactive Streams is a specification created through industry collaboration that
|
||||
has also been adopted in Java 9 as `java.util.concurrent.Flow`.
|
||||
|
||||
The Spring Framework uses https://projectreactor.io/[Reactor] internally for its own
|
||||
reactive support. Reactor is a Reactive Streams implementation that further extends the
|
||||
basic Reactive Streams `Publisher` contract with the `Flux` and `Mono` composable API
|
||||
types to provide declarative operations on data sequences of `0..N` and `0..1`.
|
||||
|
||||
The Spring Framework exposes `Flux` and `Mono` in many of its own reactive APIs.
|
||||
At the application level however, as always, Spring provides choice and fully supports
|
||||
the use of RxJava. For more on reactive types check the post
|
||||
https://spring.io/blog/2016/04/19/understanding-reactive-types["Understanding Reactive Types"]
|
||||
by Sebastien Deleuze.
|
||||
|
||||
|
||||
[[web-reactive-feature-overview]]
|
||||
== Spring WebFlux Module
|
||||
|
||||
Spring Framework 5 includes a new `spring-webflux` module. The module contains support
|
||||
for reactive HTTP and WebSocket clients as well as for reactive server web applications
|
||||
including REST, HTML browser, and WebSocket style interactions.
|
||||
|
||||
[[web-reactive-server]]
|
||||
=== Server Side
|
||||
|
||||
On the server-side WebFlux supports 2 distinct programming models:
|
||||
|
||||
* Annotation-based with `@Controller` and the other annotations supported also with Spring MVC
|
||||
* Functional, Java 8 lambda style routing and handling
|
||||
|
||||
Both programming models are executed on the same reactive foundation that adapts
|
||||
non-blocking HTTP runtimes to the Reactive Streams API. The diagram
|
||||
below shows the server-side stack including traditional, Servlet-based
|
||||
Spring MVC on the left from the `spring-webmvc` module and also the
|
||||
reactive stack on the right from the `spring-webflux` module.
|
||||
|
||||
image::images/webflux-overview.png[width=720]
|
||||
|
||||
WebFlux can run on Servlet containers with support for the
|
||||
Servlet 3.1 Non-Blocking IO API as well as on other async runtimes such as
|
||||
Netty and Undertow. Each runtime is adapted to a reactive
|
||||
`ServerHttpRequest` and `ServerHttpResponse` exposing the body of the
|
||||
request and response as `Flux<DataBuffer>`, rather than
|
||||
`InputStream` and `OutputStream`, with reactive backpressure.
|
||||
REST-style JSON and XML serialization and deserialization is supported on top
|
||||
as a `Flux<Object>`, and so is HTML view rendering and Server-Sent Events.
|
||||
|
||||
[[web-reactive-server-annotation]]
|
||||
==== Annotation-based Programming Model
|
||||
|
||||
The same `@Controller` programming model and the same annotations used in Spring MVC
|
||||
are also supported in WebFlux. The main difference is that the underlying core,
|
||||
framework contracts -- i.e. `HandlerMapping`, `HandlerAdapter`, are
|
||||
non-blocking and operate on the reactive `ServerHttpRequest` and `ServerHttpResponse`
|
||||
rather than on the `HttpServletRequest` and `HttpServletResponse`.
|
||||
Below is an example with a reactive controller:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
public class PersonController {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonController(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
Mono<Void> create(@RequestBody Publisher<Person> personStream) {
|
||||
return this.repository.save(personStream).then();
|
||||
}
|
||||
|
||||
@GetMapping("/person")
|
||||
Flux<Person> list() {
|
||||
return this.repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
Mono<Person> findById(@PathVariable String id) {
|
||||
return this.repository.findOne(id);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[web-reactive-server-functional]]
|
||||
==== Functional Programming Model
|
||||
|
||||
include::web-flux-functional.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
[[web-reactive-client]]
|
||||
=== Client Side
|
||||
|
||||
WebFlux includes a functional, reactive `WebClient` that offers a fully
|
||||
non-blocking and reactive alternative to the `RestTemplate`. It exposes network
|
||||
input and output as a reactive `ClientHttpRequest` and `ClientHttpResponse` where
|
||||
the body of the request and response is a `Flux<DataBuffer>` rather than an
|
||||
`InputStream` and `OutputStream`. In addition it supports the same reactive JSON, XML,
|
||||
and SSE serialization mechanism as on the server side so you can work with typed objects.
|
||||
Below is an example of using the `WebClient` which requires a `ClientHttpConnector`
|
||||
implementation to plug in a specific HTTP client such as Reactor Netty:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient client = WebClient.create("http://example.com");
|
||||
|
||||
Mono<Account> account = client.get()
|
||||
.url("/accounts/{id}", 1L)
|
||||
.accept(APPLICATION_JSON)
|
||||
.exchange(request)
|
||||
.then(response -> response.bodyToMono(Account.class));
|
||||
----
|
||||
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `AsyncRestTemplate` also supports non-blocking interactions. The main difference
|
||||
is it can't support non-blocking streaming, like for example
|
||||
https://dev.twitter.com/streaming/overview[Twitter one], because fundamentally it's
|
||||
still based and relies on `InputStream` and `OutputStream`.
|
||||
====
|
||||
|
||||
|
||||
[[web-reactive-http-body]]
|
||||
=== Request and Response Body Conversion
|
||||
|
||||
The `spring-core` module provides reactive `Encoder` and `Decoder` contracts
|
||||
that enable the serialization of a `Flux` of bytes to and from typed objects.
|
||||
The `spring-web` module adds JSON (Jackson) and XML (JAXB) implementations for use in
|
||||
web applications as well as others for SSE streaming and zero-copy file transfer.
|
||||
|
||||
The following Reactive APIs are supported:
|
||||
|
||||
* Reactor 3.x is supported out of the box
|
||||
* RxJava 2.x is supported when `io.reactivex.rxjava2:rxjava` dependency is on the classpath
|
||||
* RxJava 1.x is supported when both `io.reactivex:rxjava` and `io.reactivex:rxjava-reactive-streams` (https://github.com/ReactiveX/RxJavaReactiveStreams[adapter between RxJava and Reactive Streams]) dependencies are on the classpath
|
||||
|
||||
For example the request body can be one of the following way and it will be decoded
|
||||
automatically in both the annotation and the functional programming models:
|
||||
|
||||
* `Account account` -- the account is deserialized without blocking before the controller is invoked.
|
||||
* `Mono<Account> account` -- the controller can use the `Mono` to declare logic to be executed after the account is deserialized.
|
||||
* `Single<Account> account` -- same as with `Mono` but using RxJava
|
||||
* `Flux<Account> accounts` -- input streaming scenario.
|
||||
* `Observable<Account> accounts` -- input streaming with RxJava.
|
||||
|
||||
The response body can be one of the following:
|
||||
|
||||
* `Mono<Account>` -- serialize without blocking the given Account when the `Mono` completes.
|
||||
* `Single<Account>` -- same but using RxJava.
|
||||
* `Flux<Account>` -- streaming scenario, possibly SSE depending on the requested content type.
|
||||
* `Observable<Account>` -- same but using RxJava `Observable` type.
|
||||
* `Flowable<Account>` -- same but using RxJava 2 `Flowable` type.
|
||||
* `Publisher<Account>` or `Flow.Publisher<Account>` -- any type implementing Reactive Streams `Publisher` is supported.
|
||||
* `Flux<ServerSentEvent>` -- SSE streaming.
|
||||
* `Mono<Void>` -- request handling completes when the `Mono` completes.
|
||||
* `Account` -- serialize without blocking the given Account; implies a synchronous, non-blocking controller method.
|
||||
* `void` -- specific to the annotation-based programming model, request handling completes
|
||||
when the method returns; implies a synchronous, non-blocking controller method.
|
||||
|
||||
When using stream types like `Flux` or `Observable`, the media type specified in the
|
||||
request/response or at mapping/routing level is used to determine how the data should be serialized
|
||||
and flushed. For example a REST endpoint that returns a `Flux<Account>` will be serialized by
|
||||
default as following:
|
||||
|
||||
* `application/json`: a `Flux<Account>` is handled as an asynchronous collection and
|
||||
serialized as a JSON array with an explicit flush when the `complete` event is emitted.
|
||||
* `application/stream+json`: a `Flux<Account>` will be handled as a stream of `Account` elements
|
||||
serialized as individual JSON object separated by new lines and explicitly flushed after
|
||||
each element. The `WebClient` supports JSON stream decoding so this is a good use case
|
||||
for server to server use case.
|
||||
* `text/event-stream`: a `Flux<Account>` or `Flux<ServerSentEvent<Account>>` will be handled as
|
||||
a stream of `Account` or `ServerSentEvent` elements serialized as individual SSE elements
|
||||
using by default JSON for data encoding and explicit flush after each element. This
|
||||
is well suited for exposing a stream to browser clients. `WebClient` supports
|
||||
reading SSE streams as well.
|
||||
|
||||
|
||||
[[web-reactive-websocket-support]]
|
||||
=== Reactive WebSocket Support
|
||||
|
||||
WebFlux includes reactive WebSocket client and server support.
|
||||
Both client and server are supported on the Java WebSocket API
|
||||
(JSR-356), Jetty, Undertow, Reactor Netty, and RxNetty.
|
||||
|
||||
On the server side, declare a `WebSocketHandlerAdapter` and then simply add
|
||||
mappings to `WebSocketHandler`-based endpoints:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
public HandlerMapping webSocketMapping() {
|
||||
Map<String, WebSocketHandler> map = new HashMap<>();
|
||||
map.put("/foo", new FooWebSocketHandler());
|
||||
map.put("/bar", new BarWebSocketHandler());
|
||||
|
||||
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
|
||||
mapping.setUrlMap(map);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketHandlerAdapter handlerAdapter() {
|
||||
return new WebSocketHandlerAdapter();
|
||||
}
|
||||
----
|
||||
|
||||
On the client side create a `WebSocketClient` for one of the supported libraries
|
||||
listed above:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
WebSocketClient client = new ReactorNettyWebSocketClient();
|
||||
client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000);
|
||||
----
|
||||
|
||||
[[web-reactive-tests]]
|
||||
=== Testing
|
||||
|
||||
The `spring-test` module includes a `WebTestClient` that can be used to test
|
||||
WebFlux server endpoints with or without a running server.
|
||||
|
||||
Tests without a running server are comparable to `MockMvc` from Spring MVC
|
||||
where mock request and response are used instead of connecting over the network
|
||||
using a socket. The `WebTestClient` however can also perform tests against a
|
||||
running server.
|
||||
|
||||
For more see
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples[sample tests]
|
||||
in the framework.
|
||||
|
||||
|
||||
|
||||
[[web-reactive-getting-started]]
|
||||
== Getting Started
|
||||
|
||||
|
||||
[[web-reactive-getting-started-boot]]
|
||||
=== Spring Boot Starter
|
||||
|
||||
The
|
||||
https://github.com/bclozel/spring-boot-web-reactive#spring-boot-web-reactive-starter[Spring Boot Web Reactive starter]
|
||||
available via http://start.spring.io is the fastest way to get started.
|
||||
It does all that's necessary so you to start writing `@Controller` classes
|
||||
just like with Spring MVC. Simply go to http://start.spring.io, choose
|
||||
version 2.0.0.BUILD-SNAPSHOT, and type reactive in the dependencies box.
|
||||
By default the starter runs with Tomcat but the dependencies can be changed as usual with Spring Boot to switch to a different runtime.
|
||||
See the
|
||||
https://github.com/bclozel/spring-boot-web-reactive#spring-boot-web-reactive-starter[starter]
|
||||
page for more details and instruction
|
||||
|
||||
This starter also supports the functional web API and will detect automatically `RouterFunction`
|
||||
beans. Your Spring Boot WebFlux application should use the `RouterFunction` *or* the
|
||||
`RequestMapping` approach, it is currently not possible to mix them in the same application.
|
||||
|
||||
[[web-reactive-getting-started-manual]]
|
||||
=== Manual Bootstrapping
|
||||
|
||||
This section outlines the steps to get up and running manually.
|
||||
|
||||
For dependencies start with `spring-webflux` and `spring-context`.
|
||||
Then add `jackson-databind` and `io.netty:netty-buffer`
|
||||
(temporarily see https://jira.spring.io/browse/SPR-14528[SPR-14528]) for JSON support.
|
||||
Lastly add the dependencies for one of the supported runtimes:
|
||||
|
||||
* Tomcat -- `org.apache.tomcat.embed:tomcat-embed-core`
|
||||
* Jetty -- `org.eclipse.jetty:jetty-server` and `org.eclipse.jetty:jetty-servlet`
|
||||
* Reactor Netty -- `io.projectreactor.ipc:reactor-netty`
|
||||
* RxNetty -- `io.reactivex:rxnetty-common` and `io.reactivex:rxnetty-http`
|
||||
* Undertow -- `io.undertow:undertow-core`
|
||||
|
||||
For the **annotation-based programming model** bootstrap with:
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebFluxConfiguration.class); // (1)
|
||||
HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2)
|
||||
----
|
||||
|
||||
The above loads default Spring Web framework configuration (1), then creates a
|
||||
`DispatcherHandler`, the main class driving request processing (2), and adapts
|
||||
it to `HttpHandler` -- the lowest level Spring abstraction for reactive HTTP request handling.
|
||||
|
||||
For the **functional programming model** bootstrap as follows:
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(); // (1)
|
||||
context.registerBean(FooBean.class, () -> new FooBeanImpl()); // (2)
|
||||
context.registerBean(BarBean.class); // (3)
|
||||
|
||||
HttpHandler handler = WebHttpHandlerBuilder
|
||||
.webHandler(RouterFunctions.toHttpHandler(...))
|
||||
.applicationContext(context)
|
||||
.build(); // (4)
|
||||
----
|
||||
|
||||
The above creates an `AnnotationConfigApplicationContext` instance (1) that can take advantage
|
||||
of the new functional bean registration API (2) to register beans using a Java 8 `Supplier`
|
||||
or just by specifying its class (3). The `HttpHandler` is created using `WebHttpHandlerBuilder` (4).
|
||||
|
||||
The `HttpHandler` can then be installed in one of the supported runtimes:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
// Tomcat and Jetty (also see notes below)
|
||||
HttpServlet servlet = new ServletHttpHandlerAdapter(handler);
|
||||
...
|
||||
|
||||
// Reactor Netty
|
||||
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
|
||||
HttpServer.create(host, port).newHandler(adapter).block();
|
||||
|
||||
// RxNetty
|
||||
RxNettyHttpHandlerAdapter adapter = new RxNettyHttpHandlerAdapter(handler);
|
||||
HttpServer server = HttpServer.newServer(new InetSocketAddress(host, port));
|
||||
server.startAndAwait(adapter);
|
||||
|
||||
// Undertow
|
||||
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
|
||||
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
|
||||
server.start();
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
For Servlet containers especially with WAR deployment you can use the
|
||||
`AbstractAnnotationConfigDispatcherHandlerInitializer` which as a
|
||||
`WebApplicationInitializer` and is auto-detected by Servlet containers.
|
||||
It takes care of registering the `ServletHttpHandlerAdapter` as shown above.
|
||||
You will need to implement one abstract method in order to point to your
|
||||
Spring configuration.
|
||||
====
|
||||
|
||||
[[web-reactive-getting-started-examples]]
|
||||
=== Examples
|
||||
|
||||
You will find code examples useful to build reactive Web application in the following projects:
|
||||
|
||||
* https://github.com/bclozel/spring-boot-web-reactive[Spring Boot Web Reactive Starter]: sources of the reactive starter available at http://start.spring.io
|
||||
* https://github.com/poutsma/web-function-sample[Functional programming model sample]
|
||||
* https://github.com/sdeleuze/spring-reactive-playground[Spring Reactive Playground]: playground for most Spring Web reactive features
|
||||
* https://github.com/reactor/projectreactor.io/tree/spring-functional[Reactor website]: the `spring-functional` branch is a Spring 5 functional, Java 8 lambda-style application
|
||||
* https://github.com/bclozel/spring-reactive-university[Spring Reactive University session]: live-coded project from https://www.youtube.com/watch?v=Cj4foJzPF80[this Devoxx BE 2106 university talk]
|
||||
* https://github.com/thymeleaf/thymeleafsandbox-biglist-reactive[Reactive Thymeleaf Sandbox]
|
||||
* https://github.com/mix-it/mixit/[Mix-it 2017 website]: Kotlin + Reactive + Functional web and bean registration API application
|
||||
* https://github.com/simonbasle/reactor-by-example[Reactor by example]: code snippets coming from this https://www.infoq.com/articles/reactor-by-example[InfoQ article]
|
||||
* https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation[Spring integration tests]: various features tested with Reactor https://projectreactor.io/docs/test/release/api/index.html?reactor/test/StepVerifier.html[`StepVerifier`]
|
||||
249
src/docs/asciidoc/web/web-integration.adoc
Normal file
249
src/docs/asciidoc/web/web-integration.adoc
Normal file
@@ -0,0 +1,249 @@
|
||||
|
||||
[[web-integration]]
|
||||
= Integrating with other web frameworks
|
||||
|
||||
|
||||
[[intro]]
|
||||
== Introduction
|
||||
|
||||
.Spring Web Flow
|
||||
****
|
||||
Spring Web Flow (SWF) aims to be the best solution for the management of web application
|
||||
page flow.
|
||||
|
||||
SWF integrates with existing frameworks like Spring MVC and JSF, in both Servlet and
|
||||
Portlet environments. If you have a business process (or processes) that would benefit
|
||||
from a conversational model as opposed to a purely request model, then SWF may be the
|
||||
solution.
|
||||
|
||||
SWF allows you to capture logical page flows as self-contained modules that are reusable
|
||||
in different situations, and as such is ideal for building web application modules that
|
||||
guide the user through controlled navigations that drive business processes.
|
||||
|
||||
For more information about SWF, consult the
|
||||
http://projects.spring.io/spring-webflow/[Spring Web Flow website].
|
||||
****
|
||||
|
||||
This chapter details Spring's integration with third party web frameworks, such as
|
||||
http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html[JSF].
|
||||
|
||||
One of the core value propositions of the Spring Framework is that of enabling
|
||||
__choice__. In a general sense, Spring does not force one to use or buy into any
|
||||
particular architecture, technology, or methodology (although it certainly recommends
|
||||
some over others). This freedom to pick and choose the architecture, technology, or
|
||||
methodology that is most relevant to a developer and their development team is
|
||||
arguably most evident in the web area, where Spring provides its own web framework
|
||||
(<<mvc,Spring MVC>>), while at the same time providing integration with a number of
|
||||
popular third party web frameworks. This allows one to continue to leverage any and all
|
||||
of the skills one may have acquired in a particular web framework such as JSF, while
|
||||
at the same time being able to enjoy the benefits afforded by Spring in other areas such
|
||||
as data access, declarative transaction management, and flexible configuration and
|
||||
application assembly.
|
||||
|
||||
Having dispensed with the woolly sales patter (c.f. the previous paragraph), the
|
||||
remainder of this chapter will concentrate upon the meaty details of integrating your
|
||||
favorite web framework with Spring. One thing that is often commented upon by developers
|
||||
coming to Java from other languages is the seeming super-abundance of web frameworks
|
||||
available in Java. There are indeed a great number of web frameworks in the Java space;
|
||||
in fact there are far too many to cover with any semblance of detail in a single
|
||||
chapter. This chapter thus picks four of the more popular web frameworks in Java,
|
||||
starting with the Spring configuration that is common to all of the supported web
|
||||
frameworks, and then detailing the specific integration options for each supported web
|
||||
framework.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please note that this chapter does not attempt to explain how to use any of the
|
||||
supported web frameworks. For example, if you want to use JSF for the presentation
|
||||
layer of your web application, the assumption is that you are already familiar with
|
||||
JSF itself. If you need further details about any of the supported web frameworks
|
||||
themselves, please do consult <<web-integration-resources>> at the end of this chapter.
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
[[web-integration-common]]
|
||||
== Common configuration
|
||||
Before diving into the integration specifics of each supported web framework, let us
|
||||
first take a look at the Spring configuration that is __not__ specific to any one web
|
||||
framework. (This section is equally applicable to Spring's own web framework, Spring
|
||||
MVC.)
|
||||
|
||||
One of the concepts (for want of a better word) espoused by (Spring's) lightweight
|
||||
application model is that of a layered architecture. Remember that in a 'classic'
|
||||
layered architecture, the web layer is but one of many layers; it serves as one of the
|
||||
entry points into a server side application and it delegates to service objects
|
||||
(facades) defined in a service layer to satisfy business specific (and
|
||||
presentation-technology agnostic) use cases. In Spring, these service objects, any other
|
||||
business-specific objects, data access objects, etc. exist in a distinct 'business
|
||||
context', which contains __no__ web or presentation layer objects (presentation objects
|
||||
such as Spring MVC controllers are typically configured in a distinct 'presentation
|
||||
context'). This section details how one configures a Spring container (a
|
||||
`WebApplicationContext`) that contains all of the 'business beans' in one's application.
|
||||
|
||||
On to specifics: all that one need do is to declare a
|
||||
{api-spring-framework}/web/context/ContextLoaderListener.html[`ContextLoaderListener`]
|
||||
in the standard Java EE servlet `web.xml` file of one's web application, and add a
|
||||
`contextConfigLocation`<context-param/> section (in the same file) that defines which
|
||||
set of Spring XML configuration files to load.
|
||||
|
||||
Find below the <listener/> configuration:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
----
|
||||
|
||||
Find below the <context-param/> configuration:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/applicationContext*.xml</param-value>
|
||||
</context-param>
|
||||
----
|
||||
|
||||
If you don't specify the `contextConfigLocation` context parameter, the
|
||||
`ContextLoaderListener` will look for a file called `/WEB-INF/applicationContext.xml` to
|
||||
load. Once the context files are loaded, Spring creates a
|
||||
{api-spring-framework}/web/context/WebApplicationContext.html[`WebApplicationContext`]
|
||||
object based on the bean definitions and stores it in the `ServletContext` of the web
|
||||
application.
|
||||
|
||||
All Java web frameworks are built on top of the Servlet API, and so one can use the
|
||||
following code snippet to get access to this 'business context' `ApplicationContext`
|
||||
created by the `ContextLoaderListener`.
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
|
||||
----
|
||||
|
||||
The
|
||||
{api-spring-framework}/web/context/support/WebApplicationContextUtils.html[`WebApplicationContextUtils`]
|
||||
class is for convenience, so you don't have to remember the name of the `ServletContext`
|
||||
attribute. Its __getWebApplicationContext()__ method will return `null` if an object
|
||||
doesn't exist under the `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`
|
||||
key. Rather than risk getting `NullPointerExceptions` in your application, it's better
|
||||
to use the `getRequiredWebApplicationContext()` method. This method throws an exception
|
||||
when the `ApplicationContext` is missing.
|
||||
|
||||
Once you have a reference to the `WebApplicationContext`, you can retrieve beans by
|
||||
their name or type. Most developers retrieve beans by name and then cast them to one of
|
||||
their implemented interfaces.
|
||||
|
||||
Fortunately, most of the frameworks in this section have simpler ways of looking up
|
||||
beans. Not only do they make it easy to get beans from a Spring container, but they also
|
||||
allow you to use dependency injection on their controllers. Each web framework section
|
||||
has more detail on its specific integration strategies.
|
||||
|
||||
|
||||
|
||||
|
||||
[[jsf]]
|
||||
== JavaServer Faces 1.2
|
||||
JavaServer Faces (JSF) is the JCP's standard component-based, event-driven web user
|
||||
interface framework. As of Java EE 5, it is an official part of the Java EE umbrella.
|
||||
|
||||
For a popular JSF runtime as well as for popular JSF component libraries, check out the
|
||||
http://myfaces.apache.org/[Apache MyFaces project]. The MyFaces project also provides
|
||||
common JSF extensions such as http://myfaces.apache.org/orchestra/[MyFaces Orchestra]:
|
||||
a Spring-based JSF extension that provides rich conversation scope support.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring Web Flow 2.0 provides rich JSF support through its newly established Spring Faces
|
||||
module, both for JSF-centric usage (as described in this section) and for Spring-centric
|
||||
usage (using JSF views within a Spring MVC dispatcher). Check out the
|
||||
http://projects.spring.io/spring-webflow[Spring Web Flow website] for details!
|
||||
====
|
||||
|
||||
The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
|
||||
|
||||
[[jsf-springbeanfaceselresolver]]
|
||||
=== SpringBeanFacesELResolver (JSF 1.2+)
|
||||
`SpringBeanFacesELResolver` is a JSF 1.2 compliant `ELResolver` implementation,
|
||||
integrating with the standard Unified EL as used by JSF 1.2 and JSP 2.1. Like
|
||||
`SpringBeanVariableResolver`, it delegates to the Spring's 'business context'
|
||||
`WebApplicationContext` __first__, then to the default resolver of the underlying JSF
|
||||
implementation.
|
||||
|
||||
Configuration-wise, simply define `SpringBeanFacesELResolver` in your JSF 1.2
|
||||
__faces-context.xml__ file:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<faces-config>
|
||||
<application>
|
||||
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
|
||||
...
|
||||
</application>
|
||||
</faces-config>
|
||||
----
|
||||
|
||||
|
||||
[[jsf-facescontextutils]]
|
||||
=== FacesContextUtils
|
||||
A custom `VariableResolver` works well when mapping one's properties to beans
|
||||
in __faces-config.xml__, but at times one may need to grab a bean explicitly. The
|
||||
{api-spring-framework}/web/jsf/FacesContextUtils.html[`FacesContextUtils`]
|
||||
class makes this easy. It is similar to `WebApplicationContextUtils`, except that it
|
||||
takes a `FacesContext` parameter rather than a `ServletContext` parameter.
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[struts]]
|
||||
== Apache Struts 2.x
|
||||
Invented by Craig McClanahan, http://struts.apache.org[Struts] is an open source project
|
||||
hosted by the Apache Software Foundation. At the time, it greatly simplified the
|
||||
JSP/Servlet programming paradigm and won over many developers who were using proprietary
|
||||
frameworks. It simplified the programming model, it was open source (and thus free as in
|
||||
beer), and it had a large community, which allowed the project to grow and become popular
|
||||
among Java web developers.
|
||||
|
||||
Check out the Struts
|
||||
https://struts.apache.org/release/2.3.x/docs/spring-plugin.html[Spring Plugin] for the
|
||||
built-in Spring integration shipped with Struts.
|
||||
|
||||
|
||||
|
||||
[[tapestry]]
|
||||
== Tapestry 5.x
|
||||
From the http://tapestry.apache.org/[Tapestry homepage]:
|
||||
|
||||
Tapestry is a "__Component oriented framework for creating dynamic, robust,
|
||||
highly scalable web applications in Java.__"
|
||||
|
||||
While Spring has its own <<mvc,powerful web layer>>, there are a number of unique
|
||||
advantages to building an enterprise Java application using a combination of Tapestry
|
||||
for the web user interface and the Spring container for the lower layers.
|
||||
|
||||
For more information, check out Tapestry's dedicated
|
||||
https://tapestry.apache.org/integrating-with-spring-framework.html[integration module for
|
||||
Spring].
|
||||
|
||||
|
||||
|
||||
[[web-integration-resources]]
|
||||
== Further Resources
|
||||
Find below links to further resources about the various web frameworks described in this
|
||||
chapter.
|
||||
|
||||
* The http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html[JSF] homepage
|
||||
* The http://struts.apache.org/[Struts] homepage
|
||||
* The http://tapestry.apache.org/[Tapestry] homepage
|
||||
|
||||
5762
src/docs/asciidoc/web/web-mvc.adoc
Normal file
5762
src/docs/asciidoc/web/web-mvc.adoc
Normal file
File diff suppressed because it is too large
Load Diff
2139
src/docs/asciidoc/web/web-view.adoc
Normal file
2139
src/docs/asciidoc/web/web-view.adoc
Normal file
File diff suppressed because it is too large
Load Diff
2424
src/docs/asciidoc/web/web-websocket.adoc
Normal file
2424
src/docs/asciidoc/web/web-websocket.adoc
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user