@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,所以可以在构造它时指定一下字符集。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为你提供一个简单的登录案例,使用Spring Boot框架和相关注解来实现。 首先,我们需要创建一个名为"UserController"的控制器类。该类将处理用户登录请求,并返回相应的响应。 ```java @Controller public class UserController { @Autowired private UserService userService; @PostMapping("/login") @ResponseBody public ResponseEntity<String> login(@RequestBody UserDto userDto) { User user = userService.login(userDto.getUsername(), userDto.getPassword()); if (user != null) { return new ResponseEntity<>("Login Successful", HttpStatus.OK); } else { return new ResponseEntity<>("Login Failed", HttpStatus.UNAUTHORIZED); } } } ``` 在上面的代码中,我们使用了@Controller注解来标记该类为控制器类,@Autowired来注入UserService实例,@PostMapping注解用于处理HTTP POST请求,@ResponseBody注解用于将响应体直接返回给客户端,而无需使用模型和视图。@RequestBody注解用于将请求体转换为UserDto对象,然后我们使用UserService实例进行用户登录验证,如果用户存在,则返回成功响应,否则返回失败响应。 接下来,我们需要创建一个UserService类,该类将处理用户登录逻辑。 ```java @Service public class UserService { @Autowired private UserRepository userRepository; public User login(String username, String password) { User user = userRepository.findByUsername(username); if (user != null && user.getPassword().equals(password)) { return user; } else { return null; } } } ``` 在上面的代码中,我们使用了@Service注解来标记该类为服务类,@Autowired用于注入UserRepository实例,该类将处理用户登录逻辑。我们首先通过调用UserRepository的findByUsername方法来获取用户实例,然后比较用户密码是否与传递的密码相同。如果是,则返回用户实例,否则返回null。 最后,我们需要创建一个名为"UserDto"的数据传输对象类,该类将用于从请求体中提取用户名和密码。 ```java @Data public class UserDto { private String username; private String password; } ``` 在上面的代码中,我们使用了@Data注解来生成getter和setter方法,以及equals、hashCode和toString方法,这些方法将用于从请求体中提取用户名和密码。 最后,我们需要创建一个名为"UserRepository"的接口,该接口将扩展JpaRepository接口,并提供自定义方法来查询用户实例。 ```java @Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` 在上面的代码中,我们使用了@Repository注解来标记该接口为存储库接口,该接口将扩展JpaRepository接口,并提供自定义方法findByUsername来查询用户实例。 以上就是使用Spring Boot框架和相关注解实现登录案例的全部内容。希望可以对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺平程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值