java 您可以配置特定于 Spring 控制器的 Jackson 反序列化吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37844101/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 02:52:13  来源:igfitidea点击:

Can you configure Spring controller specific Hymanson deserialization?

javaspring-mvcHymanson

提问by Mark

I need to add a custom Hymanson 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.

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

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.

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

Can you configure custom deserialization for specific controllers only?

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

回答by chimmi

To have different deserialization configurations you must have different ObjectMapperinstances but out of the box Spring uses MappingHymanson2HttpMessageConverterwhich is designed to use only one instance.

要拥有不同的反序列化配置,您必须拥有不同的ObjectMapper实例,但 Spring 使用的开箱即用MappingHymanson2HttpMessageConverter,旨在仅使用一个实例。

I see at least two options here:

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

Move away from MessageConverter to an ArgumentResolver

从 MessageConverter 转移到 ArgumentResolver

Create a @CustomRequestBodyannotation, and an argument resolver:

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

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

ObjectMapperResolveris an interface we will be using to resolve actual ObjectMapperinstance 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.

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

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

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

注意:不要@CustomRequestBody与结合@RequestBody,会被忽略。

Wrap ObjectMapperin a proxy that hides multiple instances

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

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

MappingHymanson2HttpMessageConverter旨在仅与ObjectMapper. 我们可以使该实例成为代理委托。这将使使用多个映射器变得透明。

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();

}

Now our ObjectMapperproxy bean will look like this:

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

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

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.

注意:我在 Wildfly 上遇到了这个代理的类加载问题,因为它是模块化的类加载,所以我不得不扩展ObjectMapper(不改变任何东西),这样我就可以使用我的模块中的类。

It all tied up together using this configuration:

这一切都使用以下配置捆绑在一起:

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

  @Bean
  public MappingHymanson2HttpMessageConverter Hymanson2HttpMessageConverter() {
    return new MappingHymanson2HttpMessageConverter(objectMapper(objectMapperResolver()));
  }

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

ObjectMapperResolverimplementations

ObjectMapperResolver实现

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

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

public interface ObjectMapperResolver {

  ObjectMapper getObjectMapper();

}

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:

如果您没有很多使用自定义映射器的用例,您可以简单地制作一个以ReqeustMatchers 作为键的预配置实例的映射。像这样的东西:

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

}

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

您还可以使用范围内的请求ObjectMapper,然后根据每个请求对其进行配置。使用这个配置:

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

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/ControllerAdviceto configure active mapper for current request before the controller method is triggered.

这最适合自定义响应序列化,因为您可以在控制器方法中正确配置它。对于自定义反序列化必须同时使用Filter/ HandlerInterceptor/ControllerAdvice来触发控制器方法之前,当前请求配置主动映射。

You can create interface, similar to ObjectMapperResolver:

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

public interface ObjectMapperConfigurer {

  void configureObjectMapper(ObjectMapper objectMapper);

}

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

然后,让地图与此实例RequstMatcherS作为钥匙,并把它在Filter/ HandlerInterceptor/ControllerAdvice相似RequestMatcherObjectMapperResolver

P.S. If you want to explore dynamic ObjectMapperconfiguration 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 MappingHymanson2HttpMessageConverterthat I suggested in comments.

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

回答by SachinSarawgi

You can create custom deserializer for your String data.

您可以为 String 数据创建自定义反序列化器。

Custom Deserializer

自定义解串器

public class CustomStringDeserializer extends JsonDeserializer<String> {

  @Override
  public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {

    String str = p.getText();

    //return processed String
  }

}

}

Now suppose the String is present inside a POJO use @JsonDeserialize annotation above the variable:

现在假设字符串存在于 POJO 中,在变量上方使用 @JsonDeserialize 注释:

public class SamplePOJO{
  @JsonDeserialize(using=CustomStringDeserializer.class)
  private String str;
  //getter and setter
}

Now when you return it as a response it will be Deserialized in the way you have done it in CustomDeserializer.

现在,当您将其作为响应返回时,它将按照您在 CustomDeserializer 中完成的方式进行反序列化。

Hope it helps.

希望能帮助到你。

回答by Albert Bos

Probably this would help, but it ain't pretty. It would require AOP. Also I did not validate it. Create a @CustomAnnotation.

可能这会有所帮助,但它并不漂亮。这将需要 AOP。我也没有验证它。创建一个@CustomAnnotation.

Update your controller:

更新您的控制器:

void someEndpoint(@RequestBody @CustomAnnotation SomeEntity someEntity);

Then implemment the AOP part:

然后实现AOP部分:

@Around("execution(* *(@CustomAnnotation (*)))")
public void advice(ProceedingJoinPoint proceedingJoinPoint) {
  // Here you would add custom ObjectMapper, I don't know another way around it
  HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
  String body = request .getReader().lines().collect(Collectors.joining(System.lineSeparator()));

  SomeEntity someEntity = /* deserialize */;
  // This could be cleaner, cause the method can accept multiple parameters
  proceedingJoinPoint.proceed(new Object[] {someEntity});
}

回答by egorlitvinenko

You could try Message Converters. They have a context about http input request (for example, docs see here, JSON). How to customize you could see here. Idea that you could check HttpInputMessage with special URIs, which used in your controllers and convert string as you want. You could create special annotation for this, scan packages and do it automatically.

你可以试试Message Converters。他们有一个关于 http 输入请求的上下文(例如,文档见这里JSON)。如何定制你可以在这里看到。您可以使用特殊 URI 检查 HttpInputMessage 的想法,该 URI 在您的控制器中使用并根据需要转换字符串。您可以为此创建特殊注释,扫描包并自动执行。

Note

笔记

Likely, you don't need implementation of ObjectMappers. You can use simple default ObjectMapper to parse String and then convert string as you wish. In that case you would create RequestBody once.

很可能,您不需要 ObjectMappers 的实现。您可以使用简单的默认 ObjectMapper 来解析字符串,然后根据需要转换字符串。在这种情况下,您将创建一次 RequestBody。

回答by Alex S

You can define a POJO for each different type of request parameter that you would like to deserialize. Then, the following code will pull in the values from the JSON into the object that you define, assuming that the names of the fields in your POJO match with the names of the field in the JSON request.

您可以为要反序列化的每种不同类型的请求参数定义一个 POJO。然后,假设您的 POJO 中的字段名称与 JSON 请求中的字段名称匹配,以下代码会将值从 JSON 提取到您定义的对象中。

ObjectMapper mapper = new ObjectMapper(); 
YourPojo requestParams = null;

try {
    requestParams = mapper.readValue(JsonBody, YourPOJO.class);

} catch (IOException e) {
    throw new IOException(e);
}