2010-08-28 16 views
14

Ich bin durch Spring Dokumentation und Quellcode gegangen und habe immer noch keine Antwort auf meine Frage gefunden.Datenbindung einer abstrakten Klasse im Frühjahr-mvc

Ich habe diese Klassen in meinem Domänenmodell und möchte sie als Backing-Formularobjekte in Spring-MVC verwenden.


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

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

Auf UI Ich habe dynamische Form für die verschiedenen Credential-Typen. Meine POST-Anfrage in der Regel wie folgt aussieht:


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

Nach Ausnahme wird ausgelöst, wenn ich versuche Anfrage an den Server zu übermitteln:


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) 

Mein erster Gedanke war @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 zu verwenden, mit diesem Ansatz ist, dass prepareUserAccountBean Methode aufgerufen werden, bevor andere Methoden (wie listAccounts) auch, die nicht geeignet ist.

Eine robuste Lösung besteht darin, sowohl prepareUserAccountBean als auch saveUserAccount an den separaten Controller zu übertragen. Es klingt nicht richtig: Ich möchte, dass alle benutzerbezogenen Vorgänge in derselben Controller-Klasse liegen.

Eine einfache Lösung? Kann ich irgendwie DataBinder, PropertyEditor oder WebArgumentResolver verwenden?

Vielen Dank !!!!!

+0

Können Sie den vollständigen Code aus Ihren Domain-Klassen veröffentlichen? Ich würde gerne Ihre Konstruktoren sehen. –

+0

Hört sich für mich so an, als wäre das eher ein Mapping/Serialisierungs-/Konvertierungsproblem mit Ihrer Datenbank als mit der Ansicht. Werden die Konten korrekt in den richtigen Objekten deserialisiert, bevor sie an die Ansicht gesendet werden? – DavidA

Antwort

-1

Ich bin mir nicht sicher, aber Sie sollten ViewModel-Klassen auf Ihren Controllern anstelle von Domain-Objekten verwenden. Dann würden Sie innerhalb Ihrer saveAccount-Methode dieses ViewModel validieren und wenn alles gut geht, ordnen Sie es Ihrem Domain Model zu und behalten es bei.

Damit haben Sie einen weiteren Vorteil. Wenn Sie Ihrer Domain UserAccount-Klasse eine andere Eigenschaft hinzufügen, z. B .: private bool isAdmin. Wenn Ihr Webbenutzer Ihnen einen POST-Parameter mit isAdmin = true sendet, der an die Benutzerdomänenklasse gebunden und persistent ist.

Nun, das ist die Art, wie ich tun würde:

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

Aber es löst immer noch nicht das ursprüngliche Problem: wie man eine geerbte Klasse bindet. –

3

ich keine einfache und elegante Lösung sehen. Vielleicht, weil das Problem nicht darin besteht, abstrakte Daten in Spring MVC zu binden, sondern vielmehr: Warum abstrakte Klassen in Formularobjekten überhaupt? Ich denke, du solltest nicht.

Ein Objekt, das vom Formular an den Controller gesendet wird, wird aus einem bestimmten Grund als "Formular (Hintergrund) -Objekt" bezeichnet: Die Objektattribute sollten die Formularfelder widerspiegeln. Wenn Ihr Formular Felder für Benutzername und Passwort enthält, sollten Sie in Ihrer Klasse über Attribute für Benutzername und Passwort verfügen.

So credentials sollte einen UserPasswordCredentials Typ haben. Dies würde Ihren "abstrakten Instanziierungsversuch" -Fehler überspringen.Zwei Lösungen:

  • Empfehlung: die Art des UserAccount.credentials von Credentials zu UserPasswordCredentials ändern. Ich meine, was Credentials könnte ein UserAccount möglicherweise außer einem UserPasswordCredentials haben? Was mehr ist, ich wette, dass Ihre Datenbank userAccounts einen Benutzernamen und ein Passwort als Anmeldeinformationen gespeichert haben, so dass Sie auch einen UserPasswordCredentials-Typ direkt in UserAccount haben könnten. Abschließend empfiehlt Spring, "vorhandene Geschäftsobjekte als Befehls- oder Formularobjekte" zu verwenden (see doc), sodass das Ändern von UserAccount der richtige Weg wäre.
  • Nicht empfohlen: Sie behalten UserAccount unverändert und erstellen eine UserAccountForm-Klasse. Diese Klasse hätte die gleichen Attribute wie UserAccount, außer dass UserAccountForm.credentials den Typ UserPasswordCredentials aufweist. Beim Auflisten/Speichern führt eine Klasse (z. B. UserAccountService) die Konvertierung durch. Diese Lösung erfordert einige Code-Duplizierungen. Verwenden Sie sie daher nur, wenn Sie einen guten Grund haben (ältere Entitäten, die Sie nicht ändern können usw.).
Verwandte Themen