The Spring container gets an HttpServletRequest object when we go into the DispatcherServlet, but we often use RequestBody when we use controller layer methods, Annotations such as RequestHeader encapsulate the object into a more manageable object type. So what transformation logic goes through when we use annotations like RequestBody? Today we will analyze it.

Parameter analytic logic

First, find where the code is wrapped and go to the invokeForRequest method of InvocableHandlerMethod (the invocation chain can be found by itself, the DEBUG function of IDEA is convenient).

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... ProvidedArgs) throws Exception {// Args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {
			logger.trace("Arguments: "+ Arrays.toString(args)); } // Execute the controller methodreturn doInvoke(args); } protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; .if(this. ArgumentResolvers. SupportsParameter (parameter)) {try {/ / analytical method parameters one by one the args = [I] this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory);continue; }...returnargs; } public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @ Nullable WebDataBinderFactory binderFactory) throws the Exception {/ / get the corresponding parameters of the parser HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {
			throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]"); } // Perform parsing logicreturn resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}

	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for(HandlerMethodArgumentResolver methodArgumentResolver: enclosing argumentResolvers) {/ / traverse the parser, supportsParameter method returnstrueYou canif (methodArgumentResolver.supportsParameter(parameter)) {
					result = methodArgumentResolver;
					this.argumentResolverCache.put(parameter, result);
					break; }}}return result;
	}
Copy the code

The logic of the whole parse is fairly clear:

  1. Gets the parameter types required by the method, parsing them one by one
  2. Call the supportsParameter method of the parser to determine whether the parameter types are supported
  3. Call the resolveArgument method of the parser to resolve the arguments

Parameter parsing demonstration

Here we’ll use the most commonly used RequestHeader and RequestBody to analyze the parsing process.

The first is RequestHeader

For RequestHeaderMethodArgumentResolver RequestHeader corresponding the parser.

First look at the supportsParameter method:

Public Boolean supportsParameter(MethodParameter parameter) {// Check whether the parameter is the RequestHeader annotation flag and the type is not mapreturn(parameter.hasParameterAnnotation(RequestHeader.class) && ! Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())); }Copy the code

Next look at the resolveArgument method, which is in its parent class:

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// Obtain the annotation information NamedValueInfo NamedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); Object resolvedName = resolveStringValue(namedValueInfo.name);if(resolvedName == null) { ... } arg = resolveName(resolvedname.tostring (), nestedParameter, webRequest);if (arg == null) {
			...
		}

		if(binderFactory ! = null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); Try {/ / to deal with the problem of type conversion, arg. = binder convertIfNecessary (arg, parameter getParameterType (), the parameter). }... } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;
}

Copy the code

The logic is clear. The key code is in the resolveName method.

	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		String[] headerValues = request.getHeaderValues(name);
		if(headerValues ! = null) {return (headerValues.length == 1 ? headerValues[0] : headerValues);
		}
		else {
			returnnull; }}Copy the code

So here you can see the process of getting it.

Next look at the RequestBody

For RequestResponseBodyMethodProcessor RequestBody annotation corresponding the parser

First, look at the supportsParameter method:

Public Boolean supportsParameter(MethodParameter parameter) {// Check whether there is a RequestBody annotationreturn parameter.hasParameterAnnotation(RequestBody.class);
	}
Copy the code

Next look at the resolveArgument method:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); // Parse the request body of the incoming request 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 necessary; . }... }return adaptArgumentIfNecessary(arg, parameter);
	}
Copy the code

The body of the request is parsed first, and the result is validated second, so we have the RequestBody object. This place to parse and verify the relevant logic, we can do some specific articles can be viewed :SpringBoot pit diary – a non-null check caused by the bug


At this point, the logic for parameter parsing is pretty much the same. So how many parsers are built into SpringBoot?

In the DispatcherServlet, after obtaining the corresponding method according to the mapping, you need to find the corresponding HandlerAdapter to execute it. When we use RequestMapping annotations, use is RequestMappingHandlerAdapter this class. Look at the afterPropertiesSet method () for the class.

	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if(enclosing argumentResolvers = = null) {/ / get the parser method List here < HandlerMethodArgumentResolver > resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); }... } private List<HandlerMethodArgumentResolver>getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

		// Custom arguments
		if(getCustomArgumentResolvers() ! = null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(),true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}
Copy the code

In getDefaultArgumentResolvers approach, we can see that the parser springboot built a lot of parameters. We said to RequestResponseBodyMethodProcessor above, RequestHeaderMethodArgumentResolver is injected into the container on this end.


Returns the directory