携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情
@RequestBody
显然这个注解用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter
或者自定义的HttpMessageConverter
将请求的 body 中的 json 字符串转换为 java 对象。
那到底是如何帮我们转换为java的类的呢?
起初我是没有,getter和setter的,读取出来就是null,后来我自己随便测试,就发现,其实,我们转化为java的类依靠的就是 无参构造+setter
我在SpringMVC里面阅读过对应的源码,因为我们使用的是注解,但是亘古不变的是找DispatcherServlet
类中的doDispatch
进入mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
这行,
然后看你底子了,我们找到,getMethodArgumentValues
这个方法
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取参数信息
MethodParameter[] parameters = getMethodParameters();
// 判断参数是否为空
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
// 创建需要获得的参数数组
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// 是否提供相应参数的注解,一般是不提供的 为null
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 拿到对应的参数解析器
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
我们就是从这个方法里面获得数据的,而且,只要你是在controller中的方法中的参数,只要是正确的,都能解析
前面先找参数类型,然后再遍历数组尝试一个一个找到参数信息,findProvidedArgument(parameter, providedArgs);
就是找到提供的参数,但是我们看里面
protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
if (!ObjectUtils.isEmpty(providedArgs)) {
for (Object providedArg : providedArgs) {
if (parameter.getParameterType().isInstance(providedArg)) {
return providedArg;
}
}
}
return null;
}
由于我们providedArgs
是空的,是前面传参没有获取到,所以就返回了,但是这不重要,我们后面还有兜底的解决办法,if (!this.resolvers.supportsParameter(parameter))
这里面supportsParameter
好眼熟啊,没错,我们在SpringMVC里面也看到过,这说明我们学习基础的时候就不能马虎。
但是这次我们用的是Json,参数是能解析的,但是不用进去存储解析器到缓存了。
回到getMethodArgumentValues
方法中,
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
这个是最重要的一步,获取前端传过来的数据,进去看
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
进getArgumentResolver
看,其实就是查找可以解析参数的解析器
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
其实,这个方法之前我们执行过了,debug的一定知道,supportsParameter
方法里面判断是否不为null就调用这个方法,正是因为前面我们执行过了,所以缓存中已经有数据了,直接return了
再到resolveArgument
方法
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
核心了,看readWithMessageConverters
方法
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// 获取 request 请求
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
再进入readWithMessageConverters
的重载
有点长
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
// 获得数据类型 application/json;charset=UTF-8
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
// 获得controller类
Class<?> contextClass = parameter.getContainingClass();
// 获得我们参数的类,也就是pojo,就是前端传的
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
// 获得http请求类型
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message = null;
try {
// 抽取一下request请求中的头和体的输入信息
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// 遍历解析器,尝试转化为java类
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);
}
finally {
if (message != null && message.hasBody()) {
closeStreamIfNecessary(message.getBody());
}
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType,
getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read "" + selectedContentType + "" to [" + formatted + "]";
});
return body;
}
前面是一些基础操作,我们在代码中注释了,在这详细看一下遍历解析器的操作
先获得解析器的类,是不是GenericHttpMessageConverter
的子类,如果不是就将genericConverter置为空,是就强转为GenericHttpMessageConverter
,有些解析器这里不是,所以是空的,后面的判断也是false的,所以一直在遍历,直到出现了这个类org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
,很熟悉,就是Jackon
的解析器
所以执行了这个方法genericConverter.canRead(targetType, contextClass, contentType)
,就是用来判断是否可读
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
// 判断 application/json 是否支持
if (!canRead(mediaType)) {
return false;
}
// 获得对应的java类型
JavaType javaType = getJavaType(type, contextClass);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), mediaType);
if (objectMapper == null) {
return false;
}
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// 看我们获得的 objectMapper 是否可以反序列化
if (objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
logWarningIfNecessary(javaType, causeRef.get());
return false;
}
到这里是可以进行操作的,返回了true,回到for循环中,判断了message是否有body,前端传了,肯定有,这个时候我们看里面的信息发现是inputBuffer,到下一步
HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
前置处理
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
if (advice.supports(parameter, targetType, converterType)) {
request = advice.beforeBodyRead(request, parameter, targetType, converterType);
}
}
return request;
}
advice.supports()
方法
一个 RequestBodyAdvice 实现,增加了对在 Spring MVC @HttpEntity 或 @RequestBody 方法参数上声明的 Jackson 的 @JsonView
注释的支持。
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
methodParameter.getParameterAnnotation(JsonView.class) != null);
}
这个不是我们的重点,但是知道一下还有这么一个注解可以过滤掉不想传给前端的属性,好,出来,得到过滤的变量 msgToUse,进入genericConverter.read(targetType, contextClass, msgToUse)
方法
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(type, contextClass);
return readJavaType(javaType, inputMessage);
}
读取java的类型readJavaType
,其中参数inputMessage
就是msgToUse
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = getCharset(contentType);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);
boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
"UTF-16".equals(charset.name()) ||
"UTF-32".equals(charset.name());
try {
// 读取流中的内容
InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);
if (isUnicode) {
return objectReader.readValue(inputStream);
}
else {
Reader reader = new InputStreamReader(inputStream, charset);
return objectReader.readValue(reader);
}
}
}
if (isUnicode) {
// 读取里面的值
return objectMapper.readValue(inputStream, javaType);
}
else {
Reader reader = new InputStreamReader(inputStream, charset);
return objectMapper.readValue(reader, javaType);
}
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
}
}
return objectMapper.readValue(inputStream, javaType);
就是去内部读取值了,最后直接返回
body
此时就是对应的java类,然后再后置处理一下,和前置处理感觉差不多,然后就能返回到args[i]
中了
为什么需要无参构造?
我们在刚开始的时候就做出测试需要无参构造+setter,上面源码没有体现啊,那是因为在json序列化过程中,我debug好久最后定位到BeanDeserializer
这个类中的deserializeFromObject
方法
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
...
if (_nonStandardCreation) { // <--- 看这里
if (_unwrappedPropertyHandler != null) {
return deserializeWithUnwrapped(p, ctxt);
}
if (_externalTypeIdHandler != null) {
return deserializeWithExternalTypeId(p, ctxt);
}
Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
/* 27-May-2014, tatu: I don't think view processing would work
* at this point, so commenting it out; but leaving in place
* just in case I forgot something fundamental...
*/
/*
if (_needViewProcesing) {
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, bean, view);
}
}
*/
return bean;
}
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
...
return bean;
}
_nonStandardCreation
最重要的就是这个参数,是否有无参构造
我们定位到溢出代码,是BeanDeserializerBase
的构造方法里面
红方框里面的值为true
看里面代码
public boolean canCreateUsingDefault() {
return (_defaultCreator != null);
}
是StdValueInstantiator
类实现的,_defaultCreator
的值是什么?去看看
/**
* Default (no-argument) constructor to use for instantiation
* (with {@link #createUsingDefault})
*/
protected AnnotatedWithParams _defaultCreator;
注释的意思就是:用于实例化的默认(无参数)构造函数(使用 createUsingDefault)
所以代码里面明码标价了,如果不是无参构造后面就执行不了。