Связывание данных абстрактного класса в spring -mvc

Я просмотрел документацию и исходный код 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?

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

Ответы

Ответ 1

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

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

Итак, credentials должен иметь тип UserPasswordCredentials. Это пропустит вашу ошибку "абстрактной попытки создания экземпляра". Два решения для этого:

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

Ответ 2

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

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

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

public class NewUserAccount {
    private String name;
    private String username;
    private String password;
}  

@RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public @ResponseBody Long saveAccount(@Valid NewUserAccount account)
{
    //...
}