Thursday, October 16th, 2008...1:36 pm

Reusing Annotation Based Controllers in Spring MVC

Jump to Comments

I was a late comer to the wonderful world of Spring. Seeing that I hate XML, I am very keen on using annotations everywhere! Everything I read said that using annotation based controllers wasn’t a good idea because they were hard to re-use due to their very fluid rules for method signatures.

The first thing I did in Spring MVC was to write the perennial CRUD application. After I was done the code was fairly clean but still contained plenty of boilerplate java code. So I wanted to make it generic so I could churn out CRUD’s for maintenance tables.

Goals:

  1. Only write code that’s specific to the Entity I am dealing with in the controller.
  2. Make use of <Generics> so I don’t have to cast stuff like a caveman from 1997.
  3. Have spring figure out URI mapping based on controller names.
  4. Write zero bean specific xml.


Anotomy of a CRUD Controller.
Your classic CRUD (Create Update Delete) Controller works with one domain type and needs to do a few things:

  1. List items
  2. Create items
  3. Update items
  4. Delete items

As far as controller methods go, you’ll generally need to do a few things:

  • Set up your backing domain object for your form on creations and edits
  • Validate and save results from the form on submission.
  • List items from your data store.

The Old Way

Let’s look at how we’d accomplish listing items with Spring MVC.
[java]
@RequestMapping(“/employee/list”)
public ModelMap list(ModelMap model) {
model.addAttribute(dao.selectAll(Employee.class));
return model;
}
[/java]

So this method uses my dao to run a query. (Your DAO Service implementation will be different depending on how you access your database.)

It then takes the results of that query and throws it in the ModelMap. Recall the ModelMap is smart and will come up with the correct attribute name. So if selectAll returns a List of Employees it will be added to the model as employeeList. Great. Now we use the @RequestMapping annotation to set our URL mapping to employee/list.

The New Way

Let’s look at how we could make this list method generic. We’ve got two things that explicitly reference our Employee domain object. The URL mapping and the .class we pass to our DAO service.

Solving the URL problem
If we define a generic list method in our abstract controller one option for configuring the specific url in the extending class would be something like this.

[java]
@RequestMapping(“department/list”)
public ModelMap list(ModelMap map)
{
super(map);
}
[/java]

I don’t like this method, it assumes the person implementing the super class will know that they must override the list method just to set a request mapping. A better method would be if Spring was able to infer the URL map from the implementing controller’s class name. ControllerClassNameHandlerMapping to the rescue!

Spring’s ControllerClassNameHandlerMapping will take a controller’s class name and map it to a URL. I like to mix this with the SimpleUrlHandlerMapping to map views the same way.

Here’s how I configure these beans:

[xml]
class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">



class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">



[/xml]

So what all this means is that if someone requests: /department/list it will automatically map to a Controller called DepartmentController and the @RequestMapping annotated method called list. There’s no need to define a URL mapping in our @RequestMapping annotation, we’ll just define a request type instead.

Dynamic entity/domain class

There are many ways we can take care of the problem. The coolest way is to use some neat reflection tricks like so:

