FastJsonHttpMessageConverter.java
package com.alibaba.fastjson2.support.spring.http.converter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONPObject;
import com.alibaba.fastjson2.support.config.FastJsonConfig;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* Fastjson for Spring MVC Converter.
*
* @author Victor.Zxy
* @see AbstractHttpMessageConverter
* @see GenericHttpMessageConverter
* @since 2.0.2
*/
public class FastJsonHttpMessageConverter
extends AbstractHttpMessageConverter<Object>
implements GenericHttpMessageConverter<Object> {
public static final MediaType APPLICATION_JAVASCRIPT = new MediaType("application", "javascript");
/**
* with fastJson config
*/
private FastJsonConfig config = new FastJsonConfig();
/**
* Can serialize/deserialize all types.
*/
public FastJsonHttpMessageConverter() {
super(MediaType.ALL);
setDefaultCharset(StandardCharsets.UTF_8);
}
/**
* @return the fastJsonConfig.
*/
public FastJsonConfig getFastJsonConfig() {
return config;
}
/**
* @param fastJsonConfig the fastJsonConfig to set.
*/
public void setFastJsonConfig(FastJsonConfig fastJsonConfig) {
this.config = fastJsonConfig;
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return super.canRead(contextClass, mediaType);
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return super.canWrite(clazz, mediaType);
}
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return readType(getType(type, contextClass), inputMessage);
}
@Override
public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// support StreamingHttpOutputMessage in spring4.0+
super.write(o, contentType, outputMessage);
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return readType(getType(clazz, null), inputMessage);
}
/** Default initialization capacity when content-length is not specified */
private static int REQUEST_BODY_INITIAL_CAPACITY = 8192;
public static void setRequestBodyInitialCapacity(int initialCapacity) {
if (initialCapacity < 128 || initialCapacity > 1024 * 1024) {
throw new IllegalArgumentException("invalid initialCapacity: " + initialCapacity);
}
REQUEST_BODY_INITIAL_CAPACITY = initialCapacity;
}
/**
* @param contentLength The content length of the request message. If -1 is passed, it means unknown.
*/
protected static int calcInitialCapacity(long contentLength) {
return contentLength == -1 || contentLength > Integer.MAX_VALUE
? REQUEST_BODY_INITIAL_CAPACITY
// The maximum limit is 1MB to prevent fake request headers
: (int) Math.min(contentLength, 1024 * 1024);
}
/**
* @param in the specified input stream
* @param contentLength -1 means unknown
*/
protected static byte[] fastRead(final InputStream in, final long contentLength) throws IOException {
final int expectSize = calcInitialCapacity(contentLength);
byte[] body = new byte[expectSize];
int offset = in.read(body, 0, body.length);
if (offset == -1) {
body = new byte[0];
} else if (contentLength == -1 || offset != contentLength) {
final byte[] buf = new byte[1024];
int len = in.read(buf);
while (len != -1) { // Refer to the implementation of ByteArrayOutputStream
final int minRequired = offset + len;
final int oldLength = body.length;
if (minRequired > oldLength) {
int newLength = newLength(oldLength, minRequired - oldLength, oldLength);
byte[] newBody = Arrays.copyOf(body, newLength);
System.arraycopy(buf, 0, newBody, offset, len);
body = newBody;
} else {
System.arraycopy(buf, 0, body, offset, len);
}
offset = minRequired;
len = in.read(buf);
}
if (offset != body.length) {
body = Arrays.copyOf(body, offset);
}
}
return body;
}
// see jdk.internal.util.ArraysSupport.SOFT_MAX_ARRAY_LENGTH
private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
// see jdk.internal.util.ArraysSupport.newLength( )
private static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength(oldLength, minGrowth);
}
}
// see jdk.internal.util.ArraysSupport.hugeLength( )
private static int hugeLength(int oldLength, int minGrowth) {
int minLength = oldLength + minGrowth;
if (minLength < 0) { // overflow
throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");
} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
return SOFT_MAX_ARRAY_LENGTH;
} else {
return minLength;
}
}
protected Object readType(Type type, HttpInputMessage inputMessage) {
final long contentLength = inputMessage.getHeaders().getContentLength(); // -1 ������������
try {
final byte[] body = fastRead(inputMessage.getBody(), contentLength);
return JSON.parseObject(body, type, config.readerContext());
} catch (JSONException ex) {
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getMessage(), ex, inputMessage);
} catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
}
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
HttpHeaders headers = outputMessage.getHeaders();
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int contentLength;
if (object instanceof String && JSON.isValidObject((String) object)) {
byte[] strBytes = ((String) object).getBytes(config.getCharset());
contentLength = strBytes.length;
outputMessage.getBody().write(strBytes, 0, strBytes.length);
} else if (object instanceof byte[] && JSON.isValid((byte[]) object)) {
byte[] strBytes = (byte[]) object;
contentLength = strBytes.length;
outputMessage.getBody().write(strBytes, 0, strBytes.length);
} else {
if (object instanceof JSONPObject) {
headers.setContentType(APPLICATION_JAVASCRIPT);
}
contentLength = JSON.writeTo(
baos, object, config.writerContext()
);
}
if (headers.getContentLength() < 0 && config.isWriteContentLength()) {
headers.setContentLength(contentLength);
}
baos.writeTo(outputMessage.getBody());
} catch (JSONException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
} catch (IOException ex) {
throw new HttpMessageNotWritableException("I/O error while writing output message", ex);
}
}
protected Type getType(Type type, Class<?> contextClass) {
if (Spring4TypeResolvableHelper.isSupport()) {
return Spring4TypeResolvableHelper.getType(type, contextClass);
}
return type;
}
private static class Spring4TypeResolvableHelper {
private static boolean hasClazzResolvableType;
static {
try {
Class.forName("org.springframework.core.ResolvableType");
hasClazzResolvableType = true;
} catch (ClassNotFoundException e) {
hasClazzResolvableType = false;
}
}
private static boolean isSupport() {
return hasClazzResolvableType;
}
private static Type getType(Type type, Class<?> contextClass) {
if (contextClass != null) {
ResolvableType resolvedType = ResolvableType.forType(type);
if (type instanceof TypeVariable) {
ResolvableType resolvedTypeVariable = resolveVariable((TypeVariable) type, ResolvableType.forClass(contextClass));
if (resolvedTypeVariable != ResolvableType.NONE) {
return resolvedTypeVariable.resolve();
}
} else if (type instanceof ParameterizedType && resolvedType.hasUnresolvableGenerics()) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Class<?>[] generics = new Class[parameterizedType.getActualTypeArguments().length];
Type[] typeArguments = parameterizedType.getActualTypeArguments();
for (int i = 0; i < typeArguments.length; ++i) {
Type typeArgument = typeArguments[i];
if (typeArgument instanceof TypeVariable) {
ResolvableType resolvedTypeArgument = resolveVariable((TypeVariable) typeArgument, ResolvableType.forClass(contextClass));
if (resolvedTypeArgument != ResolvableType.NONE) {
generics[i] = resolvedTypeArgument.resolve();
} else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
} else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
}
return ResolvableType.forClassWithGenerics(resolvedType.getRawClass(), generics).getType();
}
}
return type;
}
private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) {
ResolvableType resolvedType;
if (contextType.hasGenerics()) {
resolvedType = ResolvableType.forType(typeVariable, contextType);
if (resolvedType.resolve() != null) {
return resolvedType;
}
}
ResolvableType superType = contextType.getSuperType();
if (superType != ResolvableType.NONE) {
resolvedType = resolveVariable(typeVariable, superType);
if (resolvedType.resolve() != null) {
return resolvedType;
}
}
for (ResolvableType ifc : contextType.getInterfaces()) {
resolvedType = resolveVariable(typeVariable, ifc);
if (resolvedType.resolve() != null) {
return resolvedType;
}
}
return ResolvableType.NONE;
}
}
}