天天看點

spring源碼解析-web系列(五):解析請求參數

spring源碼解析-web系列(一):啟動

spring源碼解析-web系列(二):處理請求的過程

spring源碼解析-web系列(三):九大元件之HandlerMapping

spring源碼解析-web系列(四):九大元件之HandlerAdapter

spring源碼解析-web系列(五):解析請求參數

spring源碼解析-web系列(六):九大元件之ViewResolver

spring源碼解析-web系列(七):九大元件之HandlerExceptionResolver

轉載請标明出處:

https://blog.csdn.net/bingospunky/article/details/98620658

本文出自馬彬彬的部落格

參數的分類

我們在使用spring-web時,配置參數的形式有很多,比如:@PathVariable、@RequestParam、@SessionAttribute、@ModelAttribute、Model。

本文把他們分為兩類,根據在源碼裡被處理的位置劃分:1.在InvocableHandlerMethod中使用HandlerMethodArgumentResolver處理的的,比如@PathVariable、@RequestParam。2.不是HandlerMethodArgumentResolver處理的,比如@ModelAttribute、@SessionAttribute、對FlashMap的處理,這些參數一般是在RequestMappingHandlerAdapter.invokeHandleMethod方法中。

本文隻叙述HandlerMethodArgumentResolver解析參數的處理。後面一類參數用的比較少。

解析參數的過程

參數是在InvocableHandlerMethod的getMethodArgumentValues方法被解析的。

代碼1 (org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues):

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        MethodParameter[] parameters = this.getMethodParameters();
        Object[] args = new Object[parameters.length];

        for(int i = 0; i < parameters.length; ++i) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            GenericTypeResolver.resolveParameterType(parameter, this.getBean().getClass());
            args[i] = this.resolveProvidedArgument(parameter, providedArgs);
            if (args[i] == null) {
                if (this.argumentResolvers.supportsParameter(parameter)) {
                    try {
                        args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var9) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug(this.getArgumentResolutionErrorMessage("Error resolving argument", i), var9);
                        }

                        throw var9;
                    }
                } else if (args[i] == null) {
                    String msg = this.getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
                    throw new IllegalStateException(msg);
                }
            }
        }

        return args;
    }
           

代碼1第2行擷取所有的MethodParameter。MethodParameter是什麼呢?MethodParameter是對Controller一個方法裡的一個參數的描述,包含Method資訊、參數Index、參數的注解等資訊。這些MethodParameter是在初始化時掃描所有的Controller時加載的。

代碼1周遊擷取到的MethodParameter,分别擷取真實的參數。代碼1第9行providedArgs為null,是以通過代碼1第13行的代碼擷取真實參數。代碼1第21~24行,擷取不到真實參數時,抛異常。

擷取真實參數的代碼如下:

代碼2 (org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument):

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        } else {
            return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
    }
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        if (result == null) {
            Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver methodArgumentResolver = (HandlerMethodArgumentResolver)var3.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + parameter.getGenericParameterType() + "]");
                }

                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, methodArgumentResolver);
                    break;
                }
            }
        }

        return result;
    }
           

代碼2第2行先擷取解析這個參數的HandlerMethodArgumentResolver。擷取的過程在getArgumentResolver方法中,先在緩存查找,如果沒有,則代碼2第12行周遊this.argumentResolvers數組,傳回支援處理這個參數的HandlerMethodArgumentResolver。那麼這些解析器的來源在哪裡呢?

HandlerMethodArgumentResolver的來源

1.argumentResolvers是HandlerMethodArgumentResolverComposite對象的屬性,這個HandlerMethodArgumentResolverComposite對象是InvocableHandlerMethod裡的argumentResolvers屬性,InvocableHandlerMethod的argumentResolvers屬性是在如下位置被指派。

代碼3 (org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod):

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
	invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
           

代碼3裡RequestMappingHandlerAdapter處理每個請求時,都會建立ServletInvocableHandlerMethod,然後把RequestMappingHandlerAdapter的this.argumentResolvers屬性指派給ServletInvocableHandlerMethod。

那麼RequestMappingHandlerAdapter裡的this.argumentResolvers是如何擷取的呢?在上一篇部落格介紹

RequestMappingHandlerAdapter加載過程的時候,在afterPropertiesSet方法初始化,會建立argumentResolvers對象并添加預設的HandlerMethodArgumentResolver,代碼如下:

