spring源码解读6,参数解析 - Go语言中文社区

spring源码解读6,参数解析


前情提要

现在我们已经知道org.springframework.web.servlet.DispatcherServlet处理请求时会获取 HandlerAdapter 并且调用
HandlerAdapter#handle方法,实际上调用了子类的
RequestMappingHandlerAdapter#handleInternal方法,

重要接口和类

RequetMappingHandlerAdapter的继承结构如下

image.png

这个类实现了HandlerAdapter接口

image.png

配置<mvc:annotation-driven/>,将会注册两个bean。RequestMappingHandlerMappingRequetMappingHandlerAdapter,这两个bean会被初始化
因为实现了InitializingBean接口,bean在创建后会调用afterPropertiesSet完成初始化

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

if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}

可以看到先调用了initControllerAdviceCache用于实现ControllerAdvice注解的相关缓存内容
之后分别初始了argumentResolvers,initBinderArgumentResolvers,returnValueHandlers,从名字上不难看出分别是参数解析,参数绑定,和返回值解析

MethodParameter

MethodParameter这个类用于保存方法的参数信息,包含类型、坐标(第几个参数)注解发现参数名的方法等

image.png

InvocableHandlerMethod

这个类继承关系如下

image.png

前文已经介绍了HandlerMethod用于保存方法的相关信息,那么不难看出InvocableHandlerMethod是可执行的方法,增加了参数解析,返回值绑定等逻辑
这个类在每次DispatcherServlet.dodispatch

具体执行

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}

if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}

return mav;
}

从代码中可以看出这段代码:

  • 检查了 request
    调用invokeHandlerMethod方法 获取ModelAndView
    根据是否是session请求分别使用了加锁的访问和不加锁的访问,具体的执行都是在invokeHandlerMethod中进行,invokeHandlerMethod的参数分别是request,response,和上一步匹配到内容HandlerMethod,也就是Controller中匹配到的方法。
    invokeHandlerMethod方法如下
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}

invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}

return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}

首先可以看到调用了getDataBinderFactory获取WebDataBinderFactory,代码如下

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
// Global methods first
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
}
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
return createDataBinderFactory(initBinderMethods);
}

从找到Controller类中找到被InitBinder修饰的方法,然后把这些方法作为参数initBinderMethods,传入createDataBinderFactorycreateDataBinderFactory中创建了一个ServletRequestDataBinderFactory

一系列准备之后,执行了invocableMethod.invokeAndHandle(webRequest, mavContainer);方法

这个方法执行具体的

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {

//这一步已经获得了结果
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);

if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}

mavContainer.setRequestHandled(false);
try {
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;
}
}

调用invokeForRequest方法已经获得了数据结果

public Object invokeForRequest(NativeWebRequest request, 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;
}

好的真相与我们越来越近,查看org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues方法

private Object[] getMethodArgumentValues(NativeWebRequest request, 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);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
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.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}

两步来找参数,第一步从providedArgs中找到类型支持的情况,我们的例子这个参数是空的
第二步,通过argumentResolvers如果支持参数解析,则调用argumentResolversresolveArgument解析参数

argumentResolversorg.springframework.web.method.support.HandlerMethodArgumentResolverComposite的一个实例。

HandlerMethodArgumentResolverComposite的初始化在 RequestMappingHandlerAdapter初始化时完成

初始化时指定的解析器

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;
}

注意解析参数时也是严格按照上面的添加顺序来解析。
需要注意的是参数名解析,jdk1.7前通过反射接口拿不到
spring 通过
通过RequestParma注解获取
jdk1.8之前通过反射接口无法得到参数名,spring默认通过org.springframework.core.DefaultParameterNameDiscoverer获取参数名

public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

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

}

org.springframework.core.StandardReflectionParameterNameDiscoverer是标准的根据反射拿到参数名的方式,只在jdk8下有效
org.springframework.core.LocalVariableTableParameterNameDiscoverer通过第三方库如ASM获取.java文件读取字节码,在字节码中找到
LocalVariableTable来命名

版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/f0a6aefa2706
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-08 21:42:38
  • 阅读 ( 1215 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