Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data binding of an abstract class in spring-mvc

I've went thru Spring documentation and source code and still haven't found answer to my question.

I have these classes in my domain model and want to use them as backing form objects in 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;
  ....
}

My controller:


@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";
  }
  ....
}

On UI I have dynamic form for the different credential types. My POST request usually looks like:


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

Following exception is thrown if I try to submit request to the server :


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)

My initial thought was to use @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;
    }

Problem with this approach is that prepareUserAccountBean method get called before any other methods (like listAccounts) as well which is not appropriate.

One robust solution is to move out both prepareUserAccountBean and saveUserAccount to the separate Controller. It doesn't sound right : I want all user-related operations to reside in the same controller class.

Any simple solution? Can I utilize somehow DataBinder, PropertyEditor or WebArgumentResolver?

Thank you!!!!!

like image 686
Denys K. Avatar asked Aug 28 '10 06:08

Denys K.


People also ask

What is data binding in Spring MVC?

Data binding is useful for allowing user input to be dynamically bound to the domain model of an application (or whatever objects you use to process user input). Spring provides the so-called DataBinder to do exactly that.

Does Spring MVC have Customisable data binding?

In the last section, we saw how to bind data submitted by an HTML form or by query string parameters to a form-backing bean. In order to do the binding, Spring MVC internally uses a special binding object called WebDataBinder ( org. springframework.

What is Formatter in Spring MVC?

Formatters are used to convert String to another Java type and back. So, one type must be String . You cannot, for example, write a formatter that converts a Long to a Date . Examples of formatters are DateFormatter , for parsing String to java. util.

What is binding in spring?

(sprɪŋ ˈbaɪndɪŋ ) a method of securing loose sheets of paper in a binder, using a mechanism containing a metal spring.


1 Answers

I can't see any simple and elegant solution. Maybe because the problem is not how to data bind abstract classes in Spring MVC, but rather : why having abstract classes in form objects in the first place ? I think you shouldn't.

An object sent from the form to the controller is called a "form (backing) object" for a reason : the object attributes should reflect the form fields. If your form has username and password fields, then you should have username and password attributes in your class.

So credentials should have a UserPasswordCredentials type. This would skip your "abstract instantiation attempt" error. Two solutions for this :

  • Recommended : you change the type of UserAccount.credentials from Credentials to UserPasswordCredentials. I mean, what Credentials could a UserAccount possibly have, except a UserPasswordCredentials ? What's more, I bet your database userAccounts have a username and password stored as credentials, so you could as well have a UserPasswordCredentials type directly in UserAccount. Finally, Spring recommends using "existing business objects as command or form objects" (see doc), so modifying UserAccount would be the way to go.
  • Not recommended : you keep UserAccount as is, and you create a UserAccountForm class. This class would have the same attributes as UserAccount, except that UserAccountForm.credentials has a UserPasswordCredentials type. Then when listing/saving, a class (UserAccountService for example) does the conversion. This solution involves some code duplication, so only use it if you have a good reason (legacy entities you cannot change, etc.).
like image 57
Jerome Dalbert Avatar answered Sep 28 '22 01:09

Jerome Dalbert