Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the best Spring converter strategies in the case of a String to convert to a set of object?

I have the following (simplified) form in one of my view:

<form:form commandName="entry" method="POST">
  <form:input type="text" path="name"/>
  <form:input type="text" path="tags" />
  <input type="submit" value="Submit"/>
</form:form>

Which is going to be bind to the following JavaBean:

public class Entry {
  private String name;
  private List<Tag> tags = new LinkedList<Tag>();

  // setters and getters omitted
}

because I want to take use all new fancy features of Spring 3, I'm using annotation-driven controller to receive the POST request:

@Controller
@RequestMapping("/entry")
public class EntryController {

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView show() {
    ModelAndView mav = new ModelAndView("entry");
    mav.addObject(new Entry());
    return mav;
  }

  @RequestMapping(method = RequestMethod.POST)
  public String add(@ModelAttribute("entry") @Valid Entry entry, 
                    BindingResult result) {
    // check validation from Binding result
    // execute method on business beans: adding this entry to the system
    // return a view if correct
  }
}

As you can see, I need to convert my input text (which look like tag1, tag2, tag3) as a list of Tag, define like this:

public class Tag {
  private String name;

  // setter and getter omitted
}

There is several strategies to do this with Spring 3.0:

(Sorry long post, questions are in bold)

The simplest

Programming a new property tagsAsText to have a getter/setter as String:

public class Entry {
  // ...

  public void setTagsAsText(String tags) {
    // convert the text as a list of tags 
  }

  public String getTagsAsText() {
    // convert list of tags to a text
  }
}

This approach has two drawbacks:

  • I include the conversion logic in my domain object, is it a problem ?
  • Where can i access to the BindingResult in the case of error in the string ?

Using BeanInfo

I can also use a BeanInfo for my bean:

public class EntryBeanInfo extends SimpleBeanInfo {

  public PropertyDescriptor[] getPropertyDescriptors() {
    try {
      @Override
      PropertyDescriptor tagsDescriptor = new PropertyDescriptor("tags", Entry.class) {
        @Override  
        public PropertyEditor createPropertyEditor(Object bean) {
                return new EntryTagListEditor(Integer.class, true);
            };
        };
        // omitting others PropertyDescriptor for this object (for instance name)
        return new PropertyDescriptor[] { tagListDescriptor };
    }
    catch (IntrospectionException ex) {
        throw new Error(ex.toString());
    }
  }
}

And declare one converter

public class EntryTagListEditor extends PropertyEditorSupport {

  public void setAsText(String text) {
    // convert the text to a list of Tag
  }

  public String getAsText() {
    // convert the list of Tag to a String
  }
}

This approach has also two drawbacks:

  • I need to edit my BeanInfo every time I add / change my Entry class. or is there any way to have a simple way to define my BeanInfo (like "for this property, use this, else just do as usual")
  • Where can i access to the BindingResult in the case of error in the string ?

Using Converter

Converter uses the generic mechanism of Java 5:

final class StringToTagList implements Converter<String, List<Tag>> {
  public List<Tag> convert(String source) {
    // convert my source to a list of Tag
  }
}

This approach looks more elegant but still two drawbacks:

  • It seems I redefine all default Converters if I configure this converter in the Property of ConversionServiceFactoryBean, is there any way to keep the default Converters ?
  • (again) Where can i access to the BindingResult in the case of error in the string ?
like image 490
Kartoch Avatar asked Jun 22 '10 16:06

Kartoch


People also ask

What are converters in Spring MVC and what they do?

Spring 3.0 introduces a simple Converter interface that you can implement and reuse anywhere in Spring. You can use them in Spring MVC to convert request String values to Controller method parameter values of any Object type that you can write a Converter for.

What is the use of converter in spring boot?

A converter converts a source object of type S to a target of type T . Implementations of this interface are thread-safe and can be shared.

Does spring convert all values to target data types?

Spring provides out-of-the-box various converters for built-in types; this means converting to/from basic types like String, Integer, Boolean and a number of other types.

What is an HttpMessageConverter in spring rest?

HttpMessageConverter is a strategy interface that specifies a converter that can convert from and to HTTP requests and responses in Spring REST Restful web services. Internally Spring MVC uses it to convert the Http request to an object representation and back.


1 Answers

A well-considered question, even it'll scare most people off :)

Anyway, I think option (2) is the closest to a practical solution. My first suggestion is that you encapsulate the list of tags into its own model class. This will give the data binding framework a concrete type to register against, whereas List and String are much too general.

So you would have the model classes:

public class Entry {
  private String name;
  private TagList tagList;
}


public class TagList {

   private final List<Tag> tags;

   public TagList(List<Tag> tags) {
      this.tags = tags;
   }

   public List<Tag> getTags() {
      return tags;
   }
}

You then have a PropertyEditor that knows how to convert to and from a TagList:

public class TagListEditor extends PropertyEditorSupport {

   @Override
   public void setAsText(String text) throws IllegalArgumentException {
      TagList tagList = // parse from the text value
      setValue(tagList);
   }

   @Override
   public String getAsText() {
      TagList tagList = (TagList) getValue();
      return tagList.toString(); // or whatever
   }
}

And finally, you need to tell the controller to use the converter:

@Controller
public class EntryController {

   @InitBinder
   public void initBinder(WebDataBinder binder) {
      binder.registerCustomEditor(TagList.class, new TagListEditor());
   }

   // request mappings here
}

I'm fairly sure the new Spring 3 Converter framework would produce a more elegant solution, but I haven't figured it out yet :) This approach, however, I know works.

like image 161
skaffman Avatar answered Oct 05 '22 21:10

skaffman