4

我有一个控制器,它的响应是 camelCase json 值。现在我们正在用新版本重新编写代码,所需的响应是在snake_case中。

我添加了一个消息转换器并修改了对象映射器来设置setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

public class ResponseJSONConverter extends MappingJackson2HttpMessageConverter {

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

我已经用 spring 注册了这个转换器,它按预期工作。现在我希望我的旧端点以 camelCase 的形式返回,以便为我的消费者和新端点与蛇案例的向后兼容性。

我试图在没有将 camelCase 设置为 Snake case 属性并使用 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");
    }
}
4

2 回答 2

0

我建议不要使用 2 个不同的对象映射器,而是创建一个自定义实现PropertyNamingStrategy,相应地使用其他两种策略:

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的所有旧类上的自定义注释。
于 2017-02-14T10:02:07.953 回答
0

好吧,经过多次尝试,没有任何效果。最后最终定义了 2 个不同的 servlet。一个没有任何版本,一个有 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>

相应地定义了两个servletsnake-case-servlet.xml 和camel-case-servlet.xml。

蛇案例-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> 

骆驼案例-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* 的请求,都使用snakeCaseObjectMapper,而对于其他请求,则使用默认对象映射器。

于 2017-03-31T05:54:55.593 回答