springmvc-notes
springmvc-notes
jim stafford
1.1. Goals
The student will learn to:
• identify two primary paradigms in today’s server logic: synchronous and reactive
1.2. Objectives
At the conclusion of this lecture and related exercises, the student will be able to:
1. identify the difference between the Spring MVC and Spring WebFlux frameworks
1
Chapter 2. Spring Web APIs
There are two primary, overlapping frameworks within Spring for developing HTTP-based APIs:
• Spring MVC
• Spring WebFlux
Spring MVC is the legacy framework that operates using synchronous, blocking request/reply
constructs. Spring WebFlux is the follow-on framework that builds on Spring MVC by adding
asynchronous, non-blocking constructs that are inline with the reactive streams paradigm.
However, we need to know the two approaches exist in order to make sense of the software and
available documentation. For example, the client-side of Spring MVC (i.e., RestTemplate) has been
put in "maintenance mode" (minor changes and bug fixes only) and its duties fulfilled by Spring
WebFlux (i.e., WebClient). Therefore, I will be demonstrating synchronous client concepts using
both libraries to help bridge the transition.
The separation of concern provides a means to logically divide web application code along
architecture boundaries. Built-in support for HTTP-based APIs have matured over time and with
the shift of UI web applications to Javascript frameworks running in the browser, the focus has
likely shifted towards the API development.
2
As mentioned earlier, the programming model
for Spring MVC is synchronous, blocking
request/reply. Each active request is blocked in
its own thread while waiting for the result of the
current request to complete. This mode scales
primarily by adding more threads — most of
which are blocked performing some sort of I/O
operation.
Figure 1. Spring MVC Synchronous Model
Some of the core concepts — like annotated @RestController and method associated
annotations — still exist. The most visible changes added include the optional functional controller
and the new, mandatory data input and return publisher types:
3
Figure 3. Synchronous
Figure 4. Asynchronous
There are different types of asynchronous processing. Spring has long supported threads with
@Async methods. However, that style simply launches one or more additional threads that
potentially also contain synchronous logic that will likely block at some point. The reactive model is
strictly non-blocking — relying on the backpressure of available data and the resources being
available to consume it. With the reactive programming paradigm comes strict rules of the road.
With that said — functionally, we can mix Spring Web MVC and Spring WebFlux together in an
application using what is considered to be the Web MVC container.
• Synchronous and reactive flows can operate side-by-side as independent paths through the code
• Synchronous flows can make use of asynchronous flows. A primary example of that is using the
WebClient reactive methods from a Spring MVC controller-initiated flow
However, we cannot have the callback of a reactive flow make synchronous requests that can
indeterminately block — or it itself will become synchronous and tie up a critical reactor thread.
4
Tomcat and Jetty are Spring MVC servlet engines. Reactor Netty is a Spring
WebFlux engine. Use of reactive streams within the Spring MVC container is
supported — but not optimized or recommended beyond use of the WebClient in
Spring MVC applications. Use of synchronous flows is not supported by Spring
WebFlux.
Synchronous
[2]
• existing synchronous API working fine — no need to change
Reactive
[3]
• need to serve a significant number (e.g., 100-300) of concurrent users
• does little to no good (i.e., badly) if the services called are synchronous (i.e., initial response
returns when overall request complete) (e.g., JDBC, JPA)
[2]
• desire to work with Kotlin or Java 8 lambdas
[4]
• service is IO-intensive (e.g., database or external service calls)
For many of the above reason, we will start out our HTTP-based API coverage in this course using
the synchronous approach.
[1] "Can I use SpringMvc and webflux together?", Brian Clozel, 2018
[2] "Spring WebFlux Documentation - Applicability", version 5.2.6 release
[3] "SpringBoot: Performance War", Santhosh Krishnan, 2020
[4] "Do’s and Don’ts: Avoiding First-Time Reactive Programmer Mines", Sergei Egorov, SpringOne Platform, 2019
5
Chapter 3. Maven Dependencies
Most dependencies for Spring MVC are satisfied by changing spring-boot-starter to spring-boot-
starter-web. Among other things, this brings in dependencies on spring-webmvc and spring-boot-
starter-tomcat.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
The dependencies for Spring MVC and Spring WebFlux’s WebClient are satisfied by adding spring-
boot-starter-webflux. It primarily brings in the spring-webflux and the reactive libraries, and
spring-boot-starter-reactor-netty. We won’t be using the netty engine, but WebClient does make
use of some netty client libraries that are brought in when using the starter.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
6
Chapter 4. Sample Application
To get started covering the basics of Web MVC, I am going to use a very simple, remote procedure
call (RPC)-oriented, RMM level 1 example where the web client simply makes a call to the service to
say "hi". The example is located within the rpc-greeter-svc module.
|-- pom.xml
`-- src
|-- main
| |-- java
| | `-- info
| | `-- ejava
| | `-- examples
| | `-- svc
| | `-- rpc
| | |-- GreeterApplication.java
| | `-- greeter
| | `-- controllers
| | `-- RpcGreeterController.java
| `-- resources
| `-- ...
`-- test
|-- java
| `-- info
| `-- ejava
| `-- examples
| `-- svc
| `-- rpc
| `-- greeter
| |-- GreeterRestTemplateHttpNTest.java
| `-- GreeterSyncWebClientHttpNTest.java
`-- resources
`-- ...
7
Chapter 5. Annotated Controllers
Traditional Spring MVC APIs are primarily implemented around annotated controller components.
Spring has a hierarchy of annotations that help identify the role of the component class. In this case
the controller class will commonly be annotated with @RestController, which wraps @Controller,
which wraps @Component. This primarily means that the class will get automatically picked up
during the component scan if it is in the application’s scope.
package info.ejava.examples.svc.httpapi.greeter.controllers;
import org.springframework.web.bind.annotation.RestController;
@RestController
// ==> wraps @Controller
// ==> wraps @Component
public class RpcGreeterController {
//...
}
In this particular case, our class-level annotation is defining a base URL path of /rpc/greeting.
...
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
@RequestMapping("rpc/greeter") ①
public class RpcGreeterController {
...
① @RequestMapping.path="rpc/greeting" at class level establishes base URI path for all hosted
methods
8
We can use either path, value, or no name (when nothing else supplied) to express
the path in @RequestMapping.
GET /rpc/greeter/sayHi
• URI - we already defined a base URI path of /rpc/greeter at the class level — we now need to
extend that to form the final URI of /rpc/greeter/sayHi
• HTTP method - this is specific to each class method — so we need to explicitly declare GET (one
of the standard RequestMethod enums) on the class method
...
/**
* This is an example of a method as simple as it gets
* @return hi
*/
@RequestMapping(path="sayHi", ①
method=RequestMethod.GET) ②
public String sayHi() {
return "hi";
}
② @RequestMapping.method=GET registers this method to accept HTTP GET calls to the URI
/rpc/greeter/sayHi
9
simplistic cases:
response body
The response body is automatically set to the marshalled value returned by the endpoint
method. In this case it is a literal String mapping.
status code
The container will return the following default status codes
Content-Type header
The container sensibly mapped our returned String to the text/plain Content-Type.
http://localhost:8080/rpc/greeter/sayHi
hi
If you have access to curl or another HTTP test tool, you will likely see the following additional
detail.
$ curl -v http://localhost:8080/rpc/greeter/sayHi
...
> GET /rpc/greeter/sayHi HTTP/1.1
> Host: localhost:8080
10
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 2
...
hi
11
Chapter 6. RestTemplate Client
The primary point of making a callable HTTP endpoint is the ability to call that endpoint from
another application. With a functional endpoint ready to go, we are ready to create a Java client
and will do so within a JUnit test using Spring MVC’s RestTemplate class in the simplest way
possible.
Please note that most of these steps are true for any Java HTTP client we might use. Only the steps
directly related to RestTemplate are specific to that topic.
• classes - reference @Component and/or @Configuration class(es) to define which components will
be in our Spring context (default is to look for @SpringBootConfiguration, which is wrapped by
@SpringBootApplication).
• webEnvironment - to define this as a web-oriented test and whether to have a fixed (e.g., 8080),
random, or none for a port number. The random port number will be injected using the
@LocalServerPort annotation. The default value is MOCK — for Mock test client libraries able to
bypass networking.
package info.ejava.examples.svc.rpc.greeter;
import info.ejava.examples.svc.rpc.GreeterApplication;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
@SpringBootTest(classes = GreeterApplication.class, ①
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) ②
@Tag("springboot") @Tag("greeter")
@Slf4j
public class GreeterRestTemplateHttpNTest {
@LocalServerPort ③
private int port;
① using the application to define the components for the Spring context
③ the random server port# will be injected into port annotated with @LocalServerPort
12
initialized — which constrains how we can inject.
As you saw earlier, we can have it injected as an attribute of the test case class.
This would be good if many of the @Test methods needed access to the raw port
value.
@SpringBootTest(...)
public class GreeterRestTemplateHttpNTest {
@LocalServerPort
private int port; //inject option way1
A close alternative would be to inject the value into the @BeforeEach lifecycle
method. This would be good if @Test methods did not use the raw port value — but
may use something that was built from the value.
@BeforeEach
public void init(@LocalServerPort int port) { //inject option way2
baseUrl = String.format("http://localhost:%d/rpc/greeter",
port);
}
import org.springframework.context.annotation.Lazy;
...
@TestConfiguration(proxyBeanMethods = false)
public class ClientTestConfiguration {
@Bean @Lazy
public String baseUrl(@LocalServerPort int port) {//inject option
way3
return String.format("http://localhost:%d/rpc/greeter", port);
}
13
@LocalServerPort
private int port;
@Test
public void say_hi() {
//given - a service available at a URL and client access
String url = String.format("http://localhost:%d/rpc/greeter/sayHi", port); ①
...
Spring Template is a thread safe class that can be constructed with a default constructor for the
simple case — or through a builder in more complex cases and injected to take advantage of
separation of concerns.
Note that a successful return from getForObject() will only occur if the response from the server is
a 2xx/successful response. Otherwise an exception of one of the following types will be thrown:
14
▪ BadRequest, NotFound, UnprocessableEntity, …
15
Chapter 7. WebClient Client
The Spring 5 documentation states the RestTemplate is in "maintenance mode" and that we should
switchover to using the Spring WebFlux WebClient. Representatives from Pivotal have stated in
various conference talks that RestTemplate will likely not go away anytime soon but would likely not
get upgrades to any new drivers.
In demonstrating WebClient, there are a few aspects of our RestTemplate example that do not change
and I do not need to repeat.
• JUnit test setup — i.e., establishing the Spring context and random port#
• Obtaining a URL
16
• performing an HTTP GET Example Invoke HTTP Call
The block() call is the synchronous part that we would look to avoid in a truly reactive thread. It is
a type of subscriber that triggers the defined flow to begin producing data. This block() is blocking
the current (synchronous) thread — just like RestTemplate. The portions of the call ahead of block()
are performed in a reactive set of threads.
17
Chapter 8. Implementing Parameters
There are three primary ways to map an HTTP call to method input parameters:
The later two are part of the next example and expressed in the URI.
/ ①
GET /rpc/greeter/say/hello?name=jim
\ ②
◦ path parameters are part of the resource URI path and are commonly required when
defined — but that is not a firm rule
◦ query parameters are commonly the technique for optional arguments against the resource
expressed in the URI path
Specific query parameters are mapped by their name in the URL to a specific method input
parameter using the @RequestParam annotation. In the following example, we are mapping
whatever is in the value position of name= to the name input variable.
@RequestMapping(path="say/{greeting}", ①
method=RequestMethod.GET)
public String sayGreeting(
@PathVariable("greeting") String greeting, ①
@RequestParam(value = "name", defaultValue = "you") String name) { ②
return greeting + ", " + name;
}
18
① URI path placeholder {greeting} is being mapped to method input parameter String greeting
② URI query parameter name is being mapped to method input parameter String name
http://localhost:8080/rpc/greeter/say/hello?name=jim
A way to build a URL through type-safe convenience methods is with the UriComponentsBuilder
class. In the following example:
• fromHttpUrl() - starts the URI using a string Example Client Code Forming URL with Path and
containing the base (e.g. Query Params
http://localhost:8080/rpc/greeter)
@Test
• path() - can be used to tack on a path to the
public void say_greeting() {
end of the baseUrl. replacePath() is also a //given - a service available to
convenient method here to use when the provide a greeting
value you have is the full path. Note the URI url = UriComponentsBuilder
placeholder with {greeting} reserving a spot .fromHttpUrl(baseUrl)
in the path. The position in the URI is .path("/say/{greeting}") ①
important, but there is no direct relationship .queryParam("name", "{name}") ②
between what the client and service use for .build("hello", "jim"); ③
this placeholder name — if they use one at
all. ① path is being expressed using a {greeting}
placeholder for the value
• queryParam() - is used to express individual
query parameters. The name of the query ② query parameter expressed using a {name}
parameter must match what is expected by placeholder for the value
the service. Note that a placeholder was used ③
values for greeting and name are filled in
here to express the value.
during call to build() to complete the URI
• build() - is used to finish off the URI. We pass
in the placeholder values in the order they
appear in the URI expression
19
Chapter 9. Accessing HTTP Responses
The target of an HTTP response may be a specific marshalled object or successful status. However,
it is common to want to have access to more detailed information. For example:
• status code
• response headers
9.2. ResponseEntity<T>
The ResponseEntity<T> can provide us with more detail than just the response object from the body.
As you can see from the following evaluation block, the client also has access to the status code and
headers.
20
then(response.getBody()).isEqualTo("hello, jim");
21
Chapter 10. Client Error Handling
As indicated earlier, something could fail in the call to the service and we do not get our expected
response returned.
$ curl -v http://localhost:8080/rpc/greeter/boom
...
< HTTP/1.1 400
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Thu, 21 May 2020 19:37:42 GMT
< Connection: close
<
{"timestamp":"2020-05-21T19:37:42.261+0000","status":400,"error":"Bad Request",
"message":"Required String parameter 'value' is not present" ①
...
① Spring MVC has default error handling that will, by default return an application/json rendering
of an error
Although there are differences in their options — for the most part both RestTemplate and WebClient
will throw an exception if the status code is not successful. Although very similar — unfortunately,
their exceptions are technically different and would need separate exception handling logic if used
together.
import org.springframework.web.client.HttpClientErrorException;
...
//when - calling the service
HttpClientErrorException ex = catchThrowableOfType( ①
()->restTemplate.getForEntity(url, String.class),
HttpClientErrorException.BadRequest.class);
22
① using assertj catchThrowableOfType() to catch the exception and it be of a specific type only if
thrown
import org.springframework.web.reactive.function.client.WebClientResponseException;
...
//when - calling the service
WebClientResponseException.BadRequest ex = catchThrowableOfType(
() -> webClient.get().uri(url).retrieve().toEntity(String.class).block(),
WebClientResponseException.BadRequest.class);
HttpClientErrorException ex = ...
23
then(ex.getResponseHeaders().getFirst(HttpHeaders.CONTENT_TYPE))
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
log.info("{}", ex.getResponseBodyAsString());
WebClientResponseException.BadRequest ex = ...
① WebClient 's exception method name to retrieve response headers different from RestTemplate
24
Chapter 11. Controller Responses
In our earlier example, our only response option from the service was a limited set of status codes
derived by the container based on what was returned. The specific error demonstrated was
generated by the Spring MVC container based on our mapping definition. It will be common for the
controller method, itself to need explicit control over the HTTP response returned --primarily to
express response-specific
• HTTP headers
The service provides control over the entire response by returning a ResponseEntity containing the
complete HTTP result versus just returning the result value for the body. The ResponseEntity can
express status code, headers, and the returned entity.
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
...
@RequestMapping(path="boys",
method=RequestMethod.GET)
public ResponseEntity<String> createBoy(@RequestParam("name") String name) { ①
try {
someMethodThatMayThrowException(name);
25
① ResponseEntity returned used to express full HTTP response
③ service is able to return an explicit HTTP response with appropriate success details
④ service is able to return an explicit HTTP response with appropriate error details
curl -v http://localhost:8080/rpc/greeter/boys?name=jim
...
< HTTP/1.1 200 ①
< Content-Location: http://localhost:8080/rpc/greeter/boys?name=jim ②
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 25
...
hello jim, how do you do?
① status explicitly
For the error condition, we see the explicit status code and error payload assigned.
$ curl -v http://localhost:8080/rpc/greeter/boys?name=blue
...
< HTTP/1.1 422 ①
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 15
...
boy named blue?
The following example uses @ExceptionHandler annotation to register a handler for when controller
methods happen to throw the IllegalArgumentException. The handler has the ability to return an
explicit ResponseEntity with the error details.
26
Example Controller ExceptionHandler
import org.springframework.web.bind.annotation.ExceptionHandler;
...
@ExceptionHandler(IllegalArgumentException.class) ①
public ResponseEntity<String> handle(IllegalArgumentException ex) {
return ResponseEntity.unprocessableEntity() ②
.body(ex.getMessage());
}
Create custom exceptions to the point that the handler has the information and
context it needs to return a valuable response.
@RequestMapping(path="boys/throws",
method=RequestMethod.GET)
public ResponseEntity<String> createBoyThrows(@RequestParam("name") String name) {
someMethodThatMayThrowException(name); ①
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_LOCATION, url)
.body(String.format("hello %s, how do you do?", name));
}
Note the new method endpoint with the exception handler returns the same, explicit HTTP
response as the earlier example.
curl -v http://localhost:8080/rpc/greeter/boys/throws?name=blue
27
...
< HTTP/1.1 422
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 15
...
boy named blue?
28
Chapter 12. Summary
In this module we:
• identified two primary paradigms (synchronous and reactive) and web frameworks (Spring
MVC and Spring WebFlux) for implementing web processing and communication
• implemented an HTTP endpoint for a URI and method using Spring MVC annotated controller in
a fully synchronous mode
• demonstrated how to pass parameters between client and service using path and query
parameters
• demonstrated how to pass return results from service to client using http status code, response
headers, and response body
• demonstrated how to invoke methods from a Spring MVC RestTemplate and Spring WebFlux
WebClient
29