你能配置Spring控制器特定的Jackson反序列化吗? [英] Can you configure Spring controller specific Jackson deserialization?

查看:88
本文介绍了你能配置Spring控制器特定的Jackson反序列化吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要为我的Spring 4.1.x MVC应用程序添加java.lang.String的自定义Jackson反序列化器。但是,所有答案(例如)都指的是配置ObjectMapper以获得完整的答案Web应用程序和更改将适用于所有控制器中所有@RequestBody的所有字符串。

I need to add a custom Jackson deserializer for java.lang.String to my Spring 4.1.x MVC application. However all answers (such as this) refer to configuring the ObjectMapper for the complete web application and the changes will apply to all Strings across all @RequestBody in all controllers.

我只想将自定义反序列化应用于特定控制器中使用的@RequestBody参数。请注意,我没有为特定字符串字段使用@JsonDeserialize注释的选项。

I only want to apply the custom deserialization to @RequestBody arguments used within particular controllers. Note that I don't have the option of using @JsonDeserialize annotations for the specific String fields.

您是否可以仅为特定控制器配置自定义反序列化?

Can you configure custom deserialization for specific controllers only?

推荐答案

要使用不同的反序列化配置,您必须具有不同的 ObjectMapper 实例,但开箱即用Spring使用 MappingJackson2HttpMessageConverter ,它只用于一个实例。

To have different deserialization configurations you must have different ObjectMapper instances but out of the box Spring uses MappingJackson2HttpMessageConverter which is designed to use only one instance.

我在这里看到至少两个选项:

I see at least two options here:

从MessageConverter转移到ArgumentResolver

创建 @ CustomRequestBody 注释和参数解析器:

Create a @CustomRequestBody annotation, and an argument resolver:

public class CustomRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {

  private final ObjectMapperResolver objectMapperResolver;

  public CustomRequestBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
    this.objectMapperResolver = objectMapperResolver;
  }

  @Override
  public boolean supportsParameter(MethodParameter methodParameter) {
    return methodParameter.getParameterAnnotation(CustomRequestBody.class) != null;
  }

  @Override
  public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    if (this.supportsParameter(methodParameter)) {
      ObjectMapper objectMapper = objectMapperResolver.getObjectMapper();
      HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
      return objectMapper.readValue(request.getInputStream(), methodParameter.getParameterType());
    } else {
      return WebArgumentResolver.UNRESOLVED;
    }
  }
}

ObjectMapperResolver 是我们将用于解析实际 ObjectMapper 实例的接口,我将在下面讨论它。当然,如果您只有一个需要自定义映射的用例,则可以在此处初始化映射器。

ObjectMapperResolver is an interface we will be using to resolve actual ObjectMapper instance to use, I will discuss it below. Of course if you have only one use case where you need custom mapping you can simply initialize your mapper here.

您可以使用以下配置添加自定义参数解析器:

You can add custom argument resolver with this configuration:

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

  @Bean
  public CustomRequestBodyArgumentResolver customBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
    return new CustomRequestBodyArgumentResolver(objectMapperResolver)
  } 

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {       
    argumentResolvers.add(customBodyArgumentResolver(objectMapperResolver()));
  }
}

注意:不要将 @CustomRequestBody @RequestBody 合并,将被忽略。

Note: Do not combine @CustomRequestBody with @RequestBody, it will be ignored.

在隐藏多个实例的代理中包装 ObjectMapper

Wrap ObjectMapper in a proxy that hides multiple instances

MappingJackson2HttpMessageConverter 旨在仅与 ObjectMapper 的一个实例一起使用。我们可以将该实例设为代理委托。这将使多个映射器的工作变得透明。

MappingJackson2HttpMessageConverter is designed to work with only one instance of ObjectMapper. We can make that instance a proxy delegate. This will make working with multiple mappers transparent.

首先,我们需要一个拦截器,它将所有方法调用转换为底层对象。

First of all we need an interceptor that will translate all method invocations to an underlying object.

public abstract class ObjectMapperInterceptor implements MethodInterceptor {

  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    return ReflectionUtils.invokeMethod(invocation.getMethod(), getObject(), invocation.getArguments());
  } 

  protected abstract ObjectMapper getObject();

}

现在我们的 ObjectMapper 代理bean将如下所示:

Now our ObjectMapper proxy bean will look like this:

