For a lot of projects, sticking to established conventions and
having reasonable defaults is just what they (the projects) need... this
theme of convention-over-configuration now has explicit support in Spring
Web MVC. What this means is that if you establish a set of naming
conventions and suchlike, you can substantially cut
down on the amount of configuration that is required to set up handler
mappings, view resolvers, ModelAndView
instances,
etc. This is a great boon with regards to rapid prototyping, and can also
lend a degree of (always good-to-have) consistency across a codebase
should you choose to move forward with it into production.
Convention-over-configuration support addresses the three core areas of MVC -- models, views, and controllers.
The ControllerClassNameHandlerMapping
class
is a HandlerMapping
implementation that
uses a convention to determine the mapping between request URLs and the
Controller
instances that are to handle
those requests.
Consider the following simple
Controller
implementation. Take special
notice of the name of the class.
public class ViewShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // the implementation is not hugely important for this example... } }
Here is a snippet from the attendent Spring Web MVC configuration file...
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewShoppingCart" class="x.y.z.ViewShoppingCartController"> <!-- inject dependencies as required... --> </bean>
The ControllerClassNameHandlerMapping
finds
all of the various handler (or
Controller
) beans defined in its
application context and strips Controller
off the
name to define its handler mappings. Thus,
ViewShoppingCartController
maps to the
/viewshoppingcart*
request URL.
Let's look at some more examples so that the central idea becomes
immediately familiar. (Notice all lowercase in the URLs, in contrast to
camel-cased Controller
class
names.)
WelcomeController
maps to the
/welcome*
request URL
HomeController
maps to the
/home*
request URL
IndexController
maps to the
/index*
request URL
RegisterController
maps to the
/register*
request URL
In the case of MultiActionController
handler classes, the mappings generated are slightly more complex. The
Controller
names in the following
examples are assumed to be MultiActionController
implementations:
AdminController
maps to the
/admin/*
request
URL
CatalogController
maps to the
/catalog/*
request URL
If you follow the convention of naming your
Controller
implementations as
xxxController
, the
ControllerClassNameHandlerMapping
saves you the
tedium of defining and maintaining a potentially
looooong
SimpleUrlHandlerMapping
(or suchlike).
The ControllerClassNameHandlerMapping
class
extends the AbstractHandlerMapping
base class so
you can define HandlerInterceptor
instances and everything else just as you would with many other
HandlerMapping
implementations.
The ModelMap
class is essentially a
glorified Map
that can make adding
objects that are to be displayed in (or on) a
View
adhere to a common naming
convention. Consider the following
Controller
implementation; notice that
objects are added to the ModelAndView
without any
associated name specified.
public class DisplayShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { List cartItems = // get a List of CartItem objects User user = // get the User doing the shopping ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- the logical view name mav.addObject(cartItems); <-- look ma, no name, just the object mav.addObject(user); <-- and again ma! return mav; } }
The ModelAndView
class uses a
ModelMap
class that is a custom
Map
implementation that automatically
generates a key for an object when an object is added to it. The
strategy for determining the name for an added object is, in the case of
a scalar object such as User
, to use the short
class name of the object's class. The following examples are names that
are generated for scalar objects put into a
ModelMap
instance.
An x.y.User
instance added will have
the name user
generated.
An x.y.Registration
instance added will
have the name registration
generated.
An x.y.Foo
instance added will have the
name foo
generated.
A java.util.HashMap
instance added will
have the name hashMap
generated. You probably
want to be explicit about the name in this case because
hashMap
is less than intuitive.
Adding null
will result in an
IllegalArgumentException
being thrown. If the
object (or objects) that you are adding could be
null
, then you will also want to be explicit
about the name.
The strategy for generating a name after adding a
Set
, List
or array object is to peek into the collection, take the short class
name of the first object in the collection, and use that with
List
appended to the name. Some examples will make
the semantics of name generation for collections clearer...
An x.y.User[]
array with one or more
x.y.User
elements added will have the name
userList
generated.
An x.y.Foo[]
array with one or more
x.y.User
elements added will have the name
fooList
generated.
A java.util.ArrayList
with one or more
x.y.User
elements added will have the name
userList
generated.
A java.util.HashSet
with one or more
x.y.Foo
elements added will have the name
fooList
generated.
An empty
java.util.ArrayList
will not be added at all
(in effect, the addObject(..)
call will
essentially be a no-op).
The RequestToViewNameTranslator
interface determines a logical View
name
when no such logical view name is explicitly supplied. It has just one
implementation, the
DefaultRequestToViewNameTranslator
class.
The DefaultRequestToViewNameTranslator
maps
request URLs to logical view names, as with this example:
public class RegistrationController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // process the request... ModelAndView mav = new ModelAndView(); // add data as necessary to the model... return mav; // notice that no View or logical view name has been set } }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <!-- this bean with the well known name generates view names for us --> <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/> <bean class="x.y.RegistrationController"> <!-- inject dependencies as necessary --> </bean> <!-- maps request URLs to Controller names --> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
Notice how in the implementation of the
handleRequest(..)
method no
View
or logical view name is ever set on
the ModelAndView
that is returned. The
DefaultRequestToViewNameTranslator
is tasked with
generating a logical view name from the URL of the
request. In the case of the above
RegistrationController
, which is used in
conjunction with the
ControllerClassNameHandlerMapping
, a request URL
of http://localhost/registration.html
results in a
logical view name of registration
being generated by
the DefaultRequestToViewNameTranslator
. This
logical view name is then resolved into the
/WEB-INF/jsp/registration.jsp
view by the
InternalResourceViewResolver
bean.
Tip | |
---|---|
You do not need to define a
|
Of course, if you need to change the default settings, then you do
need to configure your own
DefaultRequestToViewNameTranslator
bean
explicitly. Consult the comprehensive Javadoc for the
DefaultRequestToViewNameTranslator
class for
details of the various properties that can be configured.