Monday, October 08, 2007

Spring MVC Seems Overly Complicated

Chris Sawer wrote an article on Using Spring's MVC framework for web form validation. I haven't used Spring MVC a lot so I am not here to say he did it right or wrong. However, if he did it the right way I'm glad I am not using Spring MVC.

I thought it might be a good idea to show an alternative using the same example Chris used but I'll show mine in Stripes. I'm not going to repeat Chris' code here so you can follow along using another tab or open another browser window.

So the first thing Chris mentions beyond the web.xml file is the configuration for the controller bean in servlet configuration xml file. Stripes doesn't require controllers (Stripes calls them Action Beans) to be configured anywhere so we've already saved a bit of configuration.

Next Chris shows the SpringTestController.java and the SpringTestValidator; both of which are required to validate a form. Here is the Stripes version (Since I have no idea what properties a DataBean refers to I'll use my own object):

The code formatting didn't turn out exactly as I had planned so bare with me until I find a better solution. The gist of the post just be understandable, however.


import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;
import org.apache.log4j.Logger;
import com.miadidas.model.User;

public class StripesTestActionBean implements ActionBean {

protected final Logger logger = Logger.getLogger(StripesTestActionBean.class);
private ActionBeanContext context;

@ValidateNestedProperties({
@Validate(field = "username", required = true),
@Validate(field = "password", required = true)
})
private User user;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public ActionBeanContext getContext() {
return this.context;
}

public void setContext(ActionBeanContext context) {
this.context = context;
}

public Resolution doSubmitAction() {
/* The givenData object now contains the successfully validated
* data from the form, so can be written to a database, or whatever.
*/
return new RedirectResolution("We would redirect to a new view at this point");
}
}


And now the JSP, however mine may be a bit different because of the DataBean I was unclear on:


<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<html>
<head>
    <title>Stripes Test</title>
</head>
<body>
<stripes:errors />
<stripes:form beanclass="com.test.web.action.StripesTestActionBean" method="post">
    <fieldset>
        <p>
            Username: <stripes:text name="username" />
        </p>
        <p>
            Password: <stripes:password name="password" />
        </p>
        <p>
            <stripes:submit name="doSubmitAction" value="Login" />
        </p>
    </fieldset>
</stripes:form>
</body>
</html>


And that's it. To me, its simpler. I'd like to hear what you think.

9 comments:

Rockstar said...

Just curious, how would your code example look if you needed to do something more sophisticated than required=true? Say I have a registration form, and I want to check to see if a username is taken. (Spring MVC makes it trivial to access a service or DAO in a validator, which is why I ask.)

Gregg said...

There's 2 options. 1) You can provide an @ValidationMethod in your ActionBean (Controller) that accesses the DAO for the validation or 2) your ActionBean can implement ValidationErrorHandler and you would override the handleValidationErrors method and provide the validation.

Both methods would require you setting an error in ValidationErrors for the page to render however that is as trivial as errors.add(...)

Nick L said...

Good post. I enjoy reading how Frameworks compare. After looking at both entries. I noticed that they are really about the same. I wouldn't say one is more straight-forward than the other.

You need to configure your beans in both.

You have and Action Bean or Controller in both.

You seem to do your validations with Annotations, which is cool and can be really quick.

One question I would have. (And this could be just a lack of understanding of Stripes) Lets say your application has a couple of objects. (Office and Home) You have 2 forms. One to enter a Home and one to enter a business. So you need to put your annotations into both Action Beans to validate the Name, Phone Number, and Address. In Spring you can tie your validators together. Like this...


public class HomeValidator implements Validator
{

private AddressValidator addressValidator;

public boolean supports(Class givenClass)
{
return givenClass.equals(Home.class);
}

public void validate(Object obj, Errors errors)
{
Home home = (Home) obj;
// validate what you want on a home object.
try
{
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, home.getAddress(), errors);
} finally {
errors.popNestedPath();
}

....



and



public class OfficeValidator implements Validator
{
private AddressValidator addressValidator;

public boolean supports(Class givenClass)
{
return givenClass.equals(Office.class);
}

public void validate(Object obj, Errors errors)
{
Office office = (Office) obj;
// validate what you want on an office object.
try
{
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, office.getAddress(), errors);
} finally {
errors.popNestedPath();
}

....


I know this is trivial stuff, but can save a lot of time with not re-writing code / annotations. It seems to tie your validation code to each Action instead of the object that needs to be validated. So every object that had an Address object in it, would have to rewrite the validation. What is the validation changes? Would you have to change all your Actions?

I'm not trying to bash Stripes as it looks like a very nice MVC framework. I am actually thinking of trying it out now. I would definitely look at it if my project wasn't already using Spring in the business / DAO layers.

Oh...one other thing. Don't let that Chris's jsp fool you. Those spring:bind tags are old. With Spring 2.0 they have form: tags. These act alot more like your stripes tags. (like this... form:text path="firstName")

Check them out here

Mike said...

You've replaced a his validation with annotations. Of course thats going to seem faster and easier.

Also, because you have declared the Spring controllers in the xml file, you now have dependency injection available to you. Need a UserService for that login page? Easy


public void setUserService(final UserService userService) {
this.userService = userService;
}


And now I have a UserService to authenticate with.

At anyrate, I don't think your comparison of the two frameworks is a good comparison. You haven't proven that one MVC is better over the other. They are just different.

Gregg Bolinger said...

Nick,

Thanks. One note, you said:

"You need to configure your beans in both."

Not true. Stripes ActionBeans are not configured at all. They are "discovered" by Stripes. Ues, I still use Spring to IoC for my Service/DAO layer, but the MVC is completely dependent of Spring.

As far as Validation, I see your point. In Stripes what I do is create a class that other ActionBeans which need those same objects validated can extend. So I too have reusable code.

Gregg Bolinger said...

Mike:

"You've replaced a his validation with annotations. Of course thats going to seem faster and easier."

Well yea, that's part of the point of why its simpler. And it doesn't "seem" simpler and faster, it is.

I'm not clear on your point about having Spring controllers available but Stripes has an @SpringBean annotation which allows me the same comforts as Spring MVC only I don't have to wire MVC specific code in an XML file.

@SpringBean("userService)
public void setUserService(UserService service)
{
this.userService = service;
}

Yes, I still use Spring. But my point is Spring MVC "seems" more complicated than it needs to be. I guess I skimmed over too much and it was a spur of the moment post after reading Chris' article.

I do appreciate your opinion, Mike.

John said...

"In Spring you can tie your validators together. Like this..."

Yes this is all transparent with the concept of type converters. Which I think is the true gem of the stripes framework: TypeConverterFactory and TypeConverter<T>

@ValidateNestedProperties({
@Validate(field="address", converter=AddressTypeConverter.class),
...
})
private Office office;

similar for Home

class AddressTypeConverter implements TypeConverter<Address> {
public Address convert(String input, Class<Address> targetType, List<ValidationError> errors) {
// check fields & add errors here
}
}

SN said...

Spring MVC now supports JSR-303 out of the box. It would be interesting to compare Stripes with Spring 3.0

Anonymous said...

Buzz List to look for the Cheap GW2 Gold of researched terminology during the day, which been 'World Pot 2006', 'WWE', 'FIFA', 'Shakira', and 'Paris Hilton'. Next, i searched each and every expression in the big a few search engines like google (Search engines, Bing!, and Bing) and checked the top 10 recent results for every single with all the validator. Find It will set it up One hundred fifty of the most important data details on the net with the day time. The outcome had been specifically surprising to me * simply 7 of the One humdred and fifty ensuing internet pages experienced legitimate html (Several.7%). Ninety-seven with the 150 experienced alerts (Sixty four.7%) even though 46 in the One hundred fifty obtained the red times (30.7% Guild Wars 2 TW).