@RequestBody注解和@ResponseBody注解实现原理源码剖析+自定义消息转换器

@RequestBody:将请求中的参数json转换为对象
@ResponseBody:将对象转换为json响应给浏览器
@RequestBody和@ResponseBody的解析都发生在SpringMVC处理请求的第四步HandlerAdapter执行Handler逻辑并返回ModelAndView,此处只贴主要流程代码,全流程代码及处理请求的其他步骤请参考上篇文章:SpringMVC请求流程处理源码剖析

第四部主要做了三件事情:

  1. 遍历handler形参,根据不同形参类型以及是否存在@RequestBody、@RequestParam等和注解获取不同的参数解析器,利用参数解析器在请求中获取实参
  2. 反射调用Handler业务方法,获取返回值
  3. 根据返回值类型及方法是否存在@ResponseBody获取不同的返回值处理器,利用返回值处理器封装ModelAndView

一、@RequestBody源码分析(完成第四步骤的第一个事情)

首先进入获取handler实参的方法InvocableHandlerMethod#getMethodArgumentValues

	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    
		// 获取当前handler方法的形参
		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
    
    
			return EMPTY_ARGS;
		}
		// 创建新数组,用于封装handler被调用时的实参(不同类型的形参获取方式不一样)
		Object[] args = new Object[parameters.length];
		// 遍历形参
		for (int i = 0; i < parameters.length; i++) {
    
    
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			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;
	}

进入resolveArgument方法,获取对应的参数解析器并获取实参

HandlerMethodArgumentResolverComposite.class
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    
		// 根据不同类型的参数获取不同的参数解析器。如:String的参数解析器AbstractNamedValueMethodArgumentResolver、Map的参数解析器MapMethodProcessor、带有@RequestBody注解的参数解析器RequestResponseBodyMethodProcessor等
		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

HandlerMethodArgumentResolverComposite.class
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
    
    
			// 遍历参数处理器集合根据参数获取合适的参数处理器(适配器模式)处理@RequestBody的参数处理器是RequestResponseBodyMethodProcessor
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    
    
				if (resolver.supportsParameter(parameter)) {
    
    
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

进入supportsParameter,判断参数中是否带有@RequestBody注解如果有则此参数用RequestResponseBodyMethodProcessor解析器解析。

RequestResponseBodyMethodProcessor.class
public boolean supportsParameter(MethodParameter parameter) {
    
    
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

拿到参数解析器后进入RequestResponseBodyMethodProcessor#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);
	}

进入AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters

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

		MediaType contentType;
		boolean noContentType = false;
		try {
    
    
			// 获取contentType application/json
			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);
			// 遍历消息转换器,可以自定义消息转换器只要注入到HandlerAdapter的messageConverters属性
			for (HttpMessageConverter<?> converter : this.messageConverters) {
    
    
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				// 过滤合适的消息转换器canRead的底层判断逻辑是(该转换器是否包含请求中Content-Type指定的MediaType和改转换器的supports方法是否为true共同决定是否使用此消息转换器解析参数)
				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);
						// 使用消息转换器转换消息read(targetClass, msgToUse) targetClass:目标参数类型、msgToUse:包含json的输入流,red的底层方法是readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)方法用于将数据流转换为指定对象,可在自定义消息转换器重写
						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;
	}

二、@ResponseBody源码分析(完成第四步骤的第三个事情)

反射执行完handler方法拿到返回之后进入HandlerMethodReturnValueHandlerComposite#handleReturnValue方法处理返回结果值

HandlerMethodReturnValueHandlerComposite.class
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
    
		// 根据不同结果类型获取不同的结果处理器如:String类型的结果处理器ViewNameMethodReturnValueHandler、ModelAndView类型的
		// 结果处理器ModelAndViewMethodReturnValueHandler、带有@ResponseBody是的结果处理
		//RequestResponseBodyMethodProcessor等
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
    
    
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		// 调用结果处理器完成handler返回值与ModelAndViewContainer的关系组装
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

进入RequestResponseBodyMethodProcessor#handleReturnValue

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
    

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		// 使用消息转换器写入响应Response
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

