как применять преобразователи сообщений spring в зависимости от условия?

У меня есть контроллер, ответом которого является значение json camelCase. Теперь мы переписываем код с новой версией, и требуемый ответ находится в змее_кейсе.

Я добавил конвертер сообщений и изменил преобразователь объектов в setsetPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

public class ResponseJSONConverter extends MappingJackson2HttpMessageConverter {

@Autowired
public ResponseJSONConverter(ObjectMapper objectMapper) {
    setObjectMapper(objectMapper);
  }
}

Я зарегистрировал этот преобразователь с помощью spring, и он работает, как и ожидалось. Теперь я хочу, чтобы мои старые конечные точки возвращались в camelCase для обратной совместимости с моими потребителями и новыми конечными точками со змеей_case.

Я попытался использовать еще один преобразователь сообщений с простым средством сопоставления объектов без установки свойства camelCase в Snake case и регистрации с помощью spring. Применяется только один преобразователь сообщений в соответствии с порядком, объявленным в конфигурации spring.

Есть ли способ достичь этого? Загрузка конвертера сообщений на основе условия ?

ИЗМЕНИТЬ

Добавлен мой файл конфигурации spring

 <beans xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<bean id="moneySerializer" class="api.serialize.MoneySerializer"/>
    <bean id="moneyDeserializer" class="api.serialize.MoneyDeserializer"/>
    <bean id="serializationModule" class="api.serialize.SerializationModule">
        <constructor-arg index="0" ref="moneySerializer"/>
        <constructor-arg index="1" ref="moneyDeserializer"/>
    </bean>

    <bean id="customObjectMapper" class="api.serialize.CustomObjectMapper" primary="true">
        <constructor-arg ref="serializationModule"/>
    </bean>
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="api.serialize.ResponseJSONConverterCamelCaseToSnakeCase" >
                <constructor-arg ref="customObjectMapper"/>
            </bean>
            <bean class="api.serialize.ResponseJSONConverter">
                <constructor-arg ref="objectMapper"/>
            </bean>
        </mvc:message-converters>

    </mvc:annotation-driven>

    <bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>

</beans>

ИЗМЕНИТЬ 2.0

мой servlet.xml

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true">
        <bean class="com.tgt.promotions.api.serialize.ServiceJSONConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

Конвертер пользовательских сообщений

    public class ServiceJSONConverter extends MappingJackson2HttpMessageConverter {

    @Autowired
    public ServiceJSONConverter(SnakeCaseObjectMapper snakeCaseObjectMapper) {
        setObjectMapper(snakeCaseObjectMapper);
    }
}

Пользовательский сопоставитель объектов

@Component
public class SnakeCaseObjectMapper extends ObjectMapper {
    @Autowired
    public SnakeCaseObjectMapper(PropertyNamingStrategy propertyNamingStrategy) {
        setSerializationInclusion(JsonInclude.Include.NON_NULL);
        setPropertyNamingStrategy(propertyNamingStrategy);
    }
}

Стратегия именования пользовательских свойств

@Component
public class CustomPropertyNamingStrategy extends PropertyNamingStrategy {

    @Autowired
    private HttpServletRequest request;

    private final PropertyNamingStrategy legacyStrategy  = PropertyNamingStrategy.LOWER_CASE;
    private final PropertyNamingStrategy defaultStrategy  = PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES;


    @Override
    public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) {
        return getStrategy().nameForConstructorParameter(config, ctorParam, defaultName);
    }

    @Override
    public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
        return getStrategy().nameForField(config, field, defaultName);
    }

    @Override
    public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
        return getStrategy().nameForGetterMethod(config, method, defaultName);
    }

    @Override
    public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
        return getStrategy().nameForSetterMethod(config, method, defaultName);
    }

    private PropertyNamingStrategy getStrategy() {
        if (isLegacyEndpoint(request)) {
            return legacyStrategy;
        } else {
            return defaultStrategy;
        }
    }

    private boolean isLegacyEndpoint(HttpServletRequest request) {
        return request != null && request.getRequestURL() != null && !request.getRequestURL().toString().contains("/v3");
    }
}

