Last time I introduced an example application I wrote to illustrate Spring WebMVC for a Java class. I think the application is a nice example because it also illustrates the ability to add a REST API to an existing standalone Java application using Jetty as an embedded servlet container.
I’m presenting this example in a series of posts because I learned from personal experience teaching this that the more “under the covers” behavior there is, be it classpath scanning, annotation configuration, reflection, or proxying, the harder it can be for new folks to grasp. Lots of people know way more than I do about Spring WebMVC, but I’m hoping to lay out in detail what I do know. As a result, this post will focus just on the controller class. The business logic of the class is intentionally very simple in order to avoid being distracted from what Spring WebMVC is doing for us.
The Java code for the controller class is:
doCalc method is here for completeness but we can ignore it. The Spring
WebMVC behavior is configured through the annotations. Note that we annotate
both the class and the methods. The class-level annotation I discussed last
time; it’s used by the Spring classpath scanning function to automatically
find classes that should be added to the Spring application context. By using
@Controller rather than
@Service we also tell WebMVC to
search for method-level annotations.
The details of the method-level annotations are really going to depend on how the REST API requests and responses should look to clients. This simple example only illustrates a few:
@RequestMapping: This annotation provides a unique path for the service and sets up path variables. It also allows specifying what type of HTTP request should be accepted (e.g. GET, POST, PUT, DELETE).
@ResponseBody: Informs WebMVC that the Java object returned by the method should be used in the body of the HTTP response (suitably converted, as discussed below).
@PathVariable: Matches a method parameter to a specific item in the request URL.
@RequestBody: The entire body HTTP request is converted to the type of the parameter before the method is called. As you might expect, only one parameter can get this annotation, though other parameters can be path variables or request parameters (discussed below).
It is up to the application to make sure that the various @RequestMapping paths don’t conflict. A naming scheme is definitely the way to go.
The selection of the HTTP request method is important to make a REST API align with user expectations and the typical behavior for REST servers. The use of POST in the example above is non-standard as the method does not result in a state change for that particular resource. Really that method should be a GET as well since it’s returning unmodified state.
Path Variables and Request Parameters
Path variables deserve some additional discussion. A key idea of REST is that
where possible, a clear, logical URI should be assigned to a long-lived resource.
In this case, we can think of a calculation as a “resource” — if we add
2+2, we will always get the same calculation object back, with the result of 4.
It therefore makes sense to think of the URI
as the permanent “home” of the calculation:
On the other hand, there may be some transient parameter we wish to pass to the server that could hold different values for the same calculation. To extend our admittedly silly example, we might have a mode of our calculator that delegated the calculation to an external engine for performance reasons. The resulting calculation would be the same, so we wouldn’t want to encode that directly into the URI; we would want to use a query parameter.
Deciding between the two approaches is important in creating a clear API. Spring
WebMVC of course supports both, using the
@PathVariable annotation we’ve seen
@RequestParam annotation for HTTP query parameters. To finish our
example, consider the following method declaration:
The declaration is the same as before, but we’ve added a query parameter. We
don’t modify the request mapping to do this, because the Java Servlet API does
not see two identical URLs with different query parameters as different URLs, and
neither does Spring WebMVC. The resulting method could be accessed using a URL like
Above I mentioned that Spring will automatically do type conversion on the request body
and the response body. Spring will also perform automatic conversion on parameters, so
in the example above if someone tried to call
the request would not even reach our method. WebMVC would send back an HTTP error 400
(Bad Request) for us.
Conversion from string for parameters is generally straightforward. Conversion of request and response bodies is more complicated. REST APIs typically use JSON or XML, and Spring WebMVC is able to support both, as well as custom converters.
There is nothing in our example anywhere that configures JSON conversion, but we get it
for free by having the necessary Jackson libraries in the Maven
pom.xml file. This is
because we used WebMVC’s annotation-driven configuration. As a result, when Spring
instantiates our controller class, WebMVC actually generates a proxy. The proxy wraps
our various controller methods:
- Before our method is called, the proxy calls Jackson when required to convert the request body to a Java object.
- The proxy calls our request method.
- The proxy calls Jackson with the Java object that’s returned from our method to convert it to the right form for the response.
That completes this second post on this simple example using WebMVC. Next time we’ll quickly discuss the client before discussing how Jetty is used to add a REST API to a standalone Java program.