2015-11-11 14 views
6

Ich habe folgende Controller:Frühling generic REST-Controller: Parsen Anfrage Körper

@RestController 
@RequestMapping(value = "/{entity}", produces = MediaType.APPLICATION_JSON_VALUE) 
public class CrudController<T extends SomeSuperEntity> { 

    @RequestMapping(method = GET) 
    public Iterable<T> findAll(@PathVariable String entity) { 
    } 

    @RequestMapping(value = "{id}", method = GET) 
    public T findOne(@PathVariable String entity, @PathVariable String id) { 
    } 

    @RequestMapping(method = POST) 
    public void save(@PathVariable String entity, @RequestBody T body) { 
    } 
} 

SomeSuperEntity Klasse wie folgt aussieht:

public abstract class SomeSuperEntity extends AbstractEntity { 
    // some logic 
} 

Und AbstractEntity seine abstrakte Klasse mit einem Feld:

public abstract class AbstractEntity implements Comparable<AbstractEntity>, Serializable { 

    private Timestamp firstField; 
    private String secondField; 

    public Timestamp getFirstField() { 
     return firstField; 
    } 

    public void setFirstField(Timestamp firstField) { 
     this.firstField = firstField; 
    } 

    public String getSecondField() { 
     return secondField; 
    } 

    public void setSecondField(String secondField) { 
     this.secondField = secondField; 
    } 
} 

Alle Unterklassen von SomeSuperEntity - einfache JavaBeans. Im Fall mit findAll() und findOne(id) Methoden - alles funktioniert gut. Ich erstelle Entität in Service-Schicht und es wird an den Client als JSON mit allen Feldern zurückgegeben, die in der Unterklasse und in AbstractEntity deklariert sind.

Aber wenn ich versuchte Anfrage Körper in save(entity, body) zu bekommen, ich habe Fehler folgende:

Could not read document: Can not construct instance of SomeSuperEntity, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

Wenn ich von SomeSuperEntity abstrakt entfernen, alles funktioniert, aber ich wünsche Körper i nur jene Felder bekam, die deklariert in AbstractEntity.

Und hier ist meine Frage, gibt es eine Problemumgehung für solche Probleme in meinem Fall? Wenn nicht, was würde sich durch die beste Lösung hier ohne Struktur ändern (subcontroller für jede Entität zu machen ist keine Option)? Ist retrieve body als einfacher Text eine gute Idee? Oder wäre es besser, dafür Map zu verwenden?

Ich verwende Spring v4.2.1 und Jackson 2.6.3 als Konverter.

Es gibt ein paar Informationen über generische Controller, aber ich konnte nichts finden, was meinen Fall abdeckt. Also, bitte, navigieren Sie im Falle einer doppelten Frage.

Vielen Dank im Voraus.

UPD: Zur Zeit seiner Arbeits wie folgt: füge ich zusätzliche Kontrolle in meinem MessageConverter hinzufügen und definieren @RequestBody als String

Dann auf Dienstschicht definieren i, welche Entität empfangen (im Klar json) und wandeln es:

final EntityMetaData entityMetadata = getEntityMetadataByName(alias); 
final T parsedEntity = getGlobalGson().fromJson(entity, entityMetadata.getEntityType()); 

Wo EntityMetaDataenum mit definierten Beziehungen zw Ein Entitätsalias und eine Klasse. Alias ​​kommt als @PathVariable.

+0

Spring erstellt ein einzelnes Objekt für den Typ "CrudController". Es gibt keine zusätzlichen Informationen, z. der Subtyp "SomeSuperEntity". Wo erwartest du Spring (und dann Jackson)? –

+1

Im Grunde tun Sie nicht, was Sie mit 'CrudController' gemacht haben. Erstellen Sie eine dedizierte '@ Controller'-Klasse für jeden gewünschten Typ mit entsprechenden Zuordnungen. –

+0

