目录
一、异常场景复现
1、maven
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.13.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- 切面 spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.2</version>
</dependency>
2、java
2-1、Java-aspect
package com.example.temp.apo;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class LogAspect {
@Pointcut("execution(public * com.example.temp.controller..*.*(..))")
public void logPointCut() {
}
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) {
log.info(" |---- 请求体: {}", JSON.toJSONString(joinPoint.getArgs()));
}
@After("logPointCut()")
public void doAfter() {
}
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void afterThrowing(Throwable e) {
}
}
2-2、java-controller
package com.example.temp.controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("file")
public class FileController {
@PostMapping(value = "/upload")
public Object importLicense(@RequestParam("file") MultipartFile file) {
if (file == null || file.isEmpty()) {
return "上传文件不能为空";
}
return null;
}
}
3、触发
二、异常堆栈
2023-07-23 07:03:49.148 [traceId:] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] -175 -Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.alibaba.fastjson.JSONException: write javaBean error, fastjson version 1.2.83, class org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile, fieldName : 0, write javaBean error, fastjson version 1.2.83, class org.springframework.web.multipart.MultipartFileResource, fieldName : resource] with root cause
java.io.FileNotFoundException: MultipartFile resource [file] cannot be resolved to absolute file path
at org.springframework.core.io.AbstractResource.getFile(AbstractResource.java:138)
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 com.alibaba.fastjson.util.FieldInfo.get(FieldInfo.java:571)
at com.alibaba.fastjson.serializer.FieldSerializer.getPropertyValueDirect(FieldSerializer.java:143)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:284)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:154)
at com.alibaba.fastjson.serializer.FieldSerializer.writeValue(FieldSerializer.java:318)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:472)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:154)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:360)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:338)
at com.alibaba.fastjson.serializer.ObjectArrayCodec.write(ObjectArrayCodec.java:118)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:793)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:731)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:688)
at com.example.temp.apo.LogAspect.doBefore(LogAspect.java:20)
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)
三、堆栈分析
- nested exception is com.alibaba.fastjson.JSONException: write javaBean error ,提示到fastjson序列化javaBean时出错,且后面提示class org.springframework.web.multipart.MultipartFileResource,指出MultipartFile相关。
- **at com.example.temp.apo.LogAspect.doBefore(LogAspect.java:20)**指明了异常所在代码位置,该行代码为:
log.info(" |---- 请求体: {}", JSON.toJSONString(joinPoint.getArgs()));
四、问题定位
基本可以断定,异常源自于JSON.toJSONString(joinPoint.getArgs()),该语句目的是为了打印接口参数信息,但【上传】接口的参数为MultipartFile file,导致fastjson序列化异常。
五、问题解决
1、思路分析
由于日志切面是公共切面,去除**JSON.toJSONString(joinPoint.getArgs())参数打印信息,属于因噎废食下下策,那么只能从【上传】文件接口入手了。
既然fastjson与MultipartFile不合,那么我们可以考虑将MultipartFile封装入一个Po对象中,并在MultipartFile属性字段上加上注解@JSONField(serialize = false)**这样一来,对于日志切面而言,【上传】
接口的参数就不是MultipartFile,而是一个自定义的普通java对象,且MultipartFile不必序列化。
2、代码实现
2-1、controller
@PostMapping(value = "/upload")
public Object importLicense(FileParam fileParam) {
if (fileParam == null
|| fileParam.getFile() == null
|| fileParam.getFile().isEmpty()) {
return "上传文件不能为空";
}
return null;
}
2-2、FileParam
package com.example.temp.param;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class FileParam {
@JSONField(serialize = false)
private MultipartFile file;
private String Context;
}