Wednesday, April 12, 2006

Spring MVC and lost man-months

Like many others, I hopped on the Spring MVC bandwagon with sparkling eyes. Many futile developer-months later (both for me and the small team of developers I lead), I can step back and say definitively: Spring MVC is not fit for prime-time. It purports to be a replacement for Struts, and it is. But Struts has many replacements, and the Spring MVC team has done the Java community a disservice by muddying the waters with their mildly evolutionary offering. I suspect it is because the Spring folks want to be a "one-stop shop" for J2EE developers: the more of their stack you use, the more time you will spend reading their books and attending their trainings -- the more valuable their brains become. The problem is, Spring MVC has a lot of problems under the hood. I detail a partial list of gripes below. I will add to this list if/when I think of others.

Binding you down

The form-binding mechanism provided in Spring MVC has proven to be a constant headache. A little searching through the Spring Web Forum will convince you that no one really knows how to bind complex form elements to Javabean collections (List, Set, etc.). This one flaw will constantly bite you if you dare to use Spring MVC. Someone may tell you to just use an array and save yourself some hassle (it's typed, etc.). Don't be fooled so easily: that may work for Spring, but not for Hibernate, which needs your collections to be interfaces, so that it can work its behind-the-scenes magic on them. (And chances are, if you're using Spring, you're probably using Hibernate too). I have spent many hours sifting through the source code in the Eclipse debugger trying to understand what's going on during various binding snafus. The first strangeness came onscreen almost immediately after I attached the Eclipse debugger to Tomcat. The debugger kept halting at a curious line where an exception was being created for no apparent reason. It was the BindException, and it gets created every time the binding process occurs (hint: every single web request). It turns out the BindException is central to the design of MVC. Every request causes a BindException. Hmm. All sorts of wonderful stuff gets stuffed inside the BindException. Assuming you can get binding to work at all, you'll have the pleasure of using <spring:bind> in your JSP views. One real nicety is that it runs your property values through property editors so that you get an appropriately-formatted string value. This works great for viewing objects and presenting their properties as editable values. Like every other web application in existence, mine also needed to be able to display objects in tables (the list view). And I needed those to display the same neatly-formatted values as you would see in the detail view. No problem, right? Just drop your <spring:bind> tags inside a <jsp:foreach> loop so that each row would have nicely-formatted values. For a simple collection where the concrete class of every element is identical and matches your controller's commandClass property, that works fine. But the collection I needed to display was based on an interface type, where the concrete class of each element may vary. The <spring:bind> tag does not like that, because it is hard-wired to deal with only one command class in any given request, invariable at view-time. So we quickly hit a wall where we could not utilize property-editor-based display formatting for something so simple as a table.

Custom property editors, sort of

Spring allows you to register custom property editors. It can also use the system property editors provided by the java.beans.Introspector. What it does not do is leverage BeanInfo classes very well. For example, if you provided BeanInfo classes (easily generated using doclets) then a PropertyDescriptor can have a propertyEditorClass. But Spring makes no attempt to use that class. This flaw cannot be understated: Spring promises to treat your command objects as JavaBeans, but then it ignores the BeanInfo classes that make JavaBeans somewhat plausible as a component framework. Without the ability to leverage BeanInfo classes your JavaBeans are just a fiesta of getters and setters. So how do you get custom property editors for your beans? The Spring Way, of course: by registering them manually, in the controller, when you handle the request. That, combined with the single-class binding limitations described earlier, conspire to insure that a Spring controller has zero reusability -- you can make it work for exactly one concrete class, but don't get your hopes up about writing a controller that can be reused easily.

Resource "/WEB-INF/myconfig.xml" not found

Let's say one of my beans wants to load a resource: "myconfig.xml". If I include it in my dispatcher servlet's web application context as an import, it will be loaded relative to the directory the spring context file is in (usually /WEB-INF). Now what if I want beans in my context to be able to load resources themselves, from that same directory? Sorry, no luck. The web application context resource loader pins its root at the webapp root, NOT the /WEB-INF subdirectory where the context and other configuration files are typically located. Furthermore, there is no easy way to modify this: it's buried deep within the helper classes that spring app contexts use to do the initial resource load. That means you have to specify your resources as "/WEB-INF/myconfig.xml" -- which then breaks when you try to run tests outside of a webapp container. Because when you're running tests, you're using a different application context class, and chances are it's loading resources relative to its own location. The alternative is to place all your configuration files in your webapp root, which may be accessible to end users. Probably not a good idea... I cannot easily communicate the pain and frustration this has caused---that something so simple as a root resource path cannot be modified easily. I eventually subclassed web application context so that I could override the way it determines the resource root. The pain has subsided somewhat since then...


Okay, back to square one: let's learn Spring MVC by starting in the step-by-step tutorial. Throw some code here, paste some XML there, and voila, you have a controller available to you at /hello.htm. Wait a second: /hello.htm? Does anyone but me find that strange that all your controllers are mapped via *.htm? Perhaps in some strange way it's a step up from the *.do typical of Struts lore. Or perhaps it's how we trick our users into thinking we're using static files created in Front Page. But what if we're outputting XML or JSON from the controller? What if we want to support content negotiation, or alternate representations, or worse, what if we have an honest-to-goodness static file that ends in *.htm? "Houston, we have a problem." Or at least a very misleading URL. In adopting Spring MVC for my company's project, I first set out to wrest control over URLs by providing the capability to map to clean ones. Luckily, Spring MVC promises a lot of flexibility in mapping. You can write your own Handler classes and your own HandlerMapper classes. So I created one that maps regular expressions to controllers. Each controller can select a different view template based on the extension provided in the URL: so /myapp.xml and /myapp.html both go to the same controller, but get routed to a different view template. Things got worse when I realized that URL mappings in web.xml are very limited. To get the flexibility I needed, the Spring dispatcher would have to capture "/*" in web.xml. That meant that static files would no longer be handled by the servlet container's default servlet. DANGER WILL ROBINSON! I ended up writing a Servlet Filter to wrap the Dispatcher Servlet. This presented its own set of problems, as the Spring Dispatcher does not think it's running from within a servlet -- so if you're not careful you can end up with infinite forwarding loops when trying to forward to a view. I was able to eliminate that problem by having the servlet filter set a request attribute that basically says, "I was here already" and then ignore requests that it had already seen before. In the end I was relatively happy with the way I can map URLs now. Unfortunately, I realize in retrospect that I pretty much rewrote the mapping system entirely. There are a lot of things I could still improve, but when all is said and done I would basically have my own web subframework written inside of Spring MVC.

Dog Food, and the eating of it

There is a saying Joel Spoelsky of fame really likes: "Eat your own dog food." And Spring simply does not do this. You can probably read about the reasons why somewhere on their site:
"No!" you say. "Not PHP! They probably just have their Spring dispatcher mapped to *.php!" No, they really are using PHP. Somewhere on the internet I remember reading their list of apologetic reasons for doing this: "use the right tool for the job" and all that. So which job is right for the Spring MVC tool? It's obviously not the right tool for "the leading full-stack Java/J2EE application framework" and perhaps it's not a good idea for you either.


n8 said...

Nice smack-down--I see why you're researching other Web frameworks. ;)

You've got Interface21's motivations all figured out. Spring MVC is not appropriate for their website because their website is not a hypothetical example in one of their ponderous books.

smackyou said...

I recently made a post on their forums asking about this(using PHP, Drupal, PHPBB and the like) and they threatened to ban me for being off topic.

Jeoff Wilks said...

You were most definitely off topic. You were asking about the topic of "getting things done" but their topic is "selling training/books/consulting".