Building web applications with Spring Web MVC
3.5 Configuring Spring Web MVC: the application context
3.5.1 Configuring HandlerMappings
We’ll begin with the HandlerMapping interface. Here’s the idea behind handler map-pings. Whenever DispatcherServlet receives a new HTTP request, it needs to find a handler (controller) to service that request. To this end DispatcherServlet main-tains an ordered list of 1–n HandlerMappings.7 When it receives a request, it checks each of its registered HandlerMappings, in order, to see whether it’s able to generate a handler for the given request. If so, then that’s the selected handler. If the process bot-toms out without a handler being selected, then DispatcherServlet generates an HTTP 404.
There’s more we can say about how HandlerMappings work, but let’s pause to digest what you’ve learned so far. Table 3.2 shows the different HandlerMapping imple-mentations available (and again, you can always write your own).8 Each mapping scheme describes a way to match a request to a handler bean.
RequestToViewNameTranslator (bean ID: viewNameTranslator)
1 DefaultRequestToViewNameTranslator
MultipartResolver (bean ID:
multipartResolver)
0–1 None
LocaleResolver (bean ID:
localeResolver)
1 AcceptHeaderLocaleResolver
ThemeResolver (bean ID:
themeResolver)
1 FixedThemeResolver
7 We know that all lists are ordered. We’re emphasizing the list order because it matters here.
8 As of Spring 3.0, CommonsPathMapHandlerMapping no longer exists. Use annotation-based request map-ping as a replacement.
Table 3.1 DispatcherServlet strategy interfaces and default implemetations (continued)
Strategy interface # Default implementation(s)
Let’s see how (most of) the individual HandlerMapping implementations work.
BEANNAMEURLHANDLERMAPPING
As indicated, BeanNameUrlHandlerMapping is one of the defaults you get if you don’t specify some other mapping. (If you do specify another mapping, then if you want the BeanNameUrlHandlerMapping you have to define it explicitly, because explicit handler mapping definitions displace the defaults.) It’s very simple. You use the con-troller bean’s name to specify the handler URLs that map to the controller. Here’s how it works:
<bean name="/contact/*" class="mypackage.MyController"/>
The URL is relative to the servlet path. This approach is nice for its simplicity, but it can be verbose if you have a lot of controllers. Let’s see another handler mapping—
one that’s concise.
CONTROLLERCLASSNAMEHANDLERMAPPING
ControllerClassNameHandlerMapping allows you to use the name of the controller to implicitly define the URLs that map to the controller. All you need to do is place the handler-mapping bean on the application context, and it’s activated. The mapping works for controllers defined using the old Controller hierarchy as well as for those defined using the newer @Controller annotation.
Table 3.2 HandlerMapping implementations
Implementation Mapping scheme
BeanNameUrlHandlerMapping Match the request URL with a handler bean name, which must be URL-like and begin with a slash (/):
for example, /contact.do. Wildcards are OK.
This mapping is activated by default if (and only if) you don’t specify mappings explicitly.
ControllerBeanNameHandlerMapping Match the request URL with a plain handler bean name, which is converted into a URL by prepending an optional prefix, appending an optional suffix, and prepending a slash.
ControllerClassNameHandlerMapping Match the request URL with a handler class name, which is converted into a base URL using a certain convention.
DefaultAnnotationHandlerMapping Match based on the presence of the
@RequestMapping annotation at the handler-type level or the existence of @Controller at the handler-type level and @RequestMapping at the handler-method level.
SimpleUrlHandlerMapping Match according to a map whose keys are URL paths (possibly wildcarded) and whose values are bean IDs or names.
Here’s how to put it on the app context:
<bean class="org.springframework.web.servlet.mvc.support.
➥ ControllerClassNameHandlerMapping"/>
Once you do that, URLs will map to controllers based on controller names. If you have a controller called ContactController, for example, requests like /contact and /contact/* will map to the ContactController. The exact mappings depend on the type of controller; the mappings we just described apply to MultiActionControllers and @Controller beans. For more information please consult the Javadocs for ControllerClassNameHandlerMapping.
DEFAULTANNOTATIONHANDLERMAPPING
This is the second default handler mapping that’s available, although only under Java 5. If you define another handler mapping explicitly, DefaultAnnotationHan-dlerMapping will be displaced and you’ll need to define it explicitly if you want it.
This handler mapping works by inspecting method-level @RequestMapping annota-tions. Under this handler mapping, any annotations discovered on the methods auto-matically generate mappings to the handler itself.
DefaultAnnotationHandlerMapping typically depends on a type-level @Control-ler annotation to determine whether a given bean generates mappings, but this isn’t strictly required. The other alternative is to have a type-level @RequestMapping annota-tion. One of the two type-level annotations must be present.
SIMPLEURLHANDLERMAPPING
This handler mapping is similar to BeanNameUrlHandlerMapping in the sense that it involves defining explicit URL/handler pairs in the application context file; but SimpleUrlHandlerMapping allows you to define multiple mapping patterns with a sin-gle bean; BeanNameUrlHandlerMapping allows only one mapping pattern per bean.
Here’s an example of how it works:
<bean class="org.springframework.web.servlet.handler.
➥ SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/index.html">coreController</prop>
<prop key="/about.html">coreController</prop>
<prop key="/contact/*">contactController</prop>
<prop key="/forums/*">forumsController</prop>
</props>
</property>
</bean>
As with the BeanNameUrlHandlerMapping, the URLs are relative to the servlet path.
This handler mapping provides a nice way to combine several mappings in a single bean definition.
USING MULTIPLE HANDLER MAPPINGS AT ONCE
You may find yourself needing to use multiple handler mappings in a single Dis-patcherServlet. Maybe you want to use ControllerClassNameHandlerMapping as
your main handler mapping strategy, but you want to use SimpleUrlHandlerMapping to cover some cases where your URLs don’t match the controller class name in the way that would be required for the ControllerClassNameHandlerMapping to work. What to do, what to do?
You can accomplish this by placing any desired handler mappings on the context and defining an order. DispatcherServlet will find all of your handler mappings, and it will determine handler-mapping priority based on the handler mapping’s order property, which all AbstractHandlerMappings have by virtue of implementing the Order interface. Here, the lower the number (with Integer.MIN_VALUE being the lowest possibility), the higher the precedence. When routing a request, Dispatch-erServlet iterates over its registered handler mappings starting with the highest-priority mapping, trying at each step to generate a handler match. Once a match is found, request processing continues with the matched handler. If there’s no match, DispatcherServlet generates an HTTP 404.
Here’s how it looks in code:
<bean class="org.springframework.web.servlet.handler.
➥ SimpleUrlHandlerMapping"
p:order="0">
<property name="mappings">
<props>
<prop key="/index.html">coreController</prop>
<prop key="/about.html">coreController</prop>
<prop key="/contact/*">contactController</prop>
<prop key="/forums/*">forumsController</prop>
</props>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.support.
➥ ControllerClassNameHandlerMapping"
p:order="1"/>
Now let’s look at what happens when the routing actually occurs. Although in most cases the request goes directly to a controller, that’s not the only way it works. You can define interceptors around the controller to modify processing both coming in and going out.
HANDLERINTERCEPTORS
Any HandlerMapping implementation extending from AbstractHandlerMapping (and that would include all of the HandlerMapping implementations provided out of the box) allows you to specify an array of interceptors—implementing the HandlerInter-ceptor interface—which wrap handler requests in the same way that servlet filters wrap servlet requests. Figure 3.3 shows how this works.
Spring comes with several out-of-the-box HandlerInterceptor implementations.
One example is WebContentInterceptor, which supports request checks such as checking whether the HTTP method is permissible and whether a session exists (if ses-sions are required). You can apply interceptors by injecting them into handler map-pings, as shown next.
...
<bean id="webContentInterceptor"
class="org.springframework.web.servlet.mvc.WebContentInterceptor">
<property name="supportedMethods">
<list>
<value>GET</value>
<value>POST</value>
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.support.
➥ ControllerClassNameHandlerMapping">
<property name="interceptors">
<list>
<ref bean="webContentInterceptor"/>
</list>
</property>
</bean>
...
In listing 3.7, you begin by defining an interceptor
B
. Here you’re using one of the ready-made interceptors; you’ll use it to block any HTTP request that isn’t a GET or a POST. You accomplish this by configuring the supportedMethods propertyC
, which is of course specific to this particular interceptor class. Although the property type is a String[], Spring knows how to convert your list into an array.Listing 3.11 How to intercept controllers with HandlerInterceptors
Figure 3.3 Control flow in a HandlerExecutionChain
Defines interceptor
B
Configures it
C
Defines handler mapping
D
Injects interceptors
E
References defined interceptor
F
Next is the handler-mapping definition
D
. Because this particular handler map-ping extends AbstractHandlerMapmap-ping, you can define an interceptors property and pass along the list of interceptorsE
. Here there’s only one. As previously men-tioned, the interceptors property expects an array, but Spring knows how to convert the list to an array. You pass in the single WebContentInterceptor you createdF
. The result will be that for any request coming through that handler mapping, if it isn’t either a GET or a POST, it will be blocked. For more information on interceptors, please see the HandlerInterceptor Javadocs.Now let’s move on to handler adapters, which allow Spring to be flexible with respect to the type of controllers it permits.