Сериализация веб-сервиса Spring REST для нескольких форматов JSON

У меня есть веб-сервис Spring REST, который заполняет общий объект на основе данных, которые мы имеем в базе данных, цель состоит в том, чтобы пользователи передали в веб-сервис параметр, чтобы указать формат, в котором они хотят выводить данные. На основе их ввод мы будем использовать правильный JSONSerializer, чтобы дать им то, что они хотят.

Я настроил свой веб-сервис следующим образом: в своем файле spring-ws-servlet.xml я настроил нашу компанию ObjectMapper для использования в mvc:message-converter, я также установил его на RestController, чтобы он мог настроить ObjectMapper. зарегистрировать сериализатор. Это выглядит так:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean
                class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="jacksonObjectMapper" />
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="endpoint" class="org.company.Controller">
        <property name="objectMapper" ref="jacksonObjectMapper" />
    </bean>

    <bean id="jacksonObjectMapper" class="org.company.CompanyObjectMapper" />

</beans>

Контроллер выглядит так:

@RestController
public class Controller {

    private ObjectMapper objectMapper;

    @RequestMapping(...)
    public GenericObject getObject(@PathVariables ...) {

        //Get Object from database, just creating an object for example
        GenericObject object = new GenericObject();

        //Based on the user input we will pick out
        //a Serializer that  extends JsonSerializer<GenericObject>
        BaseSerializer serializer = getSerializer();

        //Create a simpleModule and use it to register our serializer
        SimpleModule module = new SimpleModule();
        module.addSerializer(GenericObject.class, serializer);

        //get module and register the serializer
        ObjectMapper mapper = getObjectMapper();            
        mapper.registerModule(module);

        return object;
    }

    public ObjectMapper getObjectMapper() {
        return objectMapper;
    }

    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
}

Проблема в том, что когда я публикую свое веб-приложение, первый запрос работает правильно, если я укажу format=format1, я получу вывод в формате format1. Однако после этого я могу получить только format1. Я могу указать format=format2, но все равно получить вывод в format1. Я полагаю, что проблема заключается в том, что ObjectMapper все еще имеет зарегистрированный модуль с первого запроса. Я читал, что могу избежать этой проблемы, каждый раз создавая новый ObjectMapper, но я не уверен, как установить его для использования Spring при выводе JSON.

Может ли кто-нибудь помочь мне найти решение для создания нового ObjectMapper при каждом запуске кода и установки этого ObjectMapper в службу отдыха Spring, или помочь мне выяснить, как я могу "отменить регистрацию" любых модулей, зарегистрированных в объекте маппер перед установкой последнего желаемого сериализатора?

2 ответа

Решение

Идея может состоять в том, чтобы создать и настроить все необходимые средства отображения во время запуска в качестве пружинных компонентов.

Затем создайте сопоставитель объектов по умолчанию, который будет работать в качестве диспетчера для других сопоставителей объектов (или в качестве резервного), и он может знать о текущем HTTP-запросе. Вы можете зарегистрировать все преобразователи в этом объектном преобразователе, зарегистрируйте этот преобразователь, чтобы он использовал его по умолчанию весной.

Что-то вроде этого может быть:

 public class RequestAwareObjectMapper extends ObjectMapper{

    private Map<String, ObjectMapper > mappers = new HashMap<>();

    @Override
    public String writeValueAsString(Object value) throws JsonProcessingException{
        HttpServletRequest req = null;//get request from spring context, if any, this is a managed spring bean it wont be a prorblem
        String param = null; // read the param from the query
        ObjectMapper mapper = mappers.get(param);
        if(mapper == null){
            mapper = this;
        }
        return mapper.writeValueAsString(value);
    }

    public void registerMapper(String key, ObjectMapper mapper){...}
} 

таким образом, вы не собираетесь загрязнять ваш контроллер ссылками на объектный маппер, и вы можете продолжать использовать @ResponseBody (благодаря @RestController)..

Я уверен, что есть более чистый способ достичь того же результата, интегрируя аналогичное решение в поток пружины, и сейчас не могу найти что-то лучшее.

Создайте свой класс customObjectMapper и автоматически подключите его к контроллеру с помощью аннотации @Autowire. Затем вы можете создавать разные методы для создания разных форматированных объектов.

Вы также можете отправить сериализатор в качестве параметров.

public class CustomObjectMapper extends ObjectMapper {
    public CustomObjectMapper() {
        super();
        super.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        super.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        ..... etc.....
        super.setDateFormat(df);
    }
    public byte[] generateJsonFormat1(Object value, BaseSerializer serializer) throws IOException, JsonGenerationException, JsonMappingException {
        Hibernate4Module hm = new Hibernate4Module();
        hm.configure(Hibernate4Module.Feature.USE_TRANSIENT_ANNOTATION, false);
        hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
        .....
        .....
        hm.addSerializer(Object.class, serializer);
        return super.registerModule(hm).writeValueAsBytes(value);
    }

    public byte[] generateJsonFormat2(Object value, BaseSerializer serialiser) throws IOException, JsonGe nerationException, JsonMappingException {
        SimpleModule sm = new SimpleModule();
        sm.addSerializer(Object.class, serialiser);
        return super.registerModule(hm).writeValueAsBytes(value);
    }
}

Выше код является фрагментом из моего собственного приложения. Я надеюсь, что это дает идею.

Другие вопросы по тегам