Das ist interessant. Warum? Was ist falsch bei der Bereitstellung eines allgemeinen "/ Tiere" - oder "/ Tiere/1" -Endpunkts, um alle Tiere oder Tiere mit ID = 1 zu erhalten, unabhängig davon, ob es sich um einen Hund oder eine Katze handelt? Sie schlagen vor, die Leute im Voraus wissen zu lassen, ob sie Hunde oder Katzen anfordern, oder? Wie '/ Tiere/Hunde/1'. Auch wenn Sie alle Tiere bekommen wollen, müssen Sie über alle Tierarten iterieren, indem Sie '/ animals/dogs','/animals/cats' usw. sammeln. Fragen Sie mich einfach, während ich versuche, ähnliche Fälle zu lösen der beste Weg. –

Antwort

0

Was Frühling wirklich sieht, ist:

public class CrudController { 

    @RequestMapping(method = GET) 
    public Iterable<Object> findAll(@PathVariable String entity) { 
    } 

    @RequestMapping(value = "{id}", method = GET) 
    public Object findOne(@PathVariable String entity, @PathVariable String id)  { 
    } 

    @RequestMapping(method = POST) 
    public void save(@PathVariable String entity, @RequestBody Object body) { 
    } 
} 

Für zurückgegebenen Objekte nicht als Jackson egal sowieso Ausgang richtige JSON erzeugen, aber es sieht aus wie Spring nicht eingehendes Objekt gleiche Art und Weise umgehen kann.

Sie könnten versuchen, Generika mit nur SomeSuperEntity und werfen Sie einen Blick auf Spring @RequestBody containing a list of different types (but same interface)

0

Für eine lange Zeit Forschung, fand ich, dass in Jackson arbeitet ersetzt:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") 
public interface ApiRequest { 

} 

und verwenden

REQUEST extends ApiRequest 

mit diesem, ändern Sie nicht MessageConverter. Daher werden in Ihrer json-Anfrage immer noch zusätzliche Klasseninformationen benötigt. Zum Beispiel könnten Sie tun:

public abstract class ApiPost<REQUEST extends ApiRequest > { 

    abstract protected Response post(REQUEST request) throws ErrorException; 

    @ResponseBody 
    @RequestMapping(method = RequestMethod.POST) 
    public Response post(
      @RequestBody REQUEST request 
    ) throws IOException { 

     return this.post(request); 
    } 

} 

und dann für Controller

public class ExistApi { 

     public final static String URL = "/user/exist"; 

     @Getter 
     @Setter 
     public static class Request implements ApiRequest{ 

      private String username; 
     } 

    } 
@Controller 
@RequestMapping(URL) 
public class ExistApiController extends ApiPost<Request> { 

    @Override 
    protected Response post(Request request) implements ApiRequest { 
     //do something 
     // and return response 
    } 

} 

Und dann Anfrage als { "username" senden: xxxx, "@class": "Paket .... .request“ }

Referenz eine https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

Aber für mich ist die beste Lösung Feder nicht zu konvertieren verwenden, um mich Ssage, und verließ die abstrakte Klasse es tun.

public abstract class ApiPost<REQUEST> { 

    @Autowired 
    private ObjectMapper mapper; 

    protected Class<REQUEST> getClazz() { 
     return (Class<REQUEST>) GenericTypeResolver 
       .resolveTypeArgument(getClass(), ApiPost.class); 
    } 

    abstract protected Response post(REQUEST request) throws ErrorException; 

    @ResponseBody 
    @RequestMapping(method = RequestMethod.POST) 
    public Response post(
      @RequestBody REQUEST request 
    ) throws IOException { 

     //resolve spring generic problem 
     REQUEST req = mapper.convertValue(request, getClazz()); 
     return this.post(request); 
    } 

} 

mit diesem, wir benötigen nicht die ApiRequest Schnittstelle und @class in Anfrage json, entkoppeln die Front und Back-End.

Verzeihen Sie mein armes Englisch.

+0

Wo verwenden Sie T erweitert ApiRequest? Kannst du ein Beispiel geben? – dukethrash

+0

Ich habe die Antwort bearbeitet und ein Beispiel gegeben, – ahll

Verwandte Themen