2011-10-17 9 views
10

In un previous similar question, ho chiesto informazioni su come serializzare due diversi set di campi utilizzando JacksonJson e Spring.Spring e JacksonJson, serializzando diversi campi con viste

mio caso d'uso è la mappatura tipica Regolatore con @ResponseBody annotazione tornare direttamente un oggetto o raccolte di oggetti, che vengono poi resi con JacksonJson ogni volta che il client aggiunge application/json nell'intestazione Accept particolare.

Ho avuto due risposte, la prima suggerisce di restituire interfacce diverse con una lista getter diversa, la seconda suggerisce di utilizzare Json Views.

Non ho problemi a capire il primo modo, tuttavia, per il secondo, dopo aver letto la documentazione su JacksonJsonViews, non so come implementarlo con Spring.

Per rimanere con l'esempio, vorrei dichiarare tre classi stub, all'interno della classe Visualizzazioni:

// View definitions: 
public class Views { 
    public static class Public { } 
    public static class ExtendedPublic extends PublicView { } 
    public static class Internal extends ExtendedPublicView { } 
} 

Poi ho dichiarare le classi menzionate:

public class PublicView { } 
public class ExtendedPublicView { } 

Perché sulla terra hanno dichiarare classi statiche vuote e classi vuote esterne, non lo so. Capisco che hanno bisogno di una "etichetta", ma i membri statici di Views sarebbero sufficienti. E non è che ExtendedPublic si estenda Public, come sarebbe logico, ma in realtà sono del tutto estranei.

E infine il fagiolo specificherà con annotazione la vista o l'elenco di visite:

//changed other classes to String for simplicity and fixed typo 
//in classname, the values are hardcoded, just for testing 
public class Bean { 
    // Name is public 
    @JsonView(Views.Public.class) 
    String name = "just testing"; 

    // Address semi-public 
    @JsonView(Views.ExtendedPublic.class) 
    String address = "address"; 

    // SSN only for internal usage 
    @JsonView(Views.Internal.class) 
    String ssn = "32342342"; 
} 

Infine nel controller primavera, ho a pensare come cambiare la mappatura originale della mia fagiolo di prova:

@RequestMapping(value = "/bean") 
@ResponseBody 
public final Bean getBean() { 
    return new Bean(); 
} 

dice chiamare:

//or, starting with 1.5, more convenient (ObjectWriter is reusable too) 
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance); 

Così ho un ObjectMapper istanza proveniente dal nulla e un out che non è il tipico servlet PrintWriter out = response.getWriter();, ma è un'istanza di JsonGenerator e che non può essere ottenuto con il nuovo operatore. Quindi non so come modificare il metodo, qui è una prova incompleta:

@RequestMapping(value = "/bean") 
@ResponseBody 
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException { 
    ObjectMapper objectMapper = new ObjectMapper(); 
    JsonGenerator out; //how to create? 
    objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean()); 
    return ??; //what should I return? 
} 

quindi vorrei sapere se qualcuno ha avuto successo con JsonView con la primavera e come lui/lei ha fatto. L'intero concetto sembra interessante, ma la documentazione sembra carente, manca anche il codice di esempio.

Se non è possibile, userò solo le interfacce che si estendono l'un l'altra. Ci scusiamo per la lunga domanda.

+1

Perché stai provando a fare il JSON da solo? HttpMessageConverter che è configurato per utilizzare Jackson dovrebbe convertirlo automaticamente. – chrislovecnm

+0

@chrislovecnm, nel mio semplice esempio, restituisco semplicemente l'annotazione Object o Collection con ResponseBody e Spring fa tutto "dietro le quinte". Ed ecco il problema, dal momento che lo fa "segretamente", non so come passare parametri (nel mio caso Views.Public.class) per cambiarne il comportamento. – stivlo

risposta

4

È necessario collegare manualmente il MappingJacksonHttpMessageConverter. In primavera 3.1 si è in grado di utilizzare i tag XML MVC come la seguente:

<mvc:annotation-driven > 
    <mvc:message-converter> 
     <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" /> 
    </mvc:message-converters> 
</mvc:annotation-driven> 

E 'abbastanza brutto non utilizzare primavera 3.1, ti farà risparmiare circa 20 linee di XML. Il tag di annotazione mvc: ALOT.

Sarà necessario collegare il programma di associazione oggetto al writer di visualizzazione corretto. Di recente ho notato che l'utilizzo di una classe @Configuration può rendere molto più semplice il cablaggio complicato. Utilizzare una classe @Configuration e creare un @Bean con MappingJacksonHttpMessageConverter e collegare il riferimento a quel bean invece del MappingJacksonHttpMessageConverter precedente.

+0

Grazie Chris per la tua risposta. Tuttavia, sono molto lontano dal capire cosa dovrei fare. Sto usando Spring 3.0.6 e sto già usando 'mvc: annotation-driven', ho aggiunto' mvc: message-converter' come suggerito.Tuttavia, ottengo che "Element" mvc: annotation-driven "non deve contenere caratteri o elementi informativi [figli], perché il tipo di contenuto del tipo è vuoto." Quindi si dice "wire the object mapper con il view writer corretto", questo probabilmente significa una chiamata simile a questa: 'objectMapper.viewWriter (Views.Public.class)' e non so dove e come ottenere un riferimento a ObjectMapper. – stivlo

