使用@RequestPart注解的错误解决

最近使用spring boot编写api,使用swagger显示并测试,执行的时候其中某一个api在解析multipart/form-data类型的时候发生了如下错误:
Content type 'application/octet-stream' not supported
大概的api代码如下:

@PostMapping(value = "/edit")
public void edit(@RequestPart(value = "info", required = false) List<UserEntity> users,@RequestParam(value = "image", required = false)MultipartFile file) {
    //to do
}

为什么会发生这种错误呢?首先可以肯定的是并没有设置application/octet-stream这种content-type,那么错误的来源是哪儿呢?通过追踪错误的堆栈信息,找到类AbstractMessageConverterMethodArgumentResolver中的方法readWithMessageConverters:

@SuppressWarnings("unchecked")
	@Nullable
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		MediaType contentType;
		boolean noContentType = false;
		try {
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
			noContentType = true;
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}

		Class<?> contextClass = parameter.getContainingClass();
		Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class<T>) resolvableType.resolve();
		}

		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message;
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					if (message.hasBody()) {
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}

		if (body == NO_VALUE) {
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
				return null;
			}
			throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
		}

		MediaType selectedContentType = contentType;
		Object theBody = body;
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
			return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
		});

		return body;
	}

该方法在开始的时候取content-type,如果找不到的话就设置为application/octet-stream,然后根据content-type找到可以处理该类型的messageConverter,获取body信息。现在我们可以找到为什么会抛出这样的错误信息了,因为没有定义application/octet-stream相关的messageConverter。
所以我们可以通过自定义一个messageConverter解决这个问题,添加的converter的代码如下:

@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    protected MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    protected boolean canWrite(MediaType mediaType) {
        return false;
    }
}

问题虽然解决了,但是,为什么content-type会是null呢?
我们使用swagger调用的api,查看swagger生成的curl命令发现,请求中image这个参数指定了content-type,但是info却并没有:
curl -X POST "http://127.0.0.1:8080/api/v1.0/edit" -H "accept: */*" -H "Content-Type: multipart/form-data" -F "[email protected];type=image/jpeg" -F "info=[]"
然后如果给info添加上content-type,即修改为curl -X POST "http://127.0.0.1:8080/api/v1.0/edit" -H "accept: */*" -H "Content-Type: multipart/form-data" -F "[email protected];type=image/jpeg" -F "info=[];type=application/json"不添加messageConverter也可以正常运行,所以这应该是swagger的问题,暂时没有找到配置swagger的好办法,所以干脆选择了添加messageConverter来解决上述问题,mark一下。

你可能感兴趣的:(Java)