@Bean
public ObjectMapper objectMapper(ObjectMapperResolver objectMapperResolver) {
  ProxyFactory factory = new ProxyFactory();
  factory.setTargetClass(ObjectMapper.class);
  factory.addAdvice(new ObjectMapperInterceptor() {

      @Override
      protected ObjectMapper getObject() {
        return objectMapperResolver.getObjectMapper();
      }

  });

  return (ObjectMapper) factory.getProxy();
}

注意:我有类加载问题Wildfly上的代理,由于它的模块化类加载,所以我不得不扩展 ObjectMapper (不做任何改动),这样我就可以使用我的模块中的类了。

Note: I had class loading issues with this proxy on Wildfly, due to its modular class loading, so I had to extend ObjectMapper (without changing anything) just so I can use class from my module.

使用此配置将它们捆绑在一起:

It all tied up together using this configuration:

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

  @Bean
  public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
    return new MappingJackson2HttpMessageConverter(objectMapper(objectMapperResolver()));
  }

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(jackson2HttpMessageConverter());
  }
}

ObjectMapperResolver implementation

ObjectMapperResolver implementations

最终作品是确定应该使用哪个映射器的逻辑,它将包含在中ObjectMapperResolver 接口。它只包含一个查找方法:

Final piece is the logic that determines which mapper should be used, it will be contained in ObjectMapperResolver interface. It contains only one look up method:

public interface ObjectMapperResolver {

  ObjectMapper getObjectMapper();

}

如果您没有很多自定义用例mappers你可以简单地用 ReqeustMatcher 作为键来制作预配置实例的地图。这样的事情:

If you do not have a lot of use cases with custom mappers you can simply make a map of preconfigured instances with ReqeustMatchers as keys. Something like this:

public class RequestMatcherObjectMapperResolver implements ObjectMapperResolver {

  private final ObjectMapper defaultMapper;
  private final Map<RequestMatcher, ObjectMapper> mapping = new HashMap<>();

  public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper, Map<RequestMatcher, ObjectMapper> mapping) {
    this.defaultMapper = defaultMapper;
    this.mapping.putAll(mapping);
  }

  public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper) {
    this.defaultMapper = defaultMapper;
  }

  @Override
  public ObjectMapper getObjectMapper() {
    ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = sra.getRequest();
    for (Map.Entry<RequestMatcher, ObjectMapper> entry : mapping.entrySet()) {
      if (entry.getKey().matches(request)) {
        return entry.getValue();
      }
    }
    return defaultMapper;
  }

}

您还可以使用请求作用域 ObjectMapper 然后根据请求对其进行配置。使用此配置:

You can also use a request scoped ObjectMapper and then configure it on a per-request basis. Use this configuration:

@Bean
public ObjectMapperResolver objectMapperResolver() {
  return new ObjectMapperResolver() {
    @Override
    public ObjectMapper getObjectMapper() {
      return requestScopedObjectMapper();
    }
  };
}


@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ObjectMapper requestScopedObjectMapper() {
  return new ObjectMapper();
}

这最适合自定义响应序列化,因为您可以直接配置它控制器方法。对于自定义反序列化,您还必须使用过滤器 / HandlerInterceptor / ControllerAdvice 在触发控制器方法之前为当前请求配置活动映射器。

This is best suited for custom response serialization, since you can configure it right in the controller method. For custom deserialization you must also use Filter/HandlerInterceptor/ControllerAdvice to configure active mapper for current request before the controller method is triggered.

您可以创建接口,类似于 ObjectMapperResolver

You can create interface, similar to ObjectMapperResolver:

public interface ObjectMapperConfigurer {

  void configureObjectMapper(ObjectMapper objectMapper);

}

然后使用<$ c $制作此实例的地图c> RequstMatcher s作为键并将其放入过滤器 / HandlerInterceptor / ControllerAdvice 类似于 RequestMatcherObjectMapperResolver

Then make a map of this instances with RequstMatchers as keys and put it in a Filter/HandlerInterceptor/ControllerAdvice similar to RequestMatcherObjectMapperResolver.

PS如果你想进一步探索动态 ObjectMapper 配置,我可以建议我的旧答案这里。它描述了如何在运行时创建动态 @JsonFilter 。它还包含我在评论中建议的扩展 MappingJackson2HttpMessageConverter 的旧方法。

P.S. If you want to explore dynamic ObjectMapper configuration a bit further I can suggest my old answer here. It describes how you can make dynamic @JsonFilters at run time. It also contains my older approach with extended MappingJackson2HttpMessageConverter that I suggested in comments.

这篇关于你能配置Spring控制器特定的Jackson反序列化吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