进入AbstractMessageConverterMethodProcessor#writeWithMessageConverters

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
    

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {
    
    
			// 返回值对象
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
    
    
			body = value;
			valueType = getReturnValueType(body, returnType);
			// 返回值类型
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {
    
    
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
    
    
				Resource resource = (Resource) value;
				try {
    
    
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
    
    
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

		MediaType selectedMediaType = null;
		// 获取ContentType
		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
    
    
			if (logger.isDebugEnabled()) {
    
    
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
    
    
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
    
    
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
    
    
				for (MediaType producibleType : producibleTypes) {
    
    
					if (requestedType.isCompatibleWith(producibleType)) {
    
    
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
    
    
				if (body != null) {
    
    
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				if (logger.isDebugEnabled()) {
    
    
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				return;
			}

			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

			for (MediaType mediaType : mediaTypesToUse) {
    
    
				if (mediaType.isConcrete()) {
    
    
					selectedMediaType = mediaType;
					break;
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
    
    
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
    
    
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}

		if (selectedMediaType != null) {
    
    
			selectedMediaType = selectedMediaType.removeQualityValue();
			// 遍历消息转换器
			for (HttpMessageConverter<?> converter : this.messageConverters) {
    
    
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				// 过滤合适的消息转换器canWrite的底层判断逻辑是 (该转换器是否包含请求中Content-Type指定的MediaType和改转换器的supports方法是否为true共同决定是否使用此消息转换器解析参数)
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
    
    

					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
    
    
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
    
    
							// 调用消息转换器的write方法底层调用的是writeInternal(T t, HttpOutputMessage outputMessage)
							//用于将对象转换为指定格式文本写入Response,outputMessage为响应输出流,可在自定义消息转换器重写
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
    
    
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
    
    
						if (logger.isDebugEnabled()) {
    
    
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}

		if (body != null) {
    
    
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

当使用RequestResponseBodyMethodProcessor处理器处理结果值是步骤四返回的ModelAndView为null,当ModelAndView为null时不执行SpringMVC处理请求的步骤七视图渲染和请求转发过程,所以最终接口响应为消息转换器写入Response输出流中的文本。

三、自定义消息转换器

实现效果:
1.自定义消息转换器MyMessageConverter实现HttpMessageConverter接口专门处理User类型的参数
2.可以name#张三,age#20 转换为User作为参数
3.可以将User转换为 “获取到名称为张三,年龄20岁的人”

1.构建demo工程目录:
在这里插入图片描述

DemoController.class

@Controller
@RequestMapping("/demo")
public class DemoController  {
    
    

    @RequestMapping("/handle01")
	@ResponseBody
    public User handle01(@RequestBody User user) {
    
    
		System.out.println(user);
        return user;
    }
}

User.class

public class User {
    
    

	private String name;
	private  Integer age;

	public String getName() {
    
    
		return name;
	}

	public void setName(String name) {
    
    
		this.name = name;
	}

	public Integer getAge() {
    
    
		return age;
	}

	public void setAge(Integer age) {
    
    
		this.age = age;
	}

	@Override
	public String toString() {
    
    
		return "User{" +
				"name='" + name + '\'' +
				", age=" + age +
				'}';
	}
}

2.自定义消息转换器,(该转换器是否包含请求中Content-Type指定的MediaType和改转换器的supports方法是否为true共同决定是否使用此消息转换器解析参数)

/**
 * 自定义消息转换器
 * 注:该转换器是否包含请求中Content-Type指定的MediaType和改转换器的supports方法是否为true共同决定是否使用此消息转换器解析参数
 */
public class MyMessageConverter extends AbstractHttpMessageConverter<User> {
    
    


	public MyMessageConverter() {
    
    
		// 新建一个我们自定义的媒体类型application/xxx-lago
		super(new MediaType("application", "xxx-lago", Charset.forName("UTF-8")));
	}


	@Override
	protected boolean supports(Class<?> clazz) {
    
    
		//表明只处理User类型的参数。
		return User.class.isAssignableFrom(clazz);
	}

	//重写readlntenal 方法,处理请求的数据。
	@Override
	protected User readInternal(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    
    
		String str = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("UTF-8"));
		// 解析参数
		String[] split = str.split(",");
		String name = split[0].split("#")[1];
		String age = split[1].split("#")[1];
		User user=new User();
		user.setName(name);
		user.setAge(Integer.parseInt(age));
		return user;
	}

	//重写writeInternal ,处理如何输出数据到response。
	@Override
	protected void writeInternal(User user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    
    
		String outStr="获取到名称为"+user.getName()+",年龄"+user.getAge()+"岁的人";
		outputMessage.getBody().write(outStr.getBytes());
	}
}

3.将自定义的消息转换器注入到HandlerAdapter中

在这里插入图片描述

工程全部构建完毕,下面进行测试
请求postman:
在这里插入图片描述
在这里插入图片描述

响应postman:
在这里插入图片描述
最后科普一下默认的转换器HttpMessageConverter有:

ByteArrayHttpMessageConverter:支持返回值类型为byte[],content-type为application/octet-stream,/

StringHttpMessageConverter:支持的返回值类型为String,
ResourceHttpMessageConverter:支持的返回值类型为Resource,content-type为 /

SourceHttpMessageConverter:支持的返回值类型为DomSource,SAXSource,Source,StreamSource,content-type为application/xml,text/xml,application/*+xml

MappingJacksonHttpMessageConverter:判断返回值能否被格式化成json,content-type为 application/json,application/*+json

AllEncompassingFormHttpMessageConverter:支持的返回值类型为MultiValueMap,content-type为application/x-www-form-urlencoded,multipart/form-data

HttpMessageConverter主要针对那些不会返回view视图的response。其中StringHttpMessageConverter有两个构造函数。当你没有给它指定字符集时,使用默认的ISO-8859-1,这便是造成乱码的一个原因,由于我们经常使用utf-8,所以可以在构造它时指定一下字符集。

猜你喜欢

转载自blog.csdn.net/yangxiaofei_java/article/details/113100424