Spring и JacksonJson, сериализуя различные поля с представлениями
В предыдущем аналогичном вопросе я спросил о том, как сериализовать два разных набора полей, используя 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
, поскольку это было бы логично, но они на самом деле абсолютно не связаны.
И, наконец, bean будет указывать с аннотацией представление или список представлений:
//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 я должен подумать, как изменить исходное сопоставление моего теста bean:
@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
и не может быть получен с новым оператором. Поэтому я не знаю, как изменить метод, вот неполная попытка:
@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 и как он это сделал. Вся концепция кажется интересной, но документация кажется недостаточной, также отсутствует код примера.
Если это невозможно, я просто буду использовать интерфейсы, расширяющие друг друга. Извините за длинный вопрос.
Ответы
Ответ 1
Основываясь на ответах @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
и соответствующим образом применяет представление Джексона.
Я предоставил полный код
Ответ 2
Вам нужно вручную подключиться к MappingJacksonHttpMessageConverter. В spring 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: annotation делает ALOT.
Вам нужно будет подключиться к устройству отображения объектов с помощью правильной записи. Недавно я заметил, что использование класса @Configuration может сделать сложную проводку подобным образом намного проще. Используйте класс @Configuration и создайте @ Bean с помощью MappingJacksonHttpMessageConverter и привяжите ссылку к этому bean вместо MappingJacksonHttpMessageConverter выше.
Ответ 3
Мне удалось решить проблему следующим образом:
- Создайте собственный абстрактный класс, чтобы содержать объект ответа 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>
Что это! Мне пришлось обновить до spring 3.1, но все. Я использую responseObject для отправки дополнительной информации о вызове json, но вы можете переопределить больше методов MessageConverter, чтобы они были полностью прозрачными. Надеюсь, что когда-нибудь spring включит аннотацию для этого.
Надеюсь, это поможет!