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
Can you configure Spring controller specific Hymanson deserialization?
提问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 ObjectMapper
instances but out of the box Spring uses MappingHymanson2HttpMessageConverter
which 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 @CustomRequestBody
annotation, 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;
}
}
}
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.
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 @CustomRequestBody
with @RequestBody
, it will be ignored.
注意:不要@CustomRequestBody
与结合@RequestBody
,会被忽略。
Wrap ObjectMapper
in a proxy that hides multiple instances
包裹ObjectMapper
在隐藏多个实例的代理中
MappingHymanson2HttpMessageConverter
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.
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 ObjectMapper
proxy 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());
}
}
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:
最后一部分是确定应该使用哪个映射器的逻辑,它将包含在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 ReqeustMatcher
s as keys. Something like this:
如果您没有很多使用自定义映射器的用例,您可以简单地制作一个以ReqeustMatcher
s 作为键的预配置实例的映射。像这样的东西:
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 ObjectMapper
and 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
/ControllerAdvice
to 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 RequstMatcher
s as keys and put it in a Filter
/HandlerInterceptor
/ControllerAdvice
similar to RequestMatcherObjectMapperResolver
.
然后,让地图与此实例RequstMatcher
S作为钥匙,并把它在Filter
/ HandlerInterceptor
/ControllerAdvice
相似RequestMatcherObjectMapperResolver
。
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 @JsonFilter
s at run time. It also contains my older approach with extended MappingHymanson2HttpMessageConverter
that 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);
}