2015-11-11 5 views
6

Ho seguente controller:Primavera regolatore REST generico: analisi corpo della richiesta

@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 classe assomiglia:

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

E AbstractEntity sua classe astratta con qualche campo:

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

Tutte le sottoclassi di SomeSuperEntity - JavaBeans semplici. In caso di metodi findAll() e findOne(id), tutto funziona correttamente. Creo entità nel livello di servizio e ritorna al client come JSON con tutti i campi dichiarati in sottoclasse e in AbstractEntity.

Ma quando ho cercato di ottenere richiesta corpo in save(entity, body), ho ottenuto l'errore seguente:

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

se tolgo astratto da SomeSuperEntity, tutto funziona, ma chiedo corpo ho ottenuto solo quei campi, che ha dichiarato in AbstractEntity.

E qui è la mia domanda, c'è qualche soluzione per tali problemi nel mio caso? In caso contrario, quale sarebbe la soluzione migliore qui senza modifiche di struttura (la creazione di subcontloller per ciascuna entità non è un'opzione)? Recuperare il corpo come testo normale sarebbe una buona idea? O sarebbe meglio usare Map per questo?

Sto utilizzando Spring v4.2.1 e Jackson 2.6.3 come convertitore.

C'è un po 'di informazioni sui controller generici, ma non sono riuscito a trovare nulla che riguardasse il mio caso. Quindi, per favore, naviga in caso di domande doppie.

Grazie in anticipo.

UPD: Attualmente il suo funzionamento come segue: aggiungo aggiungere ulteriore controllo nel mio MessageConverter e definire @RequestBody come String

Poi, il livello di servizio, definisco quanto entità ha ricevuto (in JSON pianura) e convertirlo:

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

Dove EntityMetaData è enum con relazioni definite betw un alias entità e classe. Alias ​​viene fornito come @PathVariable.

+0

Spring creerà un singolo oggetto per il tipo 'CrudController'. Non ci sono ulteriori informazioni sul tipo, es. il sottotipo 'SomeSuperEntity'. Dove ti aspetti che Spring (e poi Jackson) ottenga? –

+1

Fondamentalmente, non fare quello che hai fatto con 'CrudController'. Crea una lezione '@ Controller' dedicata per ogni tipo che ti interessa, con mappature appropriate. –

+0

È interessante. Perché? Cosa c'è di sbagliato nel fornire un endpoint generale '/ animals' o'/animals/1' per ottenere tutti gli animali o qualsiasi animale con 'ID = 1' indipendentemente dal fatto che si tratti di cane o gatto? Suggerisci di costringere le persone a sapere in anticipo se stanno richiedendo cani o gatti, giusto? Mi piace '/ animals/dogs/1'. Inoltre, se vuoi ottenere tutti gli animali devi iterare su tutti i sottotipi di animali che raccolgono insieme '/ animals/dogs','/animals/cats' ecc. Sto solo chiedendo come sto cercando di risolvere il caso di utilizzo simile io stesso e faticando a trovare il modo migliore. –

risposta

0

Cosa vede davvero la primavera è:

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) { 
    } 
} 

Per gli oggetti restituiti non importa come Jackson genererà uscita corretta JSON in ogni caso ma sembra primavera non può gestire in entrata oggetto stesso modo.

Si potrebbe provare a sostituire i farmaci generici con un solo SomeSuperEntity e dare un'occhiata a Spring @RequestBody containing a list of different types (but same interface)

0

Per una lunga ricerca volta, ho trovato che funziona a Jackson:

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

} 

e utilizzare

REQUEST extends ApiRequest 

con questo, non modificare MessageConverter. Quindi richiederà ancora informazioni di classe aggiuntive nella tua richiesta JSON. Ad esempio, si potrebbe fare:

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

} 

e poi per il 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 
    } 

} 

E poi invia una richiesta come { "nome utente": xxxx, "@class": "pacchetto .... .Request" }

riferimento un https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

Ma per me, la soluzione migliore non è usare la primavera per convertire me ssage, e ha lasciato la classe astratta farlo.

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

} 

con questo, noi non richiedono l'interfaccia ApiRequest e @class a richiesta JSON, disaccoppiare la parte anteriore e back-end.

Scusa il mio povero inglese.

+0

Dove usi T estende ApiRequest? Puoi fare un esempio? – dukethrash

+0

Ho modificato la risposta e ho inserito un esempio – ahll