提交 d0e0b6c8 编写于 作者: R Rossen Stoyanchev

Minor Jackson encoder/decoder refactoring

Consolidate JsonView hint extraction in shared base class.

Rename base class from AbstractJackson2Codec to Jackson2CodecSupport
since the class mainly provides support methods.
上级 35805995
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -16,34 +16,46 @@
package org.springframework.http.codec.json;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* Abstract base class for Jackson based decoder/encoder implementations.
* Base class providing support methods for Jackson 2 encoding and decoding.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
* @since 5.0
*/
public class AbstractJackson2Codec {
public abstract class Jackson2CodecSupport {
/**
* Hint key to use with a {@link Class} value specifying the JSON View to use to serialize
* or deserialize an object.
* The key for the hint to specify a "JSON View" for encoding or decoding
* with the value expected to be a {@link Class}.
* @see <a href="http://wiki.fasterxml.com/JacksonJsonViews">Jackson JSON Views</a>
*/
public static final String JSON_VIEW_HINT = AbstractJackson2Codec.class.getName() + ".jsonView";
public static final String JSON_VIEW_HINT = Jackson2CodecSupport.class.getName() + ".jsonView";
private static final String JSON_VIEW_HINT_ERROR =
"@JsonView only supported for write hints with exactly 1 class argument: ";
protected static final List<MimeType> JSON_MIME_TYPES = Arrays.asList(
new MimeType("application", "json", StandardCharsets.UTF_8),
......@@ -54,10 +66,9 @@ public class AbstractJackson2Codec {
/**
* Create a new Jackson codec for the given mapper.
* @param mapper the Jackson ObjectMapper to use
* Constructor with a Jackson {@link ObjectMapper} to use.
*/
protected AbstractJackson2Codec(ObjectMapper mapper) {
protected Jackson2CodecSupport(ObjectMapper mapper) {
Assert.notNull(mapper, "ObjectMapper must not be null");
this.mapper = mapper;
}
......@@ -141,4 +152,22 @@ public class AbstractJackson2Codec {
return ResolvableType.NONE;
}
protected Map<String, Object> getHints(ResolvableType actualType) {
return getParameter(actualType)
.flatMap(parameter -> Optional.ofNullable(getAnnotation(parameter, JsonView.class))
.map(annotation -> {
Class<?>[] classes = annotation.value();
Assert.isTrue(classes.length == 1, JSON_VIEW_HINT_ERROR + parameter);
return Collections.<String, Object>singletonMap(JSON_VIEW_HINT, classes[0]);
}))
.orElse(Collections.emptyMap());
}
protected Optional<MethodParameter> getParameter(ResolvableType type) {
return Optional.ofNullable (type.getSource() instanceof MethodParameter ?
(MethodParameter) type.getSource() : null);
}
protected abstract <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType);
}
......@@ -17,11 +17,10 @@
package org.springframework.http.codec.json;
import java.io.IOException;
import java.util.Collections;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
......@@ -49,11 +48,11 @@ import org.springframework.util.MimeType;
* @since 5.0
* @see Jackson2JsonEncoder
*/
public class Jackson2JsonDecoder extends AbstractJackson2Codec implements ServerHttpDecoder<Object> {
public class Jackson2JsonDecoder extends Jackson2CodecSupport implements ServerHttpDecoder<Object> {
private final JsonObjectDecoder fluxObjectDecoder = new JsonObjectDecoder(true);
private final JsonObjectDecoder fluxDecoder = new JsonObjectDecoder(true);
private final JsonObjectDecoder monoObjectDecoder = new JsonObjectDecoder(false);
private final JsonObjectDecoder monoDecoder = new JsonObjectDecoder(false);
public Jackson2JsonDecoder() {
......@@ -78,19 +77,17 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Server
}
@Override
public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
MimeType mimeType, Map<String, Object> hints) {
JsonObjectDecoder objectDecoder = this.fluxObjectDecoder;
return decodeInternal(objectDecoder, inputStream, elementType, mimeType, hints);
return decodeInternal(this.fluxDecoder, input, elementType, mimeType, hints);
}
@Override
public Mono<Object> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
public Mono<Object> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
MimeType mimeType, Map<String, Object> hints) {
JsonObjectDecoder objectDecoder = this.monoObjectDecoder;
return decodeInternal(objectDecoder, inputStream, elementType, mimeType, hints).singleOrEmpty();
return decodeInternal(this.monoDecoder, input, elementType, mimeType, hints).singleOrEmpty();
}
private Flux<Object> decodeInternal(JsonObjectDecoder objectDecoder, Publisher<DataBuffer> inputStream,
......@@ -99,20 +96,13 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Server
Assert.notNull(inputStream, "'inputStream' must not be null");
Assert.notNull(elementType, "'elementType' must not be null");
MethodParameter methodParam = (elementType.getSource() instanceof MethodParameter ?
(MethodParameter) elementType.getSource() : null);
Class<?> contextClass = (methodParam != null ? methodParam.getContainingClass() : null);
Class<?> contextClass = getParameter(elementType).map(MethodParameter::getContainingClass).orElse(null);
JavaType javaType = getJavaType(elementType.getType(), contextClass);
Class<?> jsonView = (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT);
ObjectReader reader;
Class<?> jsonView = (Class<?>)hints.get(AbstractJackson2Codec.JSON_VIEW_HINT);
if (jsonView != null) {
reader = this.mapper.readerWithView(jsonView).forType(javaType);
}
else {
reader = this.mapper.readerFor(javaType);
}
ObjectReader reader = jsonView != null ?
this.mapper.readerWithView(jsonView).forType(javaType) :
this.mapper.readerFor(javaType);
return objectDecoder.decode(inputStream, elementType, mimeType, hints)
.map(dataBuffer -> {
......@@ -134,20 +124,12 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Server
public Map<String, Object> getDecodeHints(ResolvableType actualType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response) {
Object source = actualType.getSource();
MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null);
if (parameter != null) {
JsonView annotation = parameter.getParameterAnnotation(JsonView.class);
if (annotation != null) {
Class<?>[] classes = annotation.value();
if (classes.length != 1) {
throw new IllegalArgumentException(
"@JsonView only supported for read hints with exactly 1 class argument: " + parameter);
}
return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
}
}
return Collections.emptyMap();
return getHints(actualType);
}
@Override
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType) {
return parameter.getParameterAnnotation(annotType);
}
}
......@@ -18,11 +18,10 @@ package org.springframework.http.codec.json;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
......@@ -61,7 +60,7 @@ import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON;
* @since 5.0
* @see Jackson2JsonDecoder
*/
public class Jackson2JsonEncoder extends AbstractJackson2Codec implements ServerHttpEncoder<Object> {
public class Jackson2JsonEncoder extends Jackson2CodecSupport implements ServerHttpEncoder<Object> {
private final PrettyPrinter ssePrettyPrinter;
......@@ -112,30 +111,24 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Server
}
private DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory,
ResolvableType type, Map<String, Object> hints) {
ResolvableType elementType, Map<String, Object> hints) {
TypeFactory typeFactory = this.mapper.getTypeFactory();
JavaType javaType = typeFactory.constructType(type.getType());
if (type.isInstance(value)) {
javaType = getJavaType(type.getType(), null);
JavaType javaType = typeFactory.constructType(elementType.getType());
if (elementType.isInstance(value)) {
javaType = getJavaType(elementType.getType(), null);
}
ObjectWriter writer;
Class<?> jsonView = (Class<?>)hints.get(AbstractJackson2Codec.JSON_VIEW_HINT);
if (jsonView != null) {
writer = this.mapper.writerWithView(jsonView);
}
else {
writer = this.mapper.writer();
}
Class<?> jsonView = (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT);
ObjectWriter writer = jsonView != null ? this.mapper.writerWithView(jsonView): this.mapper.writer();
if (javaType != null && javaType.isContainerType()) {
writer = writer.forType(javaType);
}
Boolean sse = (Boolean)hints.get(ServerSentEventHttpMessageWriter.SSE_CONTENT_HINT);
SerializationConfig config = writer.getConfig();
if (Boolean.TRUE.equals(sse) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
Boolean sseHint = (Boolean) hints.get(ServerSentEventHttpMessageWriter.SSE_CONTENT_HINT);
if (Boolean.TRUE.equals(sseHint) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
writer = writer.with(this.ssePrettyPrinter);
}
......@@ -158,21 +151,12 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Server
public Map<String, Object> getEncodeHints(ResolvableType actualType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) {
Map<String, Object> hints = new HashMap<>();
Object source = actualType.getSource();
MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null);
if (returnValue != null) {
JsonView annotation = returnValue.getMethodAnnotation(JsonView.class);
if (annotation != null) {
Class<?>[] classes = annotation.value();
if (classes.length != 1) {
throw new IllegalArgumentException(
"@JsonView only supported for write hints with exactly 1 class argument: " + returnValue);
}
hints.put(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
}
}
return hints;
return getHints(actualType);
}
@Override
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType) {
return parameter.getMethodAnnotation(annotType);
}
}
......@@ -29,7 +29,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.AbstractJackson2Codec;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
......@@ -207,7 +207,7 @@ public interface EntityResponse<T> extends ServerResponse {
Builder<T> contentType(MediaType contentType);
/**
* Add a serialization hint like {@link AbstractJackson2Codec#JSON_VIEW_HINT} to
* Add a serialization hint like {@link Jackson2CodecSupport#JSON_VIEW_HINT} to
* customize how the body will be serialized.
* @param key the hint key
* @param value the hint value
......
......@@ -32,7 +32,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.AbstractJackson2Codec;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.server.WebSession;
......@@ -82,7 +82,7 @@ public interface ServerRequest {
/**
* Extract the body with the given {@code BodyExtractor} and hints.
* @param extractor the {@code BodyExtractor} that reads from the request
* @param hints the map of hints like {@link AbstractJackson2Codec#JSON_VIEW_HINT}
* @param hints the map of hints like {@link Jackson2CodecSupport#JSON_VIEW_HINT}
* to use to customize body extraction
* @param <T> the type of the body returned
* @return the extracted body
......
......@@ -31,7 +31,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.AbstractJackson2Codec;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserter;
......@@ -326,7 +326,7 @@ public interface ServerResponse {
BodyBuilder contentType(MediaType contentType);
/**
* Add a serialization hint like {@link AbstractJackson2Codec#JSON_VIEW_HINT}
* Add a serialization hint like {@link Jackson2CodecSupport#JSON_VIEW_HINT}
* to customize how the body will be serialized.
* @param key the hint key
* @param value the hint value
......
......@@ -50,7 +50,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
import static org.springframework.http.codec.json.AbstractJackson2Codec.*;
import static org.springframework.http.codec.json.Jackson2CodecSupport.*;
/**
* @author Arjen Poutsma
......
......@@ -61,7 +61,7 @@ import org.springframework.util.MultiValueMap;
import static java.nio.charset.StandardCharsets.*;
import static org.junit.Assert.*;
import static org.springframework.http.codec.json.AbstractJackson2Codec.*;
import static org.springframework.http.codec.json.Jackson2CodecSupport.*;
/**
* @author Arjen Poutsma
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册