Каковы лучшие стратегии конвертера Spring в случае String для преобразования в набор объектов? - PullRequest
5 голосов
/ 22 июня 2010

У меня есть следующая (упрощенная) форма в одном из моих представлений:

<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>

Который будет привязан к следующему JavaBean:

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

  // setters and getters omitted
}

, поскольку я хочу использовать все новые необычные функции 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
  }
}

Как видите, мне нужно преобразовать свой входной текст (который выглядит как tag1, tag2, tag3) как список тегов, определив его так:

public class Tag {
  private String name;

  // setter and getter omitted
}

Существует несколько стратегий сделать это с помощью Spring 3.0:

( Извините, длинный пост, вопросы выделены жирным шрифтом )

Самый простой

Программирование нового свойства tagsAsText для получения метода получения / установки в виде строки:

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
  }
}

У этого подхода есть два недостатка:

  • Я включил логику преобразования в свой объект домена, это проблема?
  • Где я могу получить доступ к BindingResult в случае ошибки в строке?

Использование BeanInfo

Я также могу использовать 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());
    }
  }
}

и объявить один конвертер

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
  }
}

У этого подхода также есть два недостатка:

  • Мне нужно редактировать свой BeanInfo каждый раз, когда я добавляю / изменяю свой класс Entry. или есть какой-либо способ иметь простой способ определить мой BeanInfo (например, "для этого свойства, используйте это, иначе просто сделайте как обычно" )
  • Где я могу получить доступ к BindingResult в случае ошибки в строке?

Использование конвертера

Конвертер использует общий механизм Java 5:

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

Этот подход выглядит более элегантно, но все же имеет два недостатка:

  • Кажется, я переопределяю все конвертеры по умолчанию, если я сконфигурирую этот конвертер в свойстве ConversionServiceFactoryBean, есть ли способ сохранить конвертеры по умолчанию?
  • (снова) Где я могу получить доступ к BindingResult в случае ошибки в строке?

1 Ответ

2 голосов
/ 23 июня 2010

Хорошо продуманный вопрос, даже если он отпугнет большинство людей:)

В любом случае, я думаю, вариант (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;
   }
}

Затем у вас есть PropertyEditor, который знает, как преобразовывать в 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
   }
}

и, наконец, вам нужно указать контроллеру использовать конвертер:

@Controller
public class EntryController {

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

   // request mappings here
}

Я вполне уверен, что новая среда Spring 3 Converter создаст более элегантное решение, но я пока не понял :) Этот подход, однако, я знаю, работает.

...