person Pramod    schedule 14.02.2017    source источник
comment
не могли бы вы добавить свою конфигурацию spring xml или дополнительный код поддержки.   -  person Rohit Gulati    schedule 14.02.2017


Ответы (2)


Вместо того, чтобы иметь 2 разных объекта-сопоставителя, я предлагаю создать пользовательскую реализацию PropertyNamingStrategy, используя соответственно 2 другие стратегии:

public class AwesomePropertyNamingStrategy extends PropertyNamingStrategy {

  private PropertyNamingStrategy legacyStrategy  = PropertyNamingStrategy.LOWER_CASE;
  private PropertyNamingStrategy defaultStrategy  = PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES;

  @Override
  public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) {
    return getStrategy().nameForConstructorParameter(config, ctorParam, defaultName);
  }

  // TODO: implement other nameForXXX methods

  private PropertyNamingStrategy getStrategy() {
    if (isLegacyEndpoint()) {
      return legacyStrategy;
    } else {
      return defaultStrategy;
    }
  }

  private boolean isLegacyEndpoint() {
    // TODO: get hold of the RequestContext or some other thead-local context 
    // that allows you to know it's an old or a new endpoint
    return false;
  }
}

Вы должны придумать способ переключения между устаревшим и новым режимом:

  1. Использование URL-адреса конечной точки путем доступа к контексту запроса каким-либо образом
  2. Если ваша старая конечная точка использует другие объекты ответа, вместо этого используйте класс объекта, который преобразуется, чтобы определить устаревший/нормальный или вашу собственную пользовательскую аннотацию @LegacyResponse для всех старых классов.
person Frederik Heremans    schedule 14.02.2017
comment
Спасибо за ответ. Я пытался сделать все, что вы предложили. Я создал преобразователь сообщений и передал customObjectMapper и установил AwesomePropertyNamingStrategy, как вы предложили. Но ответ, который я получаю, представляет собой смесь стратегии малого случая и стратегии змеиного случая. Нужно ли мне что-то делать, чтобы решить эту проблему? - person Pramod; 15.02.2017
comment
Можете ли вы поделиться решением, которое вы придумали, особенно той частью, где вы различаете старое и новое? - person Frederik Heremans; 15.02.2017
comment
Я автоматически подключаю запрос HttpServletRequest в классе и загружаю AwesomePropertyNamingStrategy как компонент через spring. - person Pramod; 15.02.2017
comment
Это должно быть хорошо для доступа к текущему контексту запроса. Но какой механизм вы используете, чтобы различать старые и новые ресурсы/классы? - person Frederik Heremans; 16.02.2017
comment
Судя по вашему коду в вашем редактировании 2.0, он должен просто работать. Пробовали ли вы выполнять отладку в CustomPropertyNamingStrategy, чтобы понять, почему request.getRequestURL().toString() различаются при преобразовании одного объекта в JSON в одном запросе? Вы можете попробовать использовать подход RequestContextHolder, чтобы получить запрос вместо использования подхода autowire. - person Frederik Heremans; 16.02.2017

Ну ничего не получилось после многих попыток. Наконец закончилось определение 2 разных сервлетов. один без какой-либо версии и один с версией v1.

web.xml

        <servlet>
            <servlet-name>snake-case</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>

        <servlet-mapping>
            <servlet-name>snake-case</servlet-name>
            <url-pattern>/v1</url-pattern>
        </servlet-mapping>

         <servlet>
            <servlet-name>camel-case</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>

        <servlet-mapping>
            <servlet-name>camel-case</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>

Соответственно определены два сервлета: змейка-кейс-сервлет.xml и верблюд-кейс-сервлет.xml.

snake-case-servlet.xml

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="com.tgt.promotions.api.serialize.DataJSONConverter">
            <constructor-arg ref="snakeCaseObjectMapper"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven> 

camel-case-servlet.xml

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="com.tgt.promotions.api.serialize.DataJSONConverter">
            <constructor-arg ref="objectMapper"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven> 

Теперь для любых запросов с /v1* используется змейкаCaseObjectMapper, а для других запросов используется сопоставитель объектов по умолчанию.

person Pramod    schedule 31.03.2017