2010-08-28 9 views
14

He pasado por la documentación de Spring y el código fuente y todavía no he encontrado respuesta a mi pregunta.Enlace de datos de una clase abstracta en spring-mvc

Tengo estas clases en mi modelo de dominio y quiero usarlas como objetos de formulario de respaldo en 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; 
    .... 
} 

Mi controlador:


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

En la interfaz de usuario que tienen forma dinámica para los diferentes tipos de credenciales. Mi solicitud POST general se parece a:


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

excepción siguiente si trato de presentar solicitud al servidor:


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) 

Mi idea inicial era utilizar @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; 
    } 

Problema con este enfoque, se llama al método prepareUserAccountBean antes que a cualquier otro método (como listAccounts) que no sea apropiado.

Una solución robusta es mover ambos prepareUserAccountBean y saveUserAccount al controlador por separado. No suena bien: quiero que todas las operaciones relacionadas con el usuario residan en la misma clase de controlador.

¿Alguna solución simple? ¿Puedo utilizar de alguna manera DataBinder, PropertyEditor o WebArgumentResolver?

Gracias !!!!!

+0

¿Puedes publicar el código completo de las clases de tu dominio? Me gustaría ver a tus constructores. –

+0

Me parece que esto es más un problema de asignación/serialización/conversión con su base de datos que con la vista. ¿Las cuentas se deserializan correctamente en los objetos correctos antes de enviarlas a la vista? – DavidA

Respuesta

-1

No estoy seguro, pero debería usar clases de ViewModel en sus controladores en lugar de objetos de dominio. Luego, dentro de su método saveAccount usted validaría este ViewModel y si todo va bien, lo mapea en su Modelo de Dominio y lo conserva.

Al hacerlo, tiene otra ventaja. Si agrega cualquier otra propiedad a la clase User Class de su dominio, por ejemplo: private bool isAdmin. Si su usuario web le envía un parámetro POST con isAdmin = true que se uniría a la clase de dominio del usuario y se mantendría.

Bueno, esta es la forma en que lo haría:

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) 
{ 
    //... 
} 
+0

Pero aún no resuelve el problema original: cómo enlazar una clase heredada. –

3

No puedo ver ninguna solución simple y elegante. Tal vez porque el problema no es cómo vincular datos a clases abstractas en Spring MVC, sino más bien: ¿por qué tener clases abstractas en objetos de formulario en primer lugar? Creo que no deberías.

Un objeto enviado desde el formulario al controlador se denomina "objeto de formulario (respaldo)" por una razón: los atributos del objeto deben reflejar los campos del formulario. Si su formulario tiene campos de nombre de usuario y contraseña, entonces debe tener atributos de nombre de usuario y contraseña en su clase.

Así que credentials debe tener un tipo UserPasswordCredentials. Esto omitirá su error de "intento de instanciación abstracta".Dos soluciones para esto:

  • Recomendado: cambia el tipo de UserAccount.credentials de Credenciales a UserPasswordCredentials. Es decir, ¿qué credenciales podría tener una cuenta de usuario, excepto un UserPasswordCredentials? Además, apuesto a que las cuentas de usuario de su base de datos tienen un nombre de usuario y una contraseña almacenados como credenciales, por lo que también podría tener un tipo UserPasswordCredentials directamente en UserAccount. Finalmente, Spring recomienda usar "objetos comerciales existentes como comandos u objetos de formulario" (see doc), por lo que modificar la cuenta de usuario sería el camino a seguir.
  • No recomendado: mantiene UserCuenta como está, y crea una clase UserAccountForm. Esta clase tendría los mismos atributos que UserAccount, excepto que UserAccountForm.credentials tiene un tipo UserPasswordCredentials. Luego, al enumerar/guardar, una clase (UserAccountService por ejemplo) realiza la conversión. Esta solución implica un poco de duplicación de código, así que solo úsela si tiene una buena razón (entidades heredadas que no puede cambiar, etc.).
Cuestiones relacionadas