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時,可以使用參數名擷取的願意。