alibaba / alibaba/fastjson2

[BUG](2.0.43版本)@JSONField的属性serializeUsing中针对MultipartFile指定ObjectWriter不生效

Open
#2,271 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
4.4k
Forks
613
Avg merge
1d 22h
Merged PRs (30d)
6

Description

### 问题描述
在`@JSONField`注解中的属性`serializeUsing`中针对`org.springframework.web.multipart.MultipartFile`指定自定义的`ObjectWriter`在调用`JSON.toJSONString()`时没有生效

### 版本信息
```xml

com.alibaba.fastjson2
fastjson2
2.0.43

```

### 重现步骤
首先问题是在序列化`org.springframework.web.multipart.MultipartFile`时出现的, 如果不指定`ObjectWriter`, 则会在调用`JSON.toJSONString()`时出现`Caused by: java.io.FileNotFoundException: D:\temp\work\Tomcat\localhost\ROOT\upload_2ad6d63e_24e3_4352_b778_07bb9100c348_00000000.tmp (系统找不到指定的文件。)`这样的异常, 看样子是序列化时会去尝试获取这个文件/资源(有点疑惑为什么序列化`MultipartFile`时会需要读取文件?), 于是我实现了一个`ObjectWriter`, 并在`@JSONField`注解中的属性`serializeUsing`中指定了它:
```java
@Data
public class MultipartDto implements Serializable {
/**
* 单个文件
*/
@JSONField(serializeUsing = MultipartSerializer.class)
private MultipartFile file;

public static class MultipartSerializer implements ObjectWriter {

@Override
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
System.out.println("--invoke--");
// ...写入
}
}
}
```
但是发现并没有调用这个`write`方法, 依然抛出异常(见下方日志输出);
于是我尝试对整个类进行自定义序列化:
```java
@Data
public class MultipartDto implements Serializable {
/**
* 单个文件
*/
private MultipartFile file;

static {
JSON.register(MultipartDto.class, new MultipartSerializer());
}

public static class MultipartSerializer implements ObjectWriter {

@Override
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
JSONObject.toJSONString(object);
// ...写入
}
}
}
```
结果在这一步` JSONObject.toJSONString(object);`出现了堆栈溢出的异常(见下方日志输出);

而其他类型指定`ObjectWriter`是可以适配到的, 例如这样子:
```java
@Data
public static class SerializeTestClass {
@JSONField(serializeUsing = NormalSerializer.class)
private Integer normalType;

public static class NormalSerializer implements ObjectWriter {

@Override
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
System.out.println("invoke");
}
}
}
```

### 期待的正确结果
希望可以正确识别`MultipartFile`类型并适用正确的`ObjectWriter`; 如果是我的使用有错误, 希望可以指导正确的使用方式

### 相关日志输出
序列化`MultipartFile`时的异常:
```java
Exception in thread "note-task-1" org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='payload', mode=IN, javaType=interface java.util.Map, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #10 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.io.FileNotFoundException: D:\temp\work\Tomcat\localhost\ROOT\upload_2ad6d63e_24e3_4352_b778_07bb9100c348_00000000.tmp (系统找不到指定的文件。)
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
at com.sun.proxy.$Proxy77.insert(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:59)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy82.insert(Unknown Source)
at com.baomidou.mybatisplus.extension.service.IService.save(IService.java:63)
at cn.rhaegal.service.impl.AccessLogServiceImpl.save(AccessLogServiceImpl.java:34)
at cn.rhaegal.service.impl.AccessLogServiceImpl.save(AccessLogServiceImpl.java:16)
at cn.rhaegal.service.impl.AccessLogServiceImpl$$FastClassBySpringCGLIB$$66cd15d1.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy.invokeMethod(CglibAopProxy.java:386)
at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:704)
at cn.rhaegal.service.impl.AccessLogServiceImpl$$EnhancerBySpringCGLIB$$baef5677.save()
at cn.rhaegal.aspect.AccessLogAspect.lambda$doAround$0(AccessLogAspect.java:56)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
Caused by: org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='payload', mode=IN, javaType=interface java.util.Map, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #10 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.io.FileNotFoundException: D:\temp\work\Tomcat\localhost\ROOT\upload_2ad6d63e_24e3_4352_b778_07bb9100c348_00000000.tmp (系统找不到指定的文件。)
at com.baomidou.mybatisplus.core.MybatisParameterHandler.setParameters(MybatisParameterHandler.java:236)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:94)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:64)
at com.sun.proxy.$Proxy131.parameterize(Unknown Source)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:88)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)
at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:106)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:62)
at com.sun.proxy.$Proxy130.update(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:194)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:181)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
... 19 more
Caused by: org.apache.ibatis.type.TypeException: Error setting non null for parameter #10 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.io.FileNotFoundException: D:\temp\work\Tomcat\localhost\ROOT\upload_2ad6d63e_24e3_4352_b778_07bb9100c348_00000000.tmp (系统找不到指定的文件。)
at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:75)
at com.baomidou.mybatisplus.core.MybatisParameterHandler.setParameters(MybatisParameterHandler.java:234)
... 46 more
Caused by: java.io.FileNotFoundException: D:\temp\work\Tomcat\localhost\ROOT\upload_2ad6d63e_24e3_4352_b778_07bb9100c348_00000000.tmp (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.(FileInputStream.java:138)
at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:198)
at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getBytes(StandardMultipartHttpServletRequest.java:246)
at com.alibaba.fastjson2.writer.FieldWriterObjectFuncFinal.getFieldValue(FieldWriterObjectFuncFinal.java:38)
at com.alibaba.fastjson2.writer.FieldWriterObjectFinal.write(FieldWriterObjectFinal.java:52)
at com.alibaba.fastjson2.writer.ObjectWriter8.write(ObjectWriter8.java:83)
at com.alibaba.fastjson2.writer.FieldWriterObject.write(FieldWriterObject.java:381)
at com.alibaba.fastjson2.writer.ObjectWriter1.write(ObjectWriter1.java:68)
at com.alibaba.fastjson2.writer.ObjectWriterImplMap.write(ObjectWriterImplMap.java:548)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2756)
at cn.rhaegal.handler.JsonMapTypeHandler.setNonNullParameter(JsonMapTypeHandler.java:24)
at cn.rhaegal.handler.JsonMapTypeHandler.setNonNullParameter(JsonMapTypeHandler.java:19)
at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:73)
... 47 more
```
堆栈溢出的错误日志:
```java
Exception in thread "note-task-1" java.lang.StackOverflowError
at com.alibaba.fastjson2.JSONWriterUTF16.(JSONWriterUTF16.java:56)
at com.alibaba.fastjson2.JSONWriterUTF16JDK8UF.(JSONWriterUTF16JDK8UF.java:12)
at com.alibaba.fastjson2.JSONWriter.of(JSONWriter.java:549)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2805)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
at cn.rhaegal.domain.dto.MultipartDto$MultipartSerializer.write(MultipartDto.java:32)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2815)
at com.alibaba.fastjson2.JSONObject.toJSONString(JSONObject.java:1144)
// ......后面都是重复
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the MultipartFile case through JSON.toJSONString() with @JSONField(serializeUsing = MultipartSerializer.class), then inspect FieldWriterObjectFuncFinal.getFieldValue and FieldWriterObjectFinal.write from the reported stack trace. Compare this path with the working Integer ObjectWriter example. Done means the annotated MultipartFile writer is invoked and serialization no longer attempts the failing file access or recurses into StackOverflowError.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.