mirror of https://github.com/alibaba/fastjson2.git
refactor(extension): 重构 FastJsonHttpMessageConverter 以提高性能 (#3754)
* refactor(extension): 重构 FastJsonHttpMessageConverter 以提高性能 - 新增 fastRead 方法,用于高效读取请求体 - 优化内存分配,避免不必要的数组拷贝 - 使用 JSONReader.Context 和 JSONWriter.Context 缓存避免重复构造 Context 的性能开销 - 增加对大数组长度的处理逻辑,防止内存溢出 * style(extension): 删除 FastJsonConfig 类中的冗余空行 * refactor(extension-spring5): 调整请求体初始化容量的变量命名和访问修饰符,避免非预期的修改 * refactor(extension-spring5): 调整部分内部方法为 private,限制外部访问,加强封装性 * 调整:JSON 请求主体数据初始容量为 8KB
This commit is contained in:
parent
007746de07
commit
e99c4ad2b2
|
@ -1699,7 +1699,6 @@ public interface JSON {
|
|||
* @return {@link T} or {@code null}
|
||||
* @throws JSONException If a parsing error occurs
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T parseObject(
|
||||
byte[] bytes,
|
||||
Type type,
|
||||
|
@ -1719,6 +1718,26 @@ public interface JSON {
|
|||
);
|
||||
context.setDateFormat(format);
|
||||
|
||||
return parseObject(bytes, type, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the json string as {@link T}. Returns
|
||||
* {@code null} if received {@link String} is {@code null} or empty.
|
||||
*
|
||||
* @param bytes the specified UTF8 text to be parsed
|
||||
* @param type the specified actual type
|
||||
* @param context the specified custom context
|
||||
* @return {@link T} or {@code null}
|
||||
* @throws JSONException If a parsing error occurs
|
||||
* @throws NullPointerException If received context is null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T parseObject(byte[] bytes, Type type, JSONReader.Context context) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean fieldBased = (context.features & JSONReader.Feature.FieldBased.mask) != 0;
|
||||
ObjectReader<T> objectReader = context.provider.getObjectReader(type, fieldBased);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ 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.
|
||||
|
@ -93,24 +94,96 @@ public class FastJsonHttpMessageConverter
|
|||
return readType(getType(clazz, null), inputMessage);
|
||||
}
|
||||
|
||||
private Object readType(Type type, HttpInputMessage inputMessage) {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
InputStream in = inputMessage.getBody();
|
||||
/** Default initialization capacity when content-length is not specified */
|
||||
private static int REQUEST_BODY_INITIAL_CAPACITY = 8192;
|
||||
|
||||
byte[] buf = new byte[1024 * 64];
|
||||
for (; ; ) {
|
||||
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);
|
||||
if (len == -1) {
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
baos.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
byte[] bytes = baos.toByteArray();
|
||||
// see jdk.internal.util.ArraysSupport.SOFT_MAX_ARRAY_LENGTH
|
||||
private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
|
||||
|
||||
return JSON.parseObject(bytes, type, config.getDateFormat(), config.getReaderFilters(), config.getReaderFeatures());
|
||||
// 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) {
|
||||
|
@ -138,10 +211,7 @@ public class FastJsonHttpMessageConverter
|
|||
}
|
||||
|
||||
contentLength = JSON.writeTo(
|
||||
baos, object,
|
||||
config.getDateFormat(),
|
||||
config.getWriterFilters(),
|
||||
config.getWriterFeatures()
|
||||
baos, object, config.writerContext()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.alibaba.fastjson2.support.config;
|
||||
|
||||
import com.alibaba.fastjson2.JSONB;
|
||||
import com.alibaba.fastjson2.JSONFactory;
|
||||
import com.alibaba.fastjson2.JSONReader;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.fastjson2.SymbolTable;
|
||||
|
@ -64,6 +65,11 @@ public class FastJsonConfig {
|
|||
*/
|
||||
private SymbolTable symbolTable;
|
||||
|
||||
/** internal cache for JSONReader.Context, avoid repeatedly constructing new objects */
|
||||
private transient JSONReader.Context readerContext;
|
||||
/** internal cache for JSONWriter.Context, avoid repeatedly constructing new objects */
|
||||
private transient JSONWriter.Context writerContext;
|
||||
|
||||
/**
|
||||
* init param.
|
||||
*/
|
||||
|
@ -96,6 +102,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
this.clearContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,6 +121,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setDateFormat(String dateFormat) {
|
||||
this.dateFormat = dateFormat;
|
||||
this.clearContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,6 +140,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setReaderFeatures(JSONReader.Feature... readerFeatures) {
|
||||
this.readerFeatures = readerFeatures;
|
||||
this.readerContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,6 +159,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setWriterFeatures(JSONWriter.Feature... writerFeatures) {
|
||||
this.writerFeatures = writerFeatures;
|
||||
this.writerContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,6 +178,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setReaderFilters(Filter... readerFilters) {
|
||||
this.readerFilters = readerFilters;
|
||||
this.readerContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -186,6 +197,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setWriterFilters(Filter... writerFilters) {
|
||||
this.writerFilters = writerFilters;
|
||||
this.writerContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,6 +236,7 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setJSONB(boolean jsonb) {
|
||||
this.jsonb = jsonb;
|
||||
this.clearContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,5 +257,40 @@ public class FastJsonConfig {
|
|||
*/
|
||||
public void setSymbolTable(String... names) {
|
||||
this.symbolTable = JSONB.symbolTable(names);
|
||||
this.clearContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear internal caches of JSONReader.Context and JSONWriter.Context
|
||||
*/
|
||||
public void clearContext() {
|
||||
readerContext = null;
|
||||
writerContext = null;
|
||||
}
|
||||
|
||||
public JSONReader.Context readerContext() {
|
||||
JSONReader.Context context = readerContext;
|
||||
if (context == null) { // Concurrency may occur, but it will not cause any problems
|
||||
context = new JSONReader.Context(JSONFactory.getDefaultObjectReaderProvider(), jsonb ? symbolTable : null, readerFilters, readerFeatures);
|
||||
context.setDateFormat(dateFormat);
|
||||
this.readerContext = context;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public JSONWriter.Context writerContext() {
|
||||
JSONWriter.Context context = writerContext;
|
||||
if (context == null) {
|
||||
context = new JSONWriter.Context(dateFormat, writerFeatures);
|
||||
if (dateFormat != null && !dateFormat.isEmpty()) {
|
||||
context.setDateFormat(dateFormat);
|
||||
}
|
||||
// symbolTable is only in JSONWriter class
|
||||
if (writerFilters != null && writerFilters.length > 0) {
|
||||
context.configFilter(writerFilters);
|
||||
}
|
||||
this.writerContext = context;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue