问题描述
我需要为我的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 ReqeustMatcher
s 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 RequstMatcher
s 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 @JsonFilter
s at run time. It also contains my older approach with extended MappingJackson2HttpMessageConverter
that I suggested in comments.
这篇关于你能配置Spring控制器特定的Jackson反序列化吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!