how to apply spring message convertors based on condition?

做~自己de王妃 提交于 2019-12-23 16:08:22

问题


I have a controller whose response is camelCase json value. Now we are re-writing the code with new version and the response required is in snake_case.

I have added a message converter and modified object mapper to setsetPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

public class ResponseJSONConverter extends MappingJackson2HttpMessageConverter {

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

I have registered this convertor with spring and its working as expected. Now I want my old endpoints to return in camelCase for backward compatibility for my consumers and new endpoints with snake_case.

I have tried to have one more message convertor with simple object mapper without setting camelCase to Snake case property and registered with spring. Only one message convertor gets applied based on the order declared in the spring configuration.

Is there any way we can achieve this ? Loading message convertor based on the condition ?

EDIT

Added my spring config file

 <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>

EDIT 2.0

my 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>

CustomMessageConverter

    public class ServiceJSONConverter extends MappingJackson2HttpMessageConverter {

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

Custom Object Mapper

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

Custom Property Naming Strategy

@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");
    }
}

回答1:


Instead of having 2 different object-mappers, I suggest creating a custom implementation of PropertyNamingStrategy, using the 2 other strategies accordingly:

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

You should come up with a way to toggle between legacy and new mode:

  1. Using the endpoint URL by accessing the request context in some way
  2. In case your old endpoint use different response objects, use the class of the object that is being converted to determine legacy/normal or your own custom @LegacyResponse annotation on all old classes instead.



回答2:


Well, nothing worked after many attempts. Finally ended up defining 2 different servlets. one without any version and one with v1 version.

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>

Accordingly defined two servlets snake-case-servlet.xml and camel-case-servlet.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> 

Now, for any requests with /v1* , snakeCaseObjectMapper is used and for other requests default object mapper is used.



来源:https://stackoverflow.com/questions/42219676/how-to-apply-spring-message-convertors-based-on-condition

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!