This recipe introduces some more advanced concepts and tools related to Controllers such as ViewResolvers
, URI Template Patterns, and Spring MVC's injection-as-argument. The recipe is quite simple but there is more to talk about.
We will keep working from the same codebase state as the previous recipe where we have pulled the v2.2.1 tag from the remote repository. It will only be about creating one Controller with its handler method.
edu.zipcloud.cloudstreetmarket.portal.controllers
, the following DefaultController
has been created:@Controller public class DefaultController { @RequestMapping(value="/*", method={RequestMethod.GET,RequestMethod.HEAD}) public String fallback() { return "index"; } }
http://localhost:8080/portal/whatever
or http://localhost:8080/portal/index
URL with your browser.This second recipe revisits the use of the @RequestMapping
annotation. With no longer a fixed URI as a path value but with an opened-pattern (fallback). The recipe also makes use of the configured View resolver that we didn't use before.
The template word is recurring in the Spring terminology. It usually refers to generic support Spring APIs to be instantiated in order to fill specific implementations or customisations (REST template to make REST HTTP requests, JMS template to send JMS messages, WS template to make SOAP webservices requests, JDBC template, and so on). They are bridge the developer needs to Spring core features.
Under this light, URI templates allow configuring generic URIs with patterns and variables for controller end points. It is possible to instantiate URI builders that will implement URI templates but developers probably mostly use URI templates in the support they provide to @RequestMapping
annotations.
We have made use of these types of pattern to define the path value for our fallback handler method:
@RequestMapping(value="/*", ...)
This specific case, with the *
wildcard, allows whichever request URI starts with a /
after the application display name to be eligible for being handled by this method.
The wildcard can match a character, a word, or a sequence of words. Consider the following example:
/portal/1, /portal/foo, /portal/foo-bar
A limitation would be to use another slash in the last sequence:
/portal/foo/bar
Remember the difference with the table here:
|
All resources and directories at the level |
|
All resources and directories at the level and sublevels |
We have been using the single wildcard on purpose in the cloudstreetmarket-webapp
application. It might be more suited for other types of applications to redirect every unmatched URI to a default one. In our case of a single page application that will be strongly REST oriented, it is nicer to inform the client with a 404
error when a resource hasn't been found.
It is not the only option to use wildcards at the end of path patterns. We could have implemented the following type of pattern if needed:
/portal/**/foo/*/bar
(not for fallback purposes, though).
We will see that Spring MVC, to select one handler compares, all the matching Path patterns and selects the most specific of them.
For example, the following definition would have defined the path pattern /portal/default/*
for our fallback controller:
@RequestMapping(value="/default"...) @Controller public class DefaultController…{ @RequestMapping(value="/*"...) public String fallback(Model model) {...} }
A pathpattern comparison is done by Spring MVC when a given URL matches more than one registered path-pattern, to choose which handler the request will be mapped to.
The first criterion is the number of counted variables and wildcards in the compared path patterns: the pattern having the lowest number of variables and wildcards is considered the most specific.
To discriminate two path-patterns that have the same cumulated number of variables and wildcards, remember that the one with the lowest number of wildcards will be the most specific and then the longest path will be the most specific.
Finally a pattern with double wildcards is always less specific than a pattern without any.
To illustrate this selection, let's consider the following hierarchy going from the most to the least specific:
/portal/foo
/portal/{foo}
/portal/*
/portal/{foo}/{bar}
/portal/default/*/{foo}
/portal/{foo}/*
/portal/**/*
/portal/**
In
dispatcher-context.xml
of cloudstreetmarket-webapp, we have defined the viewResolver
bean:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>
A viewResolver
bean is a specific instance of a predefined class used to serve an organized and uniform set of view layers. In the case that we have configured, the viewResolver
bean is an instance of InternalResourceViewResolver
, which can serve JSP pages, handle the JSTL and tiles. This class also inherits UrlBasedViewResolver
that can navigate the application resources and can bind a logical view name to a View resource file. This capability prevents the creation of extramappings.
In our configuration, we have defined the view repository (/WEB-INF/jsp/*.jsp)
and we can directly refer to index.jsp
with the String index
.
It is better practice to set up the JSP repository under /WEB-INF
so those JSPs cannot be targeted publicly. Rather than a JSP templating, we could have used Velocity or Freemarker respectively using the view resolvers VelocityViewResolver
or FreeMarkerViewResolver
.
Also, we will talk about the ContentNegotiatingViewResolver
later on when we build the REST API.
This section highlights particularly the @PathVariable annotation. This annotation is an annotation for controller method-handler arguments (we have introduced all of them in the previous recipe).
You will find later, on several examples, the method-level @RequestMapping
annotations . Those annotations will sometimes be related to @PathVariable
annotations on the method-handler arguments. For now, let's consider the following example:
@RequestMapping(value="/example/{param}") public HttpEntity<String> example(@PathVariable("param") String parameter) { return new HttpEntity<>(parameter); }
As announced before, @PathVariable
tells Spring MVC where and how to realize its injection-as-argument from the request URI. The framework will parse the current URI Template pattern to extract the variable named param
and will inject the matching value in the current URI into the targeted method-argument.
We also declare an HTTPEntity
to be returned as a response. This HTTPEntity
will be a wrapper of a String generic type. Inside the method-handler, we instantiate this wrapper with the necessary String element.
If we would call for the /portal/example/foo
URI, it would be displayed as a response from the body of the returned HTTPEntity
: the String foo
.
With another interesting feature, we could have built this last scenario with the following declaration for @PathVariable
:
@RequestMapping(value="/example/{param}") public HttpEntity<String> example(@PathVariable String param) { return new HttpEntity<>(param); }
We will explore other features with regard to @RequestMapping
and @PathVariable
.