[java]
@SuppressWarnings(“unchecked”)
protected Class getEntityClass()
{
return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
[/java]

So now we have a generic list method:

[java]
@RequestMapping(method = RequestMethod.GET)
public ModelMap list(ModelMap model) {
model.addAttribute(dao.selectAll(getEntityClass()));
return model;
}
[/java]

Creating/Updating

The normal pattern for doing creating and updating in Spring MVC controllers consists of a few methods.

Methods to display the initial form:
These methods simply show the same form view.

I implemented them like this, we assume the view name from the Domain object (entity)’s name:

[java]
@RequestMapping(method = RequestMethod.GET)
public String create(ModelMap model, WebRequest request) {
return ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/form”;
}

@RequestMapping(method = RequestMethod.GET)
public String update(ModelMap model) {
return ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/form”;
}
[/java]

Now before the form is submitted we need to tell Spring to bind our form to a @ModelAttribute. This is the same as a FormBackingObject in classic Spring MVC.

The way it works is any method annotated with @ModelAttribute will be executed and the returned object bound to any matching forms in a view.

In this view I do one of two things. If we’re going to be doing a create I return a new Domain object. This domain object may have some default values set to get initially bound in to the form. If we’re doing an update I need to look up the Domain Object from the DAO Service.

Since our @ModelAttribute annotated method will be called on each request I need a way to tell if this is an update request or a create request. An easy way is just to check if our Primary Key parameter is set in the WebRequest.

Here’s what a generic @ModelAttribute method looks like:
[java]
@ModelAttribute(“crudObj”)
public T setupModel(WebRequest request) {
String pk = request.getParameter(getPkParam());

if(pk == null || org.apache.commons.lang.StringUtils.isEmpty(pk)) {
return getEntityInstance();
} else {
return (T)dao.find(getEntityClass(), Long.parseLong(pk));
}
}
[/java]

So we need another abstract method called getPkParam that returns the String representing the domain property that the Primary Key is stored in.
If on your database the primary key is always called “id” then you could certainly implement getPkParam to return “id” every time in the superclass.

So far we have implemented methods that do the following in our superclass: Forward a request to the correct form view and setup the model object to make it available for automatic binding in our view.

The next step is to define what happens when a form is submitted. When a form is submitted we’ll have to do 2 things, weather and update or an insert we need to Validate the form and persist the Domain object to the database if validation passes.

Due to the @ModelAttribute annotation the model object will now be available in your view as {$crudObj}. Ideally we would make it available based on the type name so the view was more readable, but that’s one of the limitations we have when using annotations is the inability to add dynamic values as parameters.

Let’s look at a generic save method:
[java]
@RequestMapping(method = RequestMethod.POST)
public String save(@ModelAttribute(“crudObj”) T entity, BindingResult bindingResult) {
//validate entity
getValidator().validate(entity, bindingResult);

//return to form if we had errors
if(bindingResult.hasErrors())
return ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/form”;

//merge into datasource
dao.save(entity);

//return to list
return “redirect:/” + ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/list.html”;
}

[/java]

We have a new abstract method called getValidator(Object obj,BindingResult res) that will return the validator for your Domain class.

Implementation

We now have all the pieces in place.

Finished CrudController<T>
[java]
public abstract class CrudController {

//Bring in a generic DAO that can handle crud operations.
@Autowired
protected SimpleDao dao;

public CrudController() {
super();
}
abstract protected Validator getValidator();

@SuppressWarnings(“unchecked”)
protected Class getEntityClass()
{
return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}

@SuppressWarnings(“unchecked”)
protected T getEntityInstance()
{
try {
return (T) Class.forName(getEntityClass().getName()).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

//assume the primary key property is going to be the Entity Class plus Seq
protected String getPkParam()
{
return ClassUtils.getShortNameAsProperty(getEntityClass()) + “Seq”;
}

@RequestMapping(method = RequestMethod.GET)
public ModelMap list(ModelMap model) {
model.addAttribute(dao.selectAll(getEntityClass()));
return model;
}

@RequestMapping(method = RequestMethod.GET)
public String create(ModelMap model, WebRequest request) {
return ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/form”;
}

@RequestMapping(method = RequestMethod.GET)
public String update(ModelMap model) {
return ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/form”;
}

@RequestMapping(method = RequestMethod.POST)
public String save(@ModelAttribute(“crudObj”) T entity, BindingResult bindingResult) {
//validate entity
getValidator().validate(entity, bindingResult);

//return to form if we had errors
if(bindingResult.hasErrors())
return ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/form”;

//merge into datasource
dao.save(entity);

//return to list
return “redirect:/” + ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/list.html”;
}

@RequestMapping(method = RequestMethod.POST)
public String delete(String isInsert, @ModelAttribute(“crudObj”) T entity, BindingResult bindingResult) {
//delete
dao.delete(entity);

//return to list
return “redirect:/” + ClassUtils.getShortName(getEntityClass()).toLowerCase() + “/list.html”;
}

@ModelAttribute(“crudObj”)
public T setupModel(WebRequest request) {
String pk = request.getParameter(getPkParam());

if(pk == null || org.apache.commons.lang.StringUtils.isEmpty(pk)) {
return getEntityInstance();
} else {
return (T)dao.find(getEntityClass(), Long.parseLong(pk));
}
}

//Set up any custom editors, adds a custom one for java.sql.date by default
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyy-MM-dd”);
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomSqlDateEditor(dateFormat, false));
}

}
[/java]
Now if I want to add another CRUD to my application I simply have to do the following:

Implement my abstract CrudController for the Domain class I want to edit. So let’s say we wanted a department controller.

It’s definition would look like this:

[java]
@Controller
public class DepartmentController extends CrudController {

@Override
protected Validator getValidator() {
return new DepartmentValidator();
}
}
[/java]

Next I’d define my validator class, it might look something like this:
[java]
public class DepartmentValidator implements Validator {

public boolean supports(Class departmentClass) {
// TODO Auto-generated method stub
return Department.class.equals(departmentClass);
}

public void validate(Object healthScope, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, “departmentName”,”departmentName”);
}

}
[/java]

View set up
If you notice sometimes I pull the view name from the Domain class, this method is obviously assuming a certain directory structure on your view side:

Here’s what my view directory looks like:
[code]
WEB-INF
/jsp
/department
form.jsp
list.jsp
[/code]

The last thing I’d do is define my jsp’s like this: (You can make your jsp’s however you want, i’m just showing some example ones for completeness.)

list.jsp
This page just lists our data using the handy DisplayTag library.

[java]

<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib uri="http://displaytag.sf.net" prefix="display" %>



Departments

“>[Create New]





[/java]

form.jsp
Displays a form for both a new and edit. Notice this pulls from the command object “crudObj” that was set up in the @ModelAttribute(“crudObj”) annotated method.

[java]
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>



Create/Edit Department

Name:



[/java]

Summary

Let’s review:

  • By using a generic templated base class we reap the benefits of Spring’s “configuration by convention” and our controller is more specific and easy to use.
  • We take advantage of Configuration by Convention again to automatically have meaningful url’s without having to explicitly configure them thanks to Spring’s ControllerClassNameHandlerMapping.

The way I created an abstract controller above was just one way to do it, however I believe I’ve shown that just because your controller classes don’t extend an old-style Spring controller class doesn’t mean you can’t take advantage of inheritance to make your own life easier, even when using annotations.

6 Comments

Leave a Reply