spring-mvc中抽象类的数据绑定

Posted

技术标签:

【中文标题】spring-mvc中抽象类的数据绑定【英文标题】:Data binding of an abstract class in spring-mvc 【发布时间】:2011-04-05 02:56:14 【问题描述】:

我浏览了 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";
  
  ....

在 UI 上,我有不同凭据类型的动态表单。我的 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)之前被调用,这是不合适的。

一个可靠的解决方案是将prepareUserAccountBeansaveUserAccount 移出到单独的控制器。听起来不对:我希望所有与用户相关的操作都驻留在同一个控制器类中。

任何简单的解决方案?我可以以某种方式使用 DataBinder、PropertyEditor 或 WebArgumentResolver 吗?

谢谢!!!!!!

【问题讨论】:

您可以发布您的域类中的完整代码吗?我想看看你的构造函数。 在我看来,这更像是数据库的映射/序列化/转换问题,而不是视图。在发送到视图之前,帐户是否被正确反序列化为正确的对象? 【参考方案1】:

我看不到任何简单而优雅的解决方案。也许是因为问题不在于如何在 Spring MVC 中对抽象类进行数据绑定,而是:为什么首先在表单对象中有抽象类?我认为你不应该。

从表单发送到控制器的对象被称为“表单(支持)对象”,原因是:对象属性应该反映表单字段。如果你的表单有用户名和密码字段,那么你的类中应该有用户名和密码属性。

所以credentials 应该有一个UserPasswordCredentials 类型。这将跳过您的“抽象实例化尝试”错误。两种解决方案:

推荐:您将 UserAccount.credentials 的类型从 Credentials 更改为 UserPasswordCredentials。 我的意思是,除了 UserPasswordCredentials 之外,UserAccount 可能具有哪些 Credentials?更重要的是,我敢打赌您的数据库 userAccounts 将用户名和密码存储为凭据,因此您也可以直接在 UserAccount 中输入 UserPasswordCredentials。最后,Spring 建议使用“现有业务对象作为命令或表单对象”(see doc),因此修改 UserAccount 是可行的方法。 不推荐:您保持 UserAccount 不变,然后创建一个 UserAccountForm 类。 该类将具有与 UserAccount 相同的属性,只是 UserAccountForm.credentials 具有 UserPasswordCredentials 类型。然后在列出/保存时,一个类(例如 UserAccountService)进行转换。此解决方案涉及一些代码重复,因此请仅在有充分理由(无法更改的旧实体等)的情况下使用它。

【讨论】:

【参考方案2】:

我不确定,但您应该在控制器上使用 ViewModel 类而不是域对象。然后,在您的 saveAccount 方法中,您将验证此 ViewModel,如果一切正常,您将其映射到您的域模型并持久化它。

这样做,您还有另一个优势。如果您将任何其他属性添加到您的域 UserAccount 类,例如:private bool isAdmin。如果您的网络用户向您发送一个带有 isAdmin=true 的 POST 参数,该参数将绑定到用户域类并持久化。

嗯,我就是这样做的:

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)

    //...

【讨论】:

但是它仍然没有解决原来的问题:如何绑定一个继承的类。

以上是关于spring-mvc中抽象类的数据绑定的主要内容,如果未能解决你的问题,请参考以下文章

Spring-MVC:需要最简单的表单处理、绑定和验证示例

java早期绑定的关键词都有哪些

类的绑定方法

面向对象

将多个类的数据绑定到单个列表视图/xamarin 表单

day14 多态与抽象