Spring и JacksonJson, сериализующие разные поля с видами - PullRequest
10 голосов
/ 17 октября 2011

В предыдущем аналогичном вопросе я спросил о том, как сериализовать два разных набора полей, используя JacksonJson и Spring.

Мой пример использования - это типичное отображение контроллера с @ResponseBodyаннотация, возвращающая непосредственно определенный объект или наборы объектов, которые затем визуализируются с помощью JacksonJson всякий раз, когда клиент добавляет application/json в заголовок Accept.

У меня было два ответа, первый предлагает возвращать разные интерфейсыс другим списком геттеров второй предлагает использовать Json Views.

У меня нет проблем с пониманием первого способа, однако, для второго, после прочтения документации по JacksonJsonViews, я не знаю, как реализовать это с помощью Spring.

Чтобы остаться в этом примере, я бы объявил три класса-заглушки внутри класса Views:

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

Тогда яЯ должен объявить упомянутые классы:

public class PublicView { }   
public class ExtendedPublicView { }

Почему они объявляют пустые статические классы и внешние пустые классы, я не знаю.Я понимаю, что им нужен "ярлык", но тогда статических членов Views будет достаточно.И дело не в том, что ExtendedPublic расширяет Public, как это было бы логично, но на самом деле они совершенно не связаны.

И, наконец, бин будет указывать с аннотацией представление или список представлений:

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

Наконец, в Spring Controller я должен подумать, как изменить исходное отображение моего тестового компонента:

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

Он говорит, что звонит:

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

Итак, у меня есть экземпляр ObjectMapper из ниоткуда и out, который не является типичным сервлетом PrintWriter out = response.getWriter();, но является экземпляром JsonGenerator, и его нельзя получить с помощью оператора new.Так что я не знаю, как изменить метод, вот неполная попытка:

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

Поэтому я хотел бы знать, имел ли кто-нибудь успех при использовании JsonView с Spring и как он / она это сделал.Вся концепция кажется интересной, но документации, похоже, не хватает, а также отсутствует пример кода.

Если это невозможно, я просто буду использовать интерфейсы, расширяющие друг друга.Извините за длинный вопрос.

Ответы [ 3 ]

6 голосов
/ 05 ноября 2012

Основываясь на ответах @igbopie и @chrislovecnm, я собрал решение, основанное на аннотациях:

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

Где SummaryView помечено на модели Book следующим образом:

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

Затем пользовательский HandlerMethodReturnValueHandler подключается к контексту Spring MVC для обнаружения аннотации @ResponseView и соответственно применяет представление Джексона.

Я добавил полный код в своем блоге .

4 голосов
/ 18 октября 2011

Вам необходимо вручную подключить MaverJacksonHttpMessageConverter. Весной 3.1 вы можете использовать теги mvc xml, как показано ниже:

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

Довольно уродливо не использовать Spring 3.1, это сэкономит вам около 20 строк XML. Тег аннотации mvc: ALOT.

Вам понадобится подключить объектный картограф с правильным модулем записи представления. Недавно я заметил, что использование класса @Configuration может значительно упростить сложную проводку. Используйте класс @Configuration и создайте @Bean с вашим MappingJacksonHttpMessageConverter и подключите ссылку на этот компонент вместо MappingJacksonHttpMessageConverter выше.

2 голосов
/ 21 февраля 2012

Мне удалось решить проблему следующим образом:

  • Создать собственный абстрактный класс, содержащий объект ответа 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;
    }
}
  • Создатькласс для каждой видимости (просто чтобы отметить ответ):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
  • Изменить объявление метода:
    @RequestMapping(value = "/bean")
    @ResponseBody
    public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
         return new PublicJson(new Bean());
    }
  • Создать таможню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;
     }

}
  • Добавьте в свой 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>

Вот и все!Пришлось обновить до весны 3.1 но это все.Я использую responseObject для отправки дополнительной информации о вызове json, но вы можете переопределить больше методов MessageConverter, чтобы они были полностью прозрачными.Я надеюсь, что когда-нибудь весной добавят аннотацию для этого.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...