要拥有不同的反序列化配置,您必须拥有不同的 ObjectMapper 实例,但开箱即用的 Spring 使用 MappingJackson2HttpMessageConverter,它被设计为仅使用一个实例。
我在这里至少看到两个选项:
从 MessageConverter 移到 ArgumentResolver
创建一个@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;
}
}
}
@CustomRequestBody注解:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRequestBody {
boolean required() default true;
}
ObjectMapperResolver 是我们将用来解析实际使用的ObjectMapper 实例的接口,我将在下面讨论它。当然,如果您只有一个需要自定义映射的用例,您可以在此处简单地初始化您的映射器。
您可以使用此配置添加自定义参数解析器:
@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 结合使用,它将被忽略。
将ObjectMapper 包装在隐藏多个实例的代理中
MappingJackson2HttpMessageConverter 设计为仅使用 ObjectMapper 的一个实例。我们可以使该实例成为代理委托。这将使多个映射器的工作变得透明。
首先,我们需要一个拦截器,它将所有方法调用转换为底层对象。
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 将如下所示:
@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(不做任何更改),这样我才能使用来自的类我的模块。
使用此配置将所有内容捆绑在一起:
@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 实现
最后一块是确定应该使用哪个映射器的逻辑,它将包含在ObjectMapperResolver接口中。它只包含一种查找方法:
public interface ObjectMapperResolver {
ObjectMapper getObjectMapper();
}
如果您没有很多使用自定义映射器的用例,您可以简单地使用 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;
}
}
您还可以使用范围为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();
}
这最适合自定义响应序列化,因为您可以在控制器方法中正确配置它。对于自定义反序列化,您还必须使用Filter/HandlerInterceptor/ControllerAdvice 在触发控制器方法之前为当前请求配置活动映射器。
你可以创建接口,类似于ObjectMapperResolver:
public interface ObjectMapperConfigurer {
void configureObjectMapper(ObjectMapper objectMapper);
}
然后以RequstMatchers 为键制作此实例的映射,并将其放入类似于RequestMatcherObjectMapperResolver 的Filter/HandlerInterceptor/ControllerAdvice。
附:如果您想进一步探索动态ObjectMapper 配置,我可以建议我的旧答案here。它描述了如何在运行时生成动态@JsonFilters。它还包含我在 cmets 中建议的带有扩展 MappingJackson2HttpMessageConverter 的旧方法。