- > Company
- > Company Blog
- > Blog Detail
Addressing dark corners of Spring MVC
23.07.2010 11:46 ( 0 comments )By Mindaugas Zaksauskas
In VYRE Unify core development team, we use a lot of Spring. We use it for the injection of our beans, as a framework for our custom portlets and so on. We wrap our data objects (DAOs) into Spring beans and integrate them with Hibernate.
This usually works well with some notable exceptions. And when it doesn’t, sometimes it takes days to figure out what went wrong and the bitterness about this can dampen the love for the framework itself.
But let’s write some code. Consider this very simple example:
public class MyPojo {
private String somethingDummy;
public String getSomethingDummy() {
return this.somethingDummy;
}
public void setSomethingDummy(String somethingDummy) {
this.somethingDummy = somethingDummy;
}
}
public class MyController {
// somewhere in the middle of this class
MyPojo myPojo = ...;
context.setAttribute(”myPojo”, myPojo);
}
<label for=”inputField”>
<input type=”text” id=”${status.expression}”
name=”${status.expression}” value=”${status.value}”/>
All classic, isn’t it? A simple POJO, trivial controller and dull JSP. That’s how most of the HOWTOs look like. You could say - “well, that’s fair enough, you don’t want newbies to be overloaded with complex examples” - and you are probably right. But this also means that the capabilities and quality of any framework can never be judged by examples given in the manual - it is the overall time saved that makes sense. And that “overall time” sometimes can be heavily affected by time spent chasing random use case.
But let’s move on and add a java.util.Map to the MyPojo class:
public class MyPojo2 extends MyPojo {
public Map<String, String> getJustAMap(); {...}
public void setJustAMap(Map<String, String> map) {...}
}
Again, nothing special - just a simple addition. Spring is still with us, allowing to bind and edit map values (let’s say map key “foo” does exist):
<label for=”inputField”>
<input type=”text” id=”${status.expression}”
name=”${status.expression}” value=”${status.value}”/>
The code above will allow you to bind map values (of type String in this case) to a JSP field and set it back to POJO when editing is done. But our life is rarely simple, and scalar map value types are not that uncommon. What if, for example, our Map was Map<String, List<String>> or Map<String, Set<String>>? If you tried to repeat the above exercise in a similar fashion I did for MyPojo2, it wouldn’t work. The reason for this is quite simple - Spring does not know what to do with a List (or Set) of map values; how to marshal and unmarshall them to and from an editable String field. So the answer to this problem is to define a property editor (converter), like one below:
public class MyEditor
extends PropertyEditorSupport
implements PropertyEditor {
@Override
public void setAsText(String text) {
//take the text, convert it into List/Set and set
setValue(...);
}
@Override
public String getAsText() {
Object value = getValue();
// convert value and return it as a String
return ...;
}
}
In fact what is a slight but pleasant surprise is that the interface and superclass comes from standard Java API (java.beans package).
If you have followed this example and ran it on your IDE, you should be angry. Adding the editor didn’t help; and the exception you’ve got will sound like a bit of gibberish:
Field error in object 'myPojo2' on field 'justAMap': rejected value [373]; codes [typeMismatch.myPojo2.justAMap,typeMismatch.myPojo2. justAMap,typeMismatch.justAMap[1],typeMismatch.justAMap, typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myPojo2.justAMap[1],justAMap[1]]; arguments []; default message [justAMap[1]]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'justAMap[1]'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [java.util.Set] for property 'justAMap[1]': PropertyEditor [MyEditor] returned inappropriate value]
How about showing that as an end user error on the screen?
In case you wondered, what does that mean, I’ll explain. The bottom part of the exception suggests that our editor was unable to convert String (which was used as an input field in the HTML) into a Set. But how is this possible if we are setting an instance of Set via setValue method??? In programmer circles cases like this are called WTFs. No other explanation necessary.
If you are adventurous and curious what went wrong here, try finding out for yourself and coming back with an answer in 48 hours (only if you’re lucky). If not, here’s the catch: Spring tries to be smart and detects that our Map is a collection. And once this is a collection it goes on and digs inside it and eventually messes everything up beyond any idea of how to fix this.
And the fix is quite simple. Instead of defining your Map as Map<String, Set<String>>, introduce a class that wraps Set<String>, i.e.
public class Wrapper {
public Set<String> getBackingCollection() {...}
public void setBackingCollection(Set<String> collection) {...}
}
so the Map becomes Map<String, Wrapper>. Believe it or not - just by introducing this wrapper and retrofitting both MyEditor and MyPojo2 fixes the problem.
This all makes me a bit sad, really. Year after year, framework after framework I have learned that each framework, no matter how popular and established it is, introduces another layer of bugs, glitches and workarounds. And Spring is not an exception here in any way - I could also provide similar failure stories about Hibernate, EJB, Lucene or virtually any other framework of your choice. And year after year when I hear fanboys of yet another new super-duper framework screaming about how cool their framework is I suddenly feel myself under an inch-thick shield of unbreakable skepticism. Am I getting too old?

Comments