代碼4 (org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.getDefaultArgumentResolvers):

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList();
        resolvers.add(new RequestParamMethodArgumentResolver(this.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(this.getMessageConverters()));
        resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters()));
        resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory()));
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters()));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
        if (this.getCustomArgumentResolvers() != null) {
            resolvers.addAll(this.getCustomArgumentResolvers());
        }

        resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
        return resolvers;
    }
           

至此,HandlerMethodArgumentResolver的來源就搞清楚了。總結一下:RequestMappingHandlerAdapter初始化的時候建立了一個HandlerMethodArgumentResolverComposite對象并添加預設的HandlerMethodArgumentResolver,且RequestMappingHandlerAdapter在BeanFactory裡是單例的,是以HandlerMethodArgumentResolverComposite對象也是單例的。在處理每個請求時,都會建立ServletInvocableHandlerMethod,然後把單例的HandlerMethodArgumentResolverComposite對象指派給每個ServletInvocableHandlerMethod使用。

HandlerMethodArgumentResolver

HandlerMethodArgumentResolver接口就是用來解析參數的,隻定義了兩個方法:1.判斷該Resolver使用可以解析某個參數。2.具體解析參數。

代碼5 (org.springframework.web.method.support.HandlerMethodArgumentResolver):

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter var1);
    Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
}
           

在代碼4中可以發現spring已經實作了很多的Resolver,這裡挑兩個有代表性的來叙述。

ModelMethodProcessor

代碼6 (org.springframework.web.method.annotation.ModelMethodProcessor):

public boolean supportsParameter(MethodParameter parameter) {
        return Model.class.isAssignableFrom(parameter.getParameterType());
    }

    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return mavContainer.getModel();
    }
           

ModelMethodProcessor的實作很簡單,判斷是否支援時根據參數的類型,解析參數時直接擷取mavContainer力的model的值。

PathVariableMethodArgumentResolver

PathVariableMethodArgumentResolver繼承AbstractNamedValueMethodArgumentResolver,AbstractNamedValueMethodArgumentResolver實作HandlerMethodArgumentResolver接口。

PathVariableMethodArgumentResolver的supportsParameter方法比較簡單,主要就是根據參數上時是否被@PathVariable注解來判斷的。

resolveArgument方法被AbstractNamedValueMethodArgumentResolver實作,然後提供了一些模闆方法供PathVariableMethodArgumentResolver實作。

代碼7 (org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument):

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Class<?> paramType = parameter.getParameterType();
        AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
        Object arg = this.resolveName(namedValueInfo.name, parameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = this.resolveDefaultValue(namedValueInfo.defaultValue);
            } else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
                this.handleMissingValue(namedValueInfo.name, parameter);
            }

            arg = this.handleNullValue(namedValueInfo.name, arg, paramType);
        } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = this.resolveDefaultValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

            try {
                arg = binder.convertIfNecessary(arg, paramType, parameter);
            } catch (ConversionNotSupportedException var10) {
                throw new MethodArgumentConversionNotSupportedException(arg, var10.getRequiredType(), namedValueInfo.name, parameter, var10.getCause());
            } catch (TypeMismatchException var11) {
                throw new MethodArgumentTypeMismatchException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
            }
        }

        this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }
           

代碼8 (org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.NamedValueInfo):

protected static class NamedValueInfo {
        private final String name;
        private final boolean required;
        private final String defaultValue;

        public NamedValueInfo(String name, boolean required, String defaultValue) {
            this.name = name;
            this.required = required;
            this.defaultValue = defaultValue;
        }
    }
           

代碼9 (org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver.resolveName):

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
				HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
		return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
	}
           

代碼7第3行,建立了NamedValueInfo對象,NamedValueInfo包含name、required、defalultValue等屬性,如代碼8所示。代碼7第4行通過resolveName方法擷取真實的參數,resolveName方法由子類實作,如代碼9所示,在uriTemplateVars裡查找,uriTemplateVars是HandlerMapping根據lookupPath找到處理請求的處理器後設定的。代碼7第6第12行使用模闆方法處理參數為空的情況,比如代碼7第9行的方法直接抛出***MissingPathVariableException***異常。代碼7第17第27行通過binderFactory對參數進行轉換。代碼7第29行調用handleResolvedValue處理擷取到的參數,在PathVariableMethodArgumentResolver中的實作就是把相關資訊緩存在HTTPServletRequest中。