+0

Quindi quello che dici con "Ho notato ..." è una soluzione alternativa? In pratica il bean sarà auto-generato dal tag AOP @Configuration, con un MappingJacksonHttpMessageConverter, e io chiamo la variabile come mi piace che una Spring la trovi? Quindi devo dire qualcosa sui punti di vista, come fare? E come differenziarli? Devo impostare qualcosa in ogni oggetto? – stivlo

+1

@stivlo è necessario utilizzare Spring 3.1 per utilizzare il tag mvc: message-converter. Inoltre non mi è familiare JacksonJsonViews ... – chrislovecnm

2

Sono riuscito a risolvere il problema in questo modo:

  • Crea classe astratta personalizzato per contenere l'oggetto risposta JSON:
public abstract AbstractJson<E>{ 
    @JsonView(Views.Public.class) 
    private E responseObject; 

    public E getResponseObject() { 
     return responseObject; 
    } 
    public void setResponseObject(E responseObject) { 
     this.responseObject = responseObject; 
    } 
} 
  • creare una classe per ogni visibilità (solo per contrassegnare la risposta):
public class PublicJson<E> extends AbstractJson<E> {} 
public class ExtendedPublicJson<E> extends AbstractJson<E> {} 
public class InternalJson<E> extends AbstractJson<E> {} 
  • Cambia la tua dichiarazione di metodo:
@RequestMapping(value = "/bean") 
    @ResponseBody 
    public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException { 
     return new PublicJson(new Bean()); 
    } 
  • Creare costumi MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{ 

    public PublicApiResponseMessageConverter(){ 
     super(); 
     org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper(); 
     objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false); 
     objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class)); 
     this.setObjectMapper(objMapper); 
    } 

    public boolean canWrite(Class<?> clazz, MediaType mediaType) { 
     if(clazz.equals(PublicJson.class)){ 
      return true; 
     } 
     return false; 
    } 

} 

public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{ 

    public ExtendedPublicJsonMessageConverter(){ 
     super(); 
     org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper(); 
     objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false); 
     objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class)); 
     this.setObjectMapper(objMapper); 
    } 

    public boolean canWrite(Class<?> clazz, MediaType mediaType) { 
     if(clazz.equals(ExtendedPublicJson.class)){ 
      return true; 
     } 
     return false; 
    } 

} 

public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{ 

    public InternalJsonMessageConverter(){ 
     super(); 
     org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper(); 
     objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false); 
     objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class)); 
     this.setObjectMapper(objMapper); 
    } 

    public boolean canWrite(Class<?> clazz, MediaType mediaType) { 
     if(clazz.equals(Internal.class)){ 
      return true; 
     } 
     return false; 
    } 

} 
  • Aggiungere il seguente al vostro xml:
<mvc:annotation-driven> 
    <mvc:message-converters> 
     <bean class="PublicJsonMessageConverter"></bean> 
     <bean class="ExtendedPublicJsonMessageConverter"></bean> 
     <bean class="InternalJsonMessageConverter"></bean> 
    </mvc:message-converters> 
</mvc:annotation-driven> 

Questo è tutto! Ho dovuto aggiornare alla primavera 3.1 ma questo è tutto. Io uso responseObject per inviare più informazioni sulla chiamata JSON, ma è possibile ignorare più metodi di MessageConverter per essere completamente trasparenti. Spero che un giorno la primavera includa un'annotazione per questo.

Spero che questo aiuti!

6

Sulla base delle risposte da parte @igbopie e @chrislovecnm, ho messo insieme un'annotazione guidato soluzione:

@Controller 
public class BookService 
{ 
    @RequestMapping("/books") 
    @ResponseView(SummaryView.class) 
    public @ResponseBody List<Book> getBookSummaries() {} 

    @RequestMapping("/books/{bookId}") 
    public @ResponseBody Book getBook(@PathVariable("bookId") Long BookId) {} 
} 

Dove SummaryView viene annotata sul modello Book in questo modo:

@Data 
class Book extends BaseEntity 
{ 
    @JsonView(SummaryView.class) 
    private String title; 
    @JsonView(SummaryView.class) 
    private String author; 
    private String review; 

    public static interface SummaryView extends BaseView {} 
} 

@Data 
public class BaseEntity 
{ 
    @JsonView(BaseView.class) 
    private Long id;  
} 

public interface BaseView {} 

Un custom HandlerMethodReturnValueHandler viene quindi collegato al contesto di Spring MVC per rilevare l'annotazione @ResponseView e applicare la vista Jackson di conseguenza.

Ho fornito il codice completo over on my blog.

+0

Bella soluzione! La soluzione guidata dall'annotazione sembra davvero accurata. – igbopie

+1

Questa è un'ottima soluzione, tuttavia non volevo annotare ogni campo in ogni classe per escludere solo un paio di campi. Così ho esteso il tuo approccio di annotazione con @ResponseMixinFilters che fornisce 2 matrici (entità e classi di filtro) e quindi esegue mapper.getSerializationConfig(). AddMixInAnnotations (entità, filtro) in ViewAndFilterAwareJsonMessageConverter. – Federico

Problemi correlati