16.10 Convention over configuration

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.

This convention over configuration support address the three core areas of MVC - namely, the models, views, and controllers.

16.10.1 The Controller - ControllerClassNameHandlerMapping

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.

An example; consider the following (simplistic) Controller implementation. Take especial 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.

Let's look at some more examples so that the central idea becomes immediately familiar.

  • 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

  • DisplayShoppingCartController maps to the '/displayshoppingcart*' request URL

    (Notice the casing - all lowercase - in the case of camel-cased Controller class names.)

In the case of MultiActionController handler classes, the mappings generated are (ever so slightly) more complex, but hopefully no less understandable. Some examples (all of the Controller names in this next bit are assumed to be MultiActionController implementations).

  • AdminController maps to the '/admin/*' request URL

  • CatalogController maps to the '/catalog/*' request URL

If you follow the pretty standard convention of naming your Controller implementations as xxxController, then the ControllerClassNameHandlerMapping will save you the tedium of having to firstly define and then having to maintain a potentially looooong SimpleUrlHandlerMapping (or suchlike).

The ControllerClassNameHandlerMapping class extends the AbstractHandlerMapping base class so you can define HandlerInterceptor instances and everything else just like you would with many other HandlerMapping implementations.

16.10.2 The Model - ModelMap (ModelAndView)

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 being 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. Find below some examples of the 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'll 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 potentially 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 (i.e. the addObject(..) call will essentially be a no-op).

16.10.3 The View - RequestToViewNameTranslator

The RequestToViewNameTranslator interface is responsible for determining a logical View name when no such logical view name is explicitly supplied. It has just one implementation, the rather cunningly named DefaultRequestToViewNameTranslator class.

The DefaultRequestToViewNameTranslator maps request URLs to logical view names in a fashion that is probably best explained by recourse to an 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. It is the DefaultRequestToViewNameTranslator that will be tasked with generating a logical view name from the URL of the request. In the case of the above RegistrationController, which is being used in conjunction with the ControllerClassNameHandlerMapping, a request URL of 'http://localhost/registration.html' will result in a logical view name of 'registration' being generated by the DefaultRequestToViewNameTranslator. This logical view name will then be resolved into the '/WEB-INF/jsp/registration.jsp' view by the InternalResourceViewResolver bean.

[Tip]Tip

You don't even need to define a DefaultRequestToViewNameTranslator bean explicitly. If you are okay with the default settings of the DefaultRequestToViewNameTranslator, then you can rely on the fact that the Spring Web MVC DispatcherServlet will actually instantiate an instance of this class if one is not explicitly configured.

Of course, if you need to change the default settings, then you do need to configure your own DefaultRequestToViewNameTranslator bean explicitly. Please do consult the quite comprehensive Javadoc for the DefaultRequestToViewNameTranslator class for details of the various properties that can be configured.