2011-10-17 13 views
10

W previous similar question, zapytałem o, jak serializować dwa różne zestawy pól przy użyciu JacksonJson i Spring.Wiosna i JacksonJson, szeregowanie różnych pól z widokami

Moje przypadek użycia jest typowym mapowanie kontrolerów z @ResponseBody adnotacja powrocie bezpośrednio konkretnego obiektu lub kolekcji obiektów, które następnie są renderowane z JacksonJson gdy klient dodaje application/json w nagłówku Accept.

Miałem dwie odpowiedzi, pierwsza sugeruje zwrócić różne interfejsy z inną listą pobierającą, druga sugeruje użycie widoków Json.

Nie mam problemów, aby zrozumieć pierwszy sposób, jednak na drugi, po przeczytaniu dokumentacji na JacksonJsonViews, nie wiem, jak wdrożyć go ze Spring.

Aby pozostać na przykładzie, chciałbym zadeklarować trzy klasy pośredniczące, wewnątrz klasy Wyświetleń:

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

Wtedy mam zadeklarować klas podano:

public class PublicView { } 
public class ExtendedPublicView { } 

Dlaczego oni na ziemi zadeklaruj puste klasy statyczne i zewnętrzne puste klasy, nie wiem. Rozumiem, że potrzebują "etykiety", ale wystarczy statycznych członków Widoku. I nie jest tak, że ExtendedPublic rozciąga się na Public, ponieważ byłoby to logiczne, ale w rzeczywistości są zupełnie niezwiązane.

I wreszcie fasola będzie podać z dopiskiem pogląd lub listę widoków:

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

Wreszcie w kontrolerze Wiosna, mam myśleć jak zmienić oryginalne odwzorowanie mojego testu fasoli:

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

mówi zadzwonić:

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

Więc mam ObjectMapper instancja wychodząca znikąd i out, która nie jest serwletem typowym PrintWriter out = response.getWriter();, ale jest instancją JsonGenerator i której nie można uzyskać za pomocą nowego operatora. Więc nie wiem, jak zmodyfikować metodę, tutaj jest niepełny try:

@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? 
} 

więc chciałbym wiedzieć, czy ktoś miał powodzenia przy użyciu JsonView z wiosną i jak on/ona. Cała koncepcja wydaje się interesująca, ale brakuje dokumentacji i brakuje przykładowego kodu.

Jeśli nie jest to możliwe, użyję tylko interfejsów rozszerzających się nawzajem. Przepraszam za długie pytanie.

+1

Dlaczego próbujesz samodzielnie wykonać JSON? HttpMessageConverter, które są skonfigurowane do używania Jacksona, powinien przekonwertować go automatycznie. – chrislovecnm

+0

@chrislovecnm, w moim prostym przykładzie, po prostu zwracam obiekt lub kolekcję z adnotacją ResponseBody, a Spring robi wszystko "za kulisami". I tu jest problem, ponieważ robi to "potajemnie", nie wiem jak przekazać mu parametry (w moim przypadku Views.Public.class), aby zmienić jego zachowanie. – stivlo

Odpowiedz

4

Musisz ręcznie połączyć w MappingJacksonHttpMessageConverter. Wiosną 3.1 jesteś w stanie korzystać z MVC znaczników XML jak poniżej:

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

Jest dość brzydkie, aby nie używać wiosna 3.1, będzie to zaoszczędzić około 20 wierszy xml. Tag mvc: adnotacja ma wartość ALOT.

Konieczne będzie podłączenie do programu odwzorowującego obiekty za pomocą właściwego programu do zapisu widoku. Ostatnio zauważyłem, że używanie klasy @Configuration może znacznie ułatwić takie skomplikowane okablowanie. Użyj klasy @Configuration i utwórz element @Bean za pomocą MappingJacksonHttpMessageConverter i połącz odwołanie do tego komponentu bean zamiast powyższego MappingJacksonHttpMessageConverter.

+0

Dziękuję Chris za odpowiedź. Jednak daleko mi do zrozumienia, co powinienem zrobić. Używam Spring 3.0.6 i już używam 'mvc: adnotation-driven', dodałem' mvc: message-converter' zgodnie z sugestią.Jednak otrzymuję "Element" mvc: "sterowany adnotacją" nie może zawierać elementu informacyjnego o znaku ani elemencie [dzieci], ponieważ typ zawartości typu jest pusty. " Następnie mówisz "połącz program odwzorowujący obiekty z właściwym pisarzem widoku", prawdopodobnie oznacza to wywołanie podobne do tego: 'objectMapper.viewWriter (Views.Public.class)' i nie wiem gdzie i jak uzyskać odniesienie do objectMapper. – stivlo

+0

To, co mówisz z "Zauważyłem ..." czy jest to alternatywne rozwiązanie? W praktyce komponent bean będzie autowyredowany przez znacznik AOP @Configuration, z MappingJacksonHttpMessageConverter, a ja nazwę zmienną, ponieważ podoba mi się, że Spring ją znajdzie? Potem muszę powiedzieć coś o poglądach, jak to zrobić? I jak je rozróżniać? Muszę ustawić coś w każdym obiekcie? – stivlo

+1

@stivlo musisz użyć Spring 3.1, aby użyć tagu mvc: message-converter. Również nie jestem znany JacksonJsonViews ... – chrislovecnm

2

mam zarządzać, aby rozwiązać problem w ten sposób:

  • tworzyć niestandardowe klasy abstrakcyjnej zawierać obiekt odpowiedzi 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; 
    } 
} 
  • utworzyć klasę dla każdego widoczności (tylko w celu oznaczenia odpowiedzi):
public class PublicJson<E> extends AbstractJson<E> {} 
public class ExtendedPublicJson<E> extends AbstractJson<E> {} 
public class InternalJson<E> extends AbstractJson<E> {} 
  • Zmień swoją deklarację metoda:
@RequestMapping(value = "/bean") 
    @ResponseBody 
    public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException { 
     return new PublicJson(new Bean()); 
    } 
  • Tworzenie zwyczaje 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; 
    } 

} 
  • dodać następujące do 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> 

To wszystko! Musiałem zaktualizować wiosnę 3.1, ale to wszystko. Używam responseObject do wysyłania więcej informacji na temat wywołania json, ale można nadpisać więcej metod MessageConverter, aby były całkowicie przezroczyste. Mam nadzieję, że pewnego dnia wiosną dołączę do tego adnotację.

Mam nadzieję, że to pomoże!

6

Na podstawie odpowiedzi przez @igbopie i @chrislovecnm, ja już ułożyła adnotacji napędzane rozwiązanie:

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

Gdzie SummaryView się uwagami na modelu Book tak:

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

Niestandardowy HandlerMethodReturnValueHandler jest następnie podłączony do kontekstu Spring MVC, aby wykryć adnotację @ResponseView i odpowiednio zastosować widok Jackson.

Dostarczyłem pełny kod over on my blog.

+0

Ładne rozwiązanie! Rozwiązanie oparte na adnotacjach wygląda naprawdę schludnie. – igbopie

+1

To świetne rozwiązanie, ale nie chciałem dodawać adnotacji do każdego pola w każdej klasie, aby wykluczyć tylko kilka pól. Rozszerzyłem więc twoje podejście do adnotacji o @ResponseMixinFilters, które udostępnia 2 tablice (jednostki i klasy filtrów), a następnie wykonaj mapper.getSerializationConfig(). AddMixInAnnotations (entity, filter) w ViewAndFilterAwareJsonMessageConverter. – Federico

Powiązane problemy