在String转换为一组对象的情况下,最好的Spring转换器策略是什么?

Kar*_*och 5 java spring converter spring-mvc javabeans

我的一个观点中有以下(简化)表格:

<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>
Run Code Online (Sandbox Code Playgroud)

哪个将绑定到以下JavaBean:

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

  // setters and getters omitted
}
Run Code Online (Sandbox Code Playgroud)

因为我想使用Spring 3的所有新功能,我使用注释驱动的控制器来接收POST请求:

@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
  }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我需要将输入文本(看起来像tag1, tag2, tag3)转换为Tag列表,定义如下:

public class Tag {
  private String name;

  // setter and getter omitted
}
Run Code Online (Sandbox Code Playgroud)

使用Spring 3.0有几种策略可以做到这一点:

(对不起,很长的帖子,问题以粗体显示)

最简单的

编程新属性tagsAsText以将getter/setter设置为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
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法有两个缺点:

  • 我在我的域对象中包含转换逻辑,这是一个问题吗?
  • 我在哪里可以访问BindingResult字符串中的错误?

使用BeanInfo

我也可以为我的bean使用BeanInfo:

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());
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

并声明一个转换器

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
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法也有两个缺点:

  • 每次添加/更改我的Entry类时,我都需要编辑我的BeanInfo.或者有没有办法有一个简单的方法来定义我的BeanInfo(比如"对于这个属性,使用它,否则只是像往常一样")
  • 我在哪里可以访问BindingResult字符串中的错误?

使用转换器

Converter使用Java 5的通用机制:

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

这种方法看起来更优雅,但仍有两个缺点:

  • 如果我在属性中配置此转换器,似乎我重新定义了所有默认转换器ConversionServiceFactoryBean,有没有办法保留默认转换器?
  • (再次)我在哪里可以访问BindingResult字符串中的错误?

ska*_*man 5

这是一个经过深思熟虑的问题,即使它会吓跑大多数人:)

无论如何,我认为选项(2)是最接近实际解决方案的。我的第一个建议是将标签列表封装到它自己的模型类中。这将为数据绑定框架提供一个具体的类型来注册,而List和 则String过于笼统。

所以你会有模型类:

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;
   }
}
Run Code Online (Sandbox Code Playgroud)

然后你就有了一个PropertyEditor知道如何与 a 相互转换的 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
   }
}
Run Code Online (Sandbox Code Playgroud)

最后,您需要告诉控制器使用转换器:

@Controller
public class EntryController {

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

   // request mappings here
}
Run Code Online (Sandbox Code Playgroud)

我相当确定新的 Spring 3 Converter 框架会产生更优雅的解决方案,但我还没有弄清楚:) 不过,我知道这种方法是有效的。