Source Allies Logo

Sharing Our Passion for Technology

& Continuous Learning

<   Back to Blog

Taking Advantage of Spring MVC's Default Behavior

Over the last several months I have worked on several content heavy websites for one of our partners. When I say “content heavy”, I mean that 80%-90% of the pages in the application are static, or at least mostly static, a customer name, membership number, etc may need to be floated in, but not big data tables with dynamic data being pulled from the database. The marketing department manages this content with their content management system and publish fully formed HTML pages (layout, look and feel, etc is controlled in the CMS) which are then pulled into our /WEB-INF/jsp/content directory by our build process.

Our applications treat these HTML pages as JSPs (simple rename in the build script). This lets the marketing team work from a cheat sheet of EL expressions such as ${customerName} and keeps the IT department out of the day to day content management work. One of our goals with these systems was to easily and seamlessly deal with both static pages and very dynamic pages requiring custom controllers to be built. With just a little bit of work Spring MVC makes it easy to provide this functionality and also provides a sane set of defaults for building out these web sites one page at a time.

Under the hood, Spring MVC can be very complicated with its Handlers, Handler Mappings, Controllers, Views, ViewResolvers, etc. Thanks to the Annotation based version of Spring MVC introduced in Spring 2.5, today we are really only going to need to worry about Controllers and ViewResolvers. The only way to truly make handling both static pages and controller based pages seamless is to route all page requests through the Spring MVC servlet. At the same time, we don't want to make changes to the application configuration every time the marketing department creates a new page or even a new folder full of pages. This requires we configure Spring MVC to have a very specific default behavior.

View Resolver

First up is the view resolver. We can't specify all the views ahead of time and the only way the marketing department communicates new pages to us is by publishing them into our JSP directory, Springs InternalResourceViewResolver is the perfect fit.

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/content/" />
    <property name="suffix" value=".jsp" />
</bean>

Assuming the build script pulls the published HTML files into the projects /WEB-INF/jsp/content directory and renames them with a “.jsp” extension, then all the pages should be available with a view that matches the path to the page, minus the ViewResolver's specified prefix and suffix. For example, if we have a page stored at /WEB-INF/jsp/content/members/profile.jsp, A controller can access that page by returning a view name of members/profile.

The Default Controller

Now that there is a view resolver capable of seeing all the pages produced by the marketing department, a controller must be created and mapped so that it will produce the correct view name.

package com.sourceallies.base;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class DefaultController {

    @RequestMapping("/**/*")
    public void defaultRequest(){}
}

That was easy. It would be hard to come up with a simpler controller, in fact it doesn't look like it does anything at all! In reality it is taking advantage of Spring MVC's default behavior for just about everything. With annotation based controllers, request mapped methods have a variety of possible return types. Strings are interpreted as view names, and ModelView objects are used like they were in previous versions of Spring MVC. But if the method has no return type then we get some interesting behavior. On void methods (or String methods that return null) Spring MVC looks at the request URI, strips off the app context and any file extensions, and uses whatever is left over as the view name. For example, if my application is mapped to /corporate/ and my request URL is /corporate/members/profile.html then Spring MVC will generate a view name of “members/profile”. The other thing to note is the RequestMapping, /**/* is the most generic mapping there is. It will match any request that comes through the Spring MVC servlet. This is how to avoid having to add new mappings for every new page. This is exactly what we want to use when working with the ViewResolver we defined earlier. All of the pages from the CMS live relative to each other, and the URLs requested from the client should match the file structure from the CMS.

So at this point I now have a more complicated version of the normal servlet request process. Its usefulness becomes apparent when marketing needs a new piece of data on a specific page and I need to create a controller.

@Controller
public class ProfileController {

    @RequestMapping("/members/profile.html")
    public void testRequest(Model model) {
        Profile profile = ...LookupProfile

        model.addAttribute("profile", profile);
    }
}

All I have to do is create a new controller and map it to the page name from the marketing department. And by having the request method be void, I once again don't have to worry about view names. Because the request mapping is more specific than the one on the DefaultController, Spring MVC chooses this controller when the profile page is requested. This setup allows the marketing department to very quickly prototype entire sites while at the same time not having to worry about IDEs, app servers, etc. They get to stay inside the CMS and the IT department only has to write custom code and configuration for the pages that need it.