我需要为我的Spring 4.1.x MVC应用程序添加java.lang.String的自定义Jackson反序列化器。但是,所有答案(例如this)都指的是为完整的Web应用程序配置ObjectMapper,并且更改将适用于所有控制器中所有@RequestBody的所有字符串。
我只想将自定义反序列化应用于特定控制器中使用的@RequestBody参数。请注意,我没有选择为特定字符串字段使用@JsonDeserialize注释。
您是否可以仅为特定控制器配置自定义反序列化?
答案 0 :(得分:7)
要使用不同的反序列化配置,您必须拥有不同的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;
}
}
}
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();
}
如果你没有很多自定义映射器的用例,你可以简单地制作一个以ReqeustMatcher
为键的预配置实例的映射。像这样:
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);
}
然后使用RequstMatcher
s作为关键点制作此实例的地图,并将其放入与Filter
类似的HandlerInterceptor
/ ControllerAdvice
/ RequestMatcherObjectMapperResolver
。< / p>
P.S。如果您想进一步探索动态ObjectMapper
配置,我可以建议我的旧答案here。它描述了如何在运行时创建动态@JsonFilter
。它还包含我在评论中建议的扩展MappingJackson2HttpMessageConverter
的旧方法。
答案 1 :(得分:0)
这可能会有所帮助,但它不是很好。这需要AOP。我也没有验证它。
创建@CustomAnnotation
。
更新您的控制器:
void someEndpoint(@RequestBody @CustomAnnotation SomeEntity someEntity);
然后实现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});
}
答案 2 :(得分:0)
您可以尝试Message Converters。 他们有关于http输入请求的上下文(例如,文档请参阅here,JSON)。如何自定义您可以看到here。 您可以使用特殊URI检查HttpInputMessage的想法,这些URI在您的控制器中使用并根据需要转换字符串。 您可以为此创建特殊注释,扫描包并自动执行。
注意的
可能,您不需要实现ObjectMappers。您可以使用简单的默认ObjectMapper来解析String,然后根据需要转换字符串。 在这种情况下,您将创建一次RequestBody。
答案 3 :(得分:0)
您可以为String数据创建自定义反序列化器。
自定义反序列化器
public class CustomStringDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String str = p.getText();
//return processed String
}
}
现在假设字符串存在于POJO中,使用变量上方的@JsonDeserialize注释:
public class SamplePOJO{
@JsonDeserialize(using=CustomStringDeserializer.class)
private String str;
//getter and setter
}
现在,当您将其作为响应返回时,它将按照您在CustomDeserializer中的方式进行反序列化。
希望它有所帮助。
答案 4 :(得分:-2)
您可以为要反序列化的每种不同类型的请求参数定义POJO。然后,以下代码将JSON中的值拉入您定义的对象中,假设POJO中字段的名称与JSON请求中字段的名称匹配。
ObjectMapper mapper = new ObjectMapper();
YourPojo requestParams = null;
try {
requestParams = mapper.readValue(JsonBody, YourPOJO.class);
} catch (IOException e) {
throw new IOException(e);
}