Привязка данных абстрактного класса в Spring-MVC - PullRequest
14 голосов
/ 28 августа 2010

Я просмотрел документацию и исходный код Spring и до сих пор не нашел ответа на свой вопрос.

У меня есть эти классы в моей доменной модели, и я хочу использовать их в качестве вспомогательных объектов формы в spring-mvc.


public abstract class Credentials {
  private Long     id;
  ....
}
public class UserPasswordCredentials extends Credentials {
  private String            username;
  private String            password;
  ....
}
public class UserAccount {
  private Long              id;
  private String            name;
  private Credentials       credentials;
  ....
}

Мой контроллер:


@Controller
public class UserAccountController
{
  @RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
  public @ResponseBody Long saveAccount(@Valid UserAccount account)
  {
    //persist in DB
    return account.id;
  }

  @RequestMapping(value = "/listAccounts", method = RequestMethod.GET)
  public String listAccounts()
  {
    //get all accounts from DB
    return "views/list_accounts";
  }
  ....
}

В пользовательском интерфейсе у меня есть динамическая форма для различных типов учетных данных. Мой POST-запрос обычно выглядит так:


name                    name
credentials_type        user_name
credentials.password    password
credentials.username    username

При попытке отправить запрос на сервер выдается следующее исключение:


org.springframework.beans.NullValueInNestedPathException: Invalid property 'credentials' of bean class [*.*.domain.UserAccount]: Could not instantiate property type [*.*.domain.Credentials] to auto-grow nested property path: java.lang.InstantiationException
    org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:628)

Моей первоначальной мыслью было использовать @ModelAttribute


    @ModelAttribute
    public PublisherAccount prepareUserAccountBean(@RequestParam("credentials_type") String credentialsType){
      UserAccount userAccount = new PublisherAccount();
      Class credClass = //figure out correct credentials class;
      userAccount.setCredentials(BeanUtils.instantiate(credClass));
      return userAccount;
    }

Проблема этого подхода заключается в том, что метод prepareUserAccountBean вызывается перед любыми другими методами (например, listAccounts), что не подходит.

Одним из надежных решений является выдвижение prepareUserAccountBean и saveUserAccount на отдельный контроллер. Это звучит неправильно: я хочу, чтобы все связанные с пользователем операции находились в одном классе контроллера.

Любое простое решение? Можно ли как-то использовать DataBinder, PropertyEditor или WebArgumentResolver?

Спасибо !!!!!

Ответы [ 2 ]

3 голосов
/ 02 сентября 2012

Я не вижу ни одного простого и элегантного решения. Может быть, потому, что проблема не в том, как привязать данные к абстрактным классам в Spring MVC, а в том, почему в первую очередь иметь абстрактные классы в объектах формы? Я думаю, что вы не должны.

Объект, отправленный из формы в контроллер, называется «формой (вспомогательным) объектом» по причине: атрибуты объекта должны отражать поля формы. Если ваша форма имеет поля имени пользователя и пароля, то в вашем классе должны быть атрибуты имени пользователя и пароля.

Так что credentials должен иметь тип UserPasswordCredentials. Это пропустит вашу ошибку «абстрактная попытка создания экземпляра». Два решения для этого:

  • Рекомендуется: вы изменяете тип UserAccount.credentials с Credentials на UserPasswordCredentials. Я имею в виду, какие учетные данные могут иметь UserAccount, кроме UserPasswordCredentials? Более того, я уверен, что ваша учетная запись userAccounts содержит имя пользователя и пароль, сохраненные в качестве учетных данных, поэтому вы также можете иметь тип UserPasswordCredentials непосредственно в UserAccount. Наконец, Spring рекомендует использовать «существующие бизнес-объекты в качестве объектов команды или формы» ( см. Документ ), поэтому изменение UserAccount было бы целесообразным.
  • Не рекомендуется: вы сохраняете UserAccount как есть и создаете класс UserAccountForm. Этот класс будет иметь те же атрибуты, что и UserAccount, за исключением того, что UserAccountForm.credentials имеет тип UserPasswordCredentials. Затем при перечислении / сохранении класс (например, UserAccountService) выполняет преобразование. Это решение предполагает некоторое дублирование кода, поэтому используйте его только в том случае, если у вас есть веская причина (устаревшие объекты, которые вы не можете изменить, и т. Д.).
0 голосов
/ 29 августа 2010

Я не уверен, но вы должны использовать классы ViewModel на ваших контроллерах вместо доменных объектов.Затем внутри вашего метода saveAccount вы должны проверить эту ViewModel, и если все пойдет хорошо, вы отобразите ее в свою модель домена и сохраните ее.

Тем самым у вас есть еще одно преимущество.Если вы добавите любое другое свойство в свой класс UserAccount своего домена, например: private bool isAdmin .Если ваш веб-пользователь отправит вам параметр POST с isAdmin = true, который будет привязан к классу домена пользователя и сохранен.

Ну, я бы так и сделал:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...