Thursday, March 01, 2007

SimpleFormController demystified - part 1

Spring MVC is a very flexible powerful Model 2 framework, but as with all flexible frameworks, it comes with a price - confusion. In order to write a controller you have the freedom/burden to choose between implementing one of two single-method interfaces, or inherit from one of the 13 (!) skeleton implementations available. Most of the time you're probably going to want one of the extremes: as much help or as little help as possible. The former scenario is typically things like a LogoutController, where you just invalidate the session and redirect to the default start page or whatever. Good candidates for this is either to implement the Controller or the ThrowawayController interface, or inherit from AbstractController, which adds a few cache header setters and some other stuff. Using the AbstractController approach, it would look like this:

public class LogoutController extends AbstractController {

public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
request.getSession().invalidate();
return new ModelAndView("redirect:/start.html");
}

}

Another candidate is when you want to delete a blog post, in which case you're only interested in a single parameter:

public class DeletePostController implements ThrowawayController {

Long id;
BlogDao blogDao;

public Long getId() { return id; }

public void setId(Long id) { this.id = id; }

public ModelAndView execute() {
blogDao.deletePost(getId());
return new ModelAndView("redirect:/posts/list.html");
}

}

The ThrowawayController is instantiated on every request, and binds parameters directly onto the controller class instead of a separate model bean, similar to the WebWork/Struts2 approach.

So in general, if you're only interested in zero or one parameter, and don't care about validation or anything, the as-simple-as-possible approach is probably right.

But this article series is supposed to be about the controller base class that gives the maximum amount of support, SimpleFormController (SFC). Some people think that's an oxymoron, but I'm going to try and show the easy ways to accomplish many common tasks using SFC.

SFC handles two actions: preparing a form view and showing it, and taking care of the submission and showing a resulting view. By default, the request method determines what kind of action to take - GET means prepare and show the form, POST means submission (it is possible to override isFormSubmission(), but it's very rarely necessary and we can disregard that for now). That is why there are two views to configure.

Suppose you have the concept of a User in your domain, and you need a way to create and edit these users in a web form. There is already a domain class named User, that's persisted in some way, and it has two properties: firstName and lastName (we'll add more later on). In order to build a controller that is capable of storing a new User, you need to do the following:

public class UserController extends SimpleFormController {

UserDao userDao;

public UserController(UserDao userDao) {
this.userDao = userDao;
setCommandClass(User.class);
}

protected void doSubmitAction(Object command) {
User user = (User) command;
userDao.store(user);
}

}

And some configuration in yourApp-servlet.xml (view parameters can be set in the constructor too, if you prefer that, and you can move the command class configuration out to XML too):

<bean name="/user/create.html" class="com.example.UserController">
<constructor-arg ref="userDao">
<property name="formView" value="user/editForm">
<property name="successViewView" value="user/created">
</bean>

This is really all there is to it. The form preparation phase is simply an instantiation of the command class, in our case a User, and rendering of the "user/editForm" view. When the form is submitted, all parameters are bound to a User instance and handed to us in the doSubmitAction() callback method where we simply store it. The superclass will then render the success view.

The default handler mapping (what controller that handles what urls) is the BeanNameUrlHandlerMapping, which uses the name attribute for registering controller beans to urls. It accepts wildcards and space-separated lists: name="/foo /bar", name="/user/*" etc.

Don't forget to set the form method to POST in the HTML page, otherwise the controller will think that you want a new form every time you submit the form. Note that the action url will be /user/create.html as well, unless you map both /user/create.html and /user/store.html to the same controller.

The input names should match the properties on the command object:

<input type="text" name="firstName"/>

The command object itself is available in the view under the default name "command", so you can access properties like this: ${command.lastName}. That will be more interesting in part 2, when we'll edit existing users.

3 comments:

Sean Tindale (tinny) said...

Thanks for preparing this tutorial, its really helped me get started with Spring MVC. Good Work

Shivangi said...

good tutorial...
helped me to understand when n how formBackingObject works

Freekeswar said...

Nice tutorial.

Eswar.
VaanNila