springmvc source code-parameter analysis

This blog introduces part of the logic of the call . This article mainly introduces the two methods of implementing the Controller interface and implementing the HttpRequestHandler interface. These two methods are relatively simple, which is to force the transfer and then call the target method, which is troublesome. It is the following controller declared by @Controller annotation, which will be introduced in detail in this blog

For the way of declaring handlerMethod through @Controller and @RequestMapping() through annotations, it will be divided into the following three steps:
1. Parse the parameters in the request
2. Call the target method
3. Parse the return parameters

Let's first look at the call chain, the code we want to talk about, the previous parsing logic is the following call chain

org.springframework.web.servlet.DispatcherServlet#doDispatch
	org.springframework.web.servlet.HandlerAdapter#handle
		org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
			org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
				org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
					org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle


较为核心的方法,就在这里
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
    
    

	/**
	 * 调用实际的目标方法
	 * 在这个方法中完成了两步操作
	 * 	1.对参数的解析
	 * 	2.对目标方法的调用
	 */
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);

	/**
	 * 下面这里是对返回参数进行解析,举例:
	 * @ResponseBody 注解就是在下面这里被处理的
	 */
	if (returnValue == null) {
    
    
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    
    
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
    
    
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
    
    
		/**
		 * 处理返回结果
		 * 如果返回结果是对象,并且加了 @ResponseBody的注解,那么spring内部维护了一个 HttpMessageConverters
		 */
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
    
    
		if (logger.isTraceEnabled()) {
    
    
			logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
		}
		throw ex;
	}
}

One, the treatment of parameters

I have written above. In the above method, three steps are completed. Let’s first look at the analysis of the parameters.


@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
    
    

	/**
	 * 第一步:解析入参
	 */
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
    
    
		logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
				"' with arguments " + Arrays.toString(args));
	}
	/**
	 * 第二步调用对应的方法,获取返回值
	 */
	Object returnValue = doInvoke(args);
	if (logger.isTraceEnabled()) {
    
    
		logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
				"] returned [" + returnValue + "]");
	}
	return returnValue;
}

Let's look at the specific analysis logic

The following method, the logic inside is like this
1. Get all the input parameters
2. parse each input parameter in turn, determine which parameter parser can parse, and then analyze

/**
 * Get the method argument values for the current request.
 * 从request中获取到当前方法的入参,也就是args
 */
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
    
    

	/**
	 * 获取到所有的参数名
	 */
	MethodParameter[] parameters = getMethodParameters();
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
    
    
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		/**
		 * 下面这行代码,看注释的意思是:从提供的参数列表中获取对应的值,但是从前面的调用链中会发现,入参的providedArgs是null
		 */
		args[i] = resolveProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
    
    
			continue;
		}
		/**
		 * 遍历所有的参数解析器,看哪个解析器可以解析,如果参数解析器返回true,就表示可以解析
		 * 如果我们要扩展参数解析器,就需要看下这里的逻辑
		 * 需要学习下这里的argumentResolvers是在哪里赋值的
		 */
		if (this.argumentResolvers.supportsParameter(parameter)) {
    
    
			try {
    
    
				/**
				 * 这里就是用parameter对应的解析器去解析该参数
				 */
				args[i] = this.argumentResolvers.resolveArgument(
						parameter, mavContainer, request, this.dataBinderFactory);
				continue;
			}
			catch (Exception ex) {
    
    
				if (logger.isDebugEnabled()) {
    
    
					logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
				}
				throw ex;
			}
		}
		if (args[i] == null) {
    
    
			throw new IllegalStateException("Could not resolve method parameter at index " +
					parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
					": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
		}
	}
	return args;
}

The following method is the logic of specific judgment, for example: For
example, I customize a annotation @XXXX, and then customize a parameter parser, which is used to parse this custom annotation, so I only need to judge in the supportsParameter method. , Whether the parameter is annotated with @XXXX is fine, this will be introduced separately later


/**
 * 遍历所有的参数处理器,找到处理该parameter的处理器,然后存入到map集合中,
 * 第二次获取处理该参数的处理器时,就无须再次遍历所有的support方法,直接从map缓存中获取即可
 * @param parameter
 * @return
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
    
    
		for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
    
    
			if (logger.isTraceEnabled()) {
    
    
				logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
						parameter.getGenericParameterType() + "]");
			}
			/**
			 * 核心方法就是这里,上面会遍历所有的参数解析器,依次去调用对应的supports方法,判断是否可以处理parameter
			 * 如果可以,就返回true,然后会把对应的methodArgumentResolver和对应的parameter关联起来
			 */
			if (methodArgumentResolver.supportsParameter(parameter)) {
    
    
				result = methodArgumentResolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}

Here is the specific method to deal with

@Override
@Nullable
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("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
	}
	/**
	 * 这里调用的就是处理的方法
	 */
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

Two, call the target method

I won’t elaborate on calling here, and there is not much logic inside.

Object returnValue = doInvoke(args);

method.invoke(obj,args); method will be called in doInvoke to call the target method

Three, the processing of the return value

For the return value, the core method is here:

handleReturnValue
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
    

	/**
	 * 拿到返回值之后,判断当前返回的对象,需要由哪个handler来处理,如果是json的话,是requestResponseBodyMethodProcessor来处理的
	 */
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
    
    
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	/**
	 * 这里是调用returnValueHandler的处理方法
	 */
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

In fact, the processing logic of the return value is basically the same as the parameter parsing. First, determine whether the parsing class supports parsing the return value. If it supports parsing the return value, call the corresponding parsing method


@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    
    
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
    
    
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
    
    
			continue;
		}
		if (handler.supportsReturnType(returnType)) {
    
    
			return handler;
		}
	}
	return null;
}

Let’s take the @ResponseBody annotation as an example. After getting the return value, we judge whether the @ResponseBody annotation is added to the method or class.

/**
 * 处理@ResponseBody注解修饰的方法,在获取到方法的返回值之后,会通过该类对返回值进行处理
 * @param returnType the method return type to check
 * @return
 */
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    
    
	return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
			returnType.hasMethodAnnotation(ResponseBody.class));
}

The above is the logic of method parameter parsing, method invocation, and return value parsing. In fact, it is relatively simple:
1. First traverse all the parameter parsers to determine whether the parameter is supported. If it is supported, analyze it
2. Pass method.invoke () Call the target method
3. Judge the return value, which is consistent with the idea of ​​parameter analysis. The adapter mode should be used here.

Guess you like

Origin blog.csdn.net/CPLASF_/article/details/110189503