下面我們來看一下代碼7第3行擷取NamedValueInfo的過程。

代碼10 (org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver):

private AbstractNamedValueMethodArgumentResolver.NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = (AbstractNamedValueMethodArgumentResolver.NamedValueInfo)this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
            namedValueInfo = this.createNamedValueInfo(parameter);
            namedValueInfo = this.updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }

        return namedValueInfo;
    }
    
    private AbstractNamedValueMethodArgumentResolver.NamedValueInfo updateNamedValueInfo(MethodParameter parameter, AbstractNamedValueMethodArgumentResolver.NamedValueInfo info) {
        String name = info.name;
        if (info.name.length() == 0) {
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException("Name for argument type [" + parameter.getParameterType().getName() + "] not available, and parameter name information not found in class file either.");
            }
        }

        String defaultValue = "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n".equals(info.defaultValue) ? null : info.defaultValue;
        return new AbstractNamedValueMethodArgumentResolver.NamedValueInfo(name, info.required, defaultValue);
    }
           

代碼11 (org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver.createNamedValueInfo):

@Override
	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
		return new PathVariableNamedValueInfo(annotation);
	}
	private static class PathVariableNamedValueInfo extends NamedValueInfo {
		public PathVariableNamedValueInfo(PathVariable annotation) {
			super(annotation.value(), true, ValueConstants.DEFAULT_NONE);
		}
	}
           

代碼10第4行調用createNamedValueInfo方法建立NamedValueInfo,createNamedValueInfo由子類實作,在PathVariableMethodArgumentResolver中的實作如代碼11所示,通過注解擷取name,required設定為true,defaultValue設定為ValueConstants.DEFAULT_NONE。代碼10第5行調用updateNamedValueInfo方法更新NamedValueInfo。更新的過程在如代碼10第12~第23行所示,如果name長度為0,則通過代碼10第15行的***parameter.getParameterName()***重新擷取name;代碼10第21行根據條件對預設值進行替換。重新生成NamedValueInfo。

代碼10第15行的***parameter.getParameterName()***重新擷取name的方法如下:

代碼12 (org.springframework.core.MethodParameter.getParameterName):

public String getParameterName() {
		ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
		if (discoverer != null) {
			String[] parameterNames = (this.method != null ? discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
			if (parameterNames != null) {
				this.parameterName = parameterNames[this.parameterIndex];
			}
			this.parameterNameDiscoverer = null;
		}
		return this.parameterName;
	}
           

代碼12第45行擷取該方法的所有參數名稱,然後通過索引擷取目前參數的名字。擷取方法的所有參數名稱使用的是discoverer對象,它是ParameterNameDiscoverer類型的。那麼這個對象是從哪裡來的呢?discoverer的來源和本文上面提到的 ***HandlerMethodArgumentResolver的來源***相似,它也是RequestMappingHandlerAdapter建立的對象,然後一層層傳遞到這裡使用的。在RequestMappingHandlerAdapter中建立的代碼如下: private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); ,可以看到這個discoverer是DefaultParameterNameDiscoverer類型的。

DefaultParameterNameDiscoverer代碼如下:

代碼13 (org.springframework.core.DefaultParameterNameDiscoverer):

public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

	private static final boolean standardReflectionAvailable = ClassUtils.isPresent(
			"java.lang.reflect.Executable", DefaultParameterNameDiscoverer.class.getClassLoader());

	public DefaultParameterNameDiscoverer() {
		if (standardReflectionAvailable) {
			addDiscoverer(new StandardReflectionParameterNameDiscoverer());
		}
		addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
	}
}
           

這個DefaultParameterNameDiscoverer很符合spring的風格,它自己不幹活,它包含了StandardReflectionParameterNameDiscoverer和LocalVariableTableParameterNameDiscoverer,當需要擷取方法的參數名稱時,依次調用這兩個Discoverer擷取參數名稱。

StandardReflectionParameterNameDiscoverer是通過反射擷取參數名的,LocalVariableTableParameterNameDiscoverer是通過asm架構擷取參數名的。這就是當我們使用@PathVariable、@RequestParam等注解時,如果不指定name時,可以使用參數名擷取的願意。