Um verschiedene Deserialisierungskonfigurationen zu haben, müssen Sie verschiedene ObjectMapper
Instanzen haben, aber Spring verwendet MappingJackson2HttpMessageConverter
, die nur eine Instanz verwenden soll.
Ich sehe zumindest zwei Möglichkeiten:
Umzug von MessageConverter weg zu einem ArgumentResolver
erstellen @CustomRequestBody
Anmerkung und ein 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
ist eine Schnittstelle Wir werden verwenden, um tatsächliche ObjectMapper
Instanz zu lösen, um zu verwenden, ich werde es unten diskutieren. Wenn Sie nur einen Anwendungsfall haben, in dem Sie ein benutzerdefiniertes Mapping benötigen, können Sie Ihren Mapper hier einfach initialisieren.
@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()));
}
}
Hinweis:
Sie können benutzerdefinierte Argument Resolver mit dieser Konfiguration hinzufügen nicht @CustomRequestBody
mit @RequestBody
kombinieren, werden sie ignoriert.
Wrap ObjectMapper
in einem Proxy, der mehrere Instanzen
MappingJackson2HttpMessageConverter
wurde entwickelt, um die Arbeit mit nur einer Instanz von ObjectMapper
versteckt. Wir können diese Instanz zu einem Proxy-Delegaten machen. Dies macht das Arbeiten mit mehreren Mappern transparent.
Zuerst brauchen wir einen Interzeptor, der alle Methodenaufrufe in ein zugrunde liegendes Objekt übersetzt.
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();
}
Jetzt ist unser ObjectMapper
Proxy-Bean wird wie folgt aussehen:
@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();
}
Hinweis: ich mit diesem Proxy auf Wildfly Klasse Ladeprobleme hatte, aufgrund seiner modularen Klasse Laden, so musste ich extend ObjectMapper
(ohne etwas zu ändern) nur so kann ich Klasse von meinem Modul verwenden.
sie alle zusammen mit dieser Konfiguration gebunden:
@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
Implementierungen
Abschließendes Stück ist die Logik, die die Mapper bestimmt verwendet werden soll, wird es in ObjectMapperResolver
Schnittstelle enthalten sein. Es enthält nur ein Look-Methode:
public interface ObjectMapperResolver {
ObjectMapper getObjectMapper();
}
Wenn Sie können Sie einfach eine Karte von vorkonfigurierten Instanzen mit ReqeustMatcher
s als Schlüssel machen nicht viele Anwendungsfälle mit benutzerdefinierten Mapper haben. Etwas wie folgt aus:
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;
}
}
Sie auch eine Anfrage scoped ObjectMapper
verwenden können, und konfigurieren Sie dann auf einer Pro-Anfrage Basis. Verwenden Sie diese Konfiguration:
@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();
}
Dies ist am besten geeignet für die individuelle Antwort Serialisierung, da Sie es direkt im Controller-Methode konfigurieren. Für die benutzerdefinierte Deserialisierung müssen Sie auch Filter
/HandlerInterceptor
/ControllerAdvice
verwenden, um den aktiven Mapper für die aktuelle Anforderung zu konfigurieren, bevor die Controller-Methode ausgelöst wird.
Sie können Schnittstelle, ähnlich wie ObjectMapperResolver
erstellen:
public interface ObjectMapperConfigurer {
void configureObjectMapper(ObjectMapper objectMapper);
}
Dann eine Karte dieser Instanzen mit RequstMatcher
s als Schlüssel machen und steckt es in einem Filter
/HandlerInterceptor
/ControllerAdvice
ähnlich wie RequestMatcherObjectMapperResolver
.
P.S.Wenn Sie die dynamische ObjectMapper
Konfiguration ein bisschen weiter erforschen möchten, kann ich meine alte Antwort here vorschlagen. Es beschreibt, wie Sie zur Laufzeit dynamische @JsonFilter
s machen können. Es enthält auch meinen älteren Ansatz mit erweiterten MappingJackson2HttpMessageConverter
, dass ich in Kommentaren vorgeschlagen.
Was ist mit dem Schreiben eines Objekt Mappers? Ich denke, dass Sie die Deserialisierungslogik hinzufügen können, die Sie darin benötigen. – reos
Das Problem erstellt keinen Objekt-Mapper. Meine Frage ist, wie kann ich einen Object Mapper auf einer Pro-Controller-Basis statt global in der Web-Anwendung konfigurieren. – Mark
Ich verstehe Ihre Frage, ich schlage vor, eine Objekt-Mapper zu schreiben, die in allen Controllern verwendet werden würde, aber ich kann das Objekt deserialize abhängig von der Anforderung, die es empfängt. – reos