2010-08-28 8 views
14

Sono passato attraverso la documentazione Spring e il codice sorgente e non ho ancora trovato risposta alla mia domanda.Associazione dati di una classe astratta in spring-mvc

Ho queste classi nel mio modello di dominio e voglio usarle come oggetti del modulo di supporto 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; 
    .... 
} 

mio regolatore:


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

Sulla UI devo forma dinamica per i diversi tipi di credenziali. La mia richiesta POST di solito si presenta come:


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

seguito viene generata un'eccezione se cerco di inviare richiesta al 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) 

Il mio primo pensiero è stato quello di utilizzare @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 questo approccio è che il metodo prepareUserAccountBean viene chiamato prima di qualsiasi altro metodo (come listAccounts) che non è appropriato.

Una soluzione efficace è quella di spostare sia prepareUserAccountBean sia saveUserAccount sul controller separato. Non suona bene: voglio che tutte le operazioni relative all'utente risiedano nella stessa classe di controller.

Qualsiasi soluzione semplice? Posso utilizzare in qualche modo DataBinder, PropertyEditor o WebArgumentResolver?

Grazie !!!!!

+0

Puoi pubblicare il codice completo dalle tue classi di dominio? Mi piacerebbe vedere i tuoi costruttori. –

+0

Mi sembra che questo sia più un problema di mappatura/serializzazione/conversione con il tuo database piuttosto che con la vista. Gli account vengono deserializzati correttamente negli oggetti corretti prima di essere inviati alla vista? – DavidA

risposta

-1

Non sono sicuro, ma dovresti utilizzare le classi ViewModel sui controller invece di Oggetti dominio. Quindi, all'interno del tuo metodo saveAccount, convalidi questo ViewModel e, se tutto va bene, lo associ al tuo modello di dominio e lo mantenga.

Così facendo, hai un altro vantaggio. Se aggiungi altre proprietà alla classe UserAccount del tuo dominio, ad esempio: bool privato èAdmin. Se il tuo utente web ti invia un parametro POST con isAdmin = true che sarebbe associato alla classe dominio utente e mantenuto.

Bene, questo è il modo in cui mi piacerebbe fare:

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

Ma non risolve ancora il problema originale: come associare una classe ereditata. –

3

non riesco a vedere alcuna soluzione semplice ed elegante. Forse perché il problema non è come associare i dati a classi astratte in Spring MVC, ma piuttosto: perché avere classi astratte in oggetti form in primo luogo? Penso che non dovresti.

Un oggetto inviato dal modulo al controller viene chiamato un "oggetto di tipo modulo (backing)" per un motivo: gli attributi dell'oggetto devono riflettere i campi del modulo. Se il tuo modulo ha campi username e password, allora dovresti avere gli attributi username e password nella tua classe.

Quindi credentials dovrebbe avere un tipo UserPasswordCredentials. Questo salta l'errore "tentativo di istanziazione astratta".Due soluzioni per questo:

  • consigliato: si cambia il tipo di UserAccount.credentials da credenziali a UserPasswordCredentials. Intendo, quali credenziali potrebbe avere un account utente, ad eccezione di UserPasswordCredentials? Inoltre, scommetto che i tuoi account utente di database hanno un nome utente e una password memorizzati come credenziali, quindi potresti anche avere un tipo UserPasswordCredentials direttamente in UserAccount. Infine, Spring consiglia di utilizzare "oggetti di business esistenti come oggetti di comando o di modulo" (see doc), quindi la modifica di Account utente sarebbe la strada da percorrere.
  • Non consigliato: si mantiene Account utente come è e si crea una classe UserAccountForm. Questa classe avrà gli stessi attributi di Account utente, ad eccezione del fatto che UserAccountForm.credentials ha un tipo UserPasswordCredentials. Quindi quando si elenca/si salva, una classe (UserAccountService ad esempio) esegue la conversione. Questa soluzione implica una duplicazione del codice, quindi usala solo se hai una buona ragione (entità legacy che non puoi cambiare, ecc.).