springMvc - Mapping匹配过程
2019-08-22 19:52:07 32 举报
AI智能生成
SpringMVC请求过程,当一个请求传来时,Spring都做了什么
作者其他创作
大纲/内容
一个get请求,/myjava/game/games.do?gameId=1时,springMVC会做什么
1. 扫描controller注解和requstMapping注解的类<br>初始化成handlerMethod<br>
HttpServletBean.init<br>将配置参数映射到此servlet的bean属性,并调用子类初始化。<br>FrameworkServlet.initServletBean<br>FrameworkServlet.initWebApplicationContext<br>初始化WebApplicationContext<br>FrameworkServlet.createWebApplicationContext<br>创建WebApplicationContext<br>FrameworkServlet.configureAndRefreshWebApplicationContext<b><font color="#c41230"><br>AbstractApplicationContext.refresh<br>spring上下文初始化bean的方法</font></b><br>AbstractApplicationContext.finishBeanFactoryInitialization<br>完成此上下文的全部bean工厂初始化<br>DefaultListbleBeanFactory.preInstantiateSingletons<br>批量实例化单例模式bean,获取bean<br>AbstractBeanFactory.getBean<br>AbstractBeanFactory.doGetBean<br>DefaultSingletonBeanRegistry.getSingleton<br>AbstractAutowireCapableBeanFactory.createBean<br>AbstractAutowireCapableBeanFactory.doCreateBean<br>AbstractAutowireCapableBeanFactory.initializeBean<br>AbstractAutowireCapableBeanFactory.invokeInitMethods<br>RequestMappingHandlerMapping.afterPropertiesSet<br>AbstractHandlerMethodMapping.afterPropertiesSet<br>初始化时检查方法处理器<br>
AbstractHandlerMethodMapping.initHandlerMethods<br>扫描ApplicationContext中的bean,检测和注册处理程序方法。<br><font color="#c41230"><i><b>程序启动时执行,主要用于区分出controller和requestmapping注解的方法</b></i></font>
获取所有beanNames放入数组<br><font color="#c41230"><i><b>这里获取的是springIOC中全部的Bean<br>包含controller,service,dao等一系列我们注册的类</b></i></font>
遍历beanNames,调用RequestMappingHandlerMapping.isHandler<br>通过AnnotationUtils.findAnnotation(beanType, Controller.class)<br>处理controller注解和RequestMapping注解<br>
(AnnotationUtils.findAnnotation(beanType, Controller.class) != null)<br>(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null)<br>
从findAnnotationCacheMap中获取该bean和controller注解
findAnnotationCache 是一个线程安全的map,用于存放已经找到的AnnotationCacheKey
如果未取到值,则调用findAnnotation判断该Bean是否包含该注解
1. clazz.getDeclaredAnnotations()<br>常规的获取注解的方法
2. 遍历注解数组,判断ann.annotationType().equals(annotationType)<br>此处的annotationType为controller和requestMapping,如果找到直接返回
3. 如果上面的遍历没有找到,则判断是否为JDK提供的注解,并且add到一个Set visited中
visited,用于保存已经访问过的annotation
4. 获取类的全部接口interface,判断接口的注解
5. 判断父类,是否包含此注解
如果找到值,则set到findAnnotationCacheMap中
detectHandlerMethods<br>在处理程序中寻找处理程序方法。<br>
1. 获取handler的Class对象<br>(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()<br>比如此处获取的是GameController
2. HandlerMethodSelectorselectMethods<br>选择处理器方法,其实就是获取该类GameController下的全部方法<br>
3. registerHandlerMethod<br>遍历GameController下的全部方法来调用该方法<br>注册到HandlerMethodMapping中<br>
根据传入的类和方法,以及方法对应的requstmapping路径创建一个HandlerMethod方法处理器类<br><b><i><font color="#c41230">其实是一个方法对应一个处理器,如果controller中包含多个方法,会创造多个HandlerMethod类,只不过他们的bean属性是相同的</font></i></b><br>
HandlerMethod
private final Object bean;
private final BeanFactory beanFactory; 该bean的工厂<br>
private final Class<?> beanType; controller
private final Method method; 请求路径对应的方法
this.bridgedMethod = handlerMethod.bridgedMethod;
private final MethodParameter[] parameters;
将该方法处理器类放入名为handlerMethods的Map中
key : requestMappingInfo
value : HandlerMethod类
将请求路径放入urlMap中
key : Set<String>类型的请求路径
value : requestMappingInfo
封装以下请求映射条件
匹配方式 : Patterns
请求方法 : method
请求参数 : params
url请求头 : headers
Consumes
Produces
updateNameMap<br>将方法处理器放入nameMap中
key : GC#方法名
value : HandlerMethod类
getHandlerMethods
handlerMethodsInitialized
2. 初始化handlerMapping
DispatcherServlet.initStrategies<br>初始化该servlet使用的策略对象
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);<br>初始化该类使用的HandlerMappings。<br>如果在BeanFactory中没有为这个名称空间定义HandlerMapping bean,<br>我们默认为BeanNameUrlHandlerMapping。<br>
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);<br>在applicationContext中查找全部的handlerMapping,<font color="#c41230"><b><i>并赋值给handlerMappings集合</i></b></font><br>
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
3. 拦截分发前端请求
FrameworkServlet.service
获取请求方式 get/post/patch等
patch进入FrameworkServlet.processRequest
否则进入httpServlet.service
根据请求方式调用相应方法.如doGet
4. 根据请求查找对应的handlerMethod<br>并返回ModelAndView
例如 : 调用FrameworkServlet.doGet
FrameworkServlet.processRequest : 处理请求<br>FrameworkServlet重写了post,get等请求,交由processRequest方法处理
DispatcherServlet.doService
向request中放入一些值,方便在handler中获取
DispatcherServlet.doDispatch<br>这步执行完之后,response中就包含了返回值
checkMultipart(request); 处理是否是文件上传请求
DispatcherServlet.getHandler<br>返回的是一个HandlerExecutionChain(执行链)<br><h2><b><i><font color="#c41230">到这里,我们根据request的请求路径获得到了路径对应Controller和方法</font></i></b></h2>
1. 遍历handlerMappings
HandlerMapping接口,抽象类AbstractHandlerMapping
1. RequestMappingHandlerMapping
2. BeanNameUrlHandlerMaping
3. SimpleUrlHandlerMapping
AbstractHandlerMapping.getHandler<br>查询请求的处理器执行链
1. AbstractHandlerMethodMapping.getHandlerInternal <br>查找给定请求的处理程序方法。返回HandlerMethod类<br>
getUrlPathHelper <br>用于URL路径匹配的Helper类。提供对RequestDispatcher include中的URL路径的支持,并支持一致的URL解码。<br>
getLookupPathForRequest<br>
getPathWithinServletMapping(request)<br>返回给定请求的servlet映射中的路径,<br><i><b><font color="#c41230">从request获取到/game/games.do请求路径</font></b></i><br>
getPathWithinApplication(request)<br>返回给定请求的web应用程序内的路径。<b><font color="#c41230">/game/games.do</font></b><br>
getContextPath : 获取请求根目录/myjava
getRequestUri : 获取请求路径 (request.getRequestURI())
decodeAndCleanUriString : 解码提供的URI字符串,并去除';'后面的任何无关部分。
getRemainingPath : 去除掉请求路径中的项目名称等<br>
getServletPath(request)<br>request.getServletPath()<br>返回servlet请求路径 <font color="#c41230"><b>/game/games.do</b></font><br>
getRemainingPath
getPathWithinApplication 如果路径不为一个空字符串,则进入此方法
lookupHandlerMethod <br>查找当前请求的最佳匹配处理程序方法。<br>如果找到多个匹配项,则选择最佳匹配项。<br><b><font color="#c41230"><i>也就是根据上一步获取的路径找到最佳匹配的包装了Controller的HandlerMethod</i></font></b><br>
根据key /game/games.do 从urlMap中获取直接匹配
urlMap中存的是全部的requestMapping地址
addMatchingMappings : 如果urlmap中获取到匹配的地址,则进入addMatchingMappings<br>
getMatchingMapping<br>遍历获取urlMap的匹配结果来调用此方法<br>
getMatchingCondition : 从requestMappingInfo中获取这个url的详情,包括参数,请求头,请求方法等
通过一系列参数,new一个新的requestMappingInfo并返回
new Match(match, this.handlerMethods.get(mapping))<br>从AbstractHandlerMethodMapping.handlerMethods的map中获取handlerMethod然后new 一个Match<br>
Match是AbstractHandlerMethodMapping的一个内部类<br>一个用于匹配HandlerMethod及其映射的薄包装,在当前请求的上下文中与比较器比较最佳匹配。<br>
private final T mapping;
private final HandlerMethod handlerMethod;
添加Match进集合 matches
如果集合matches有多个结果,抛出IllegalStateException异常,说明请求遇到了多个匹配项,如果不进入,则到第四步
RequestMappingInfoHandlerMapping.handleMatch<br>向request中set一些参数
super.handleMatch(info, lookupPath, request)<br>先调用父类 AbstractHandlerMethodMapping的handlerMatch方法
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath); 将路径set到request中
将一些参数set进request中,如最佳匹配模式,URI模板变量属性<br>
return HandlerMethod.createWithResolvedBean<br><b><font color="#c41230"><i>返回一个新的保存了controller的HandlerMethod</i></font></b>
HandlerMethod
根据bean从Bean工厂中getBean,也就是实例化controller bean
return new HandlerMethod
2. return AbstractHandlerMapping.getHandlerExecutionChain(处理器controller,request)<br>构建一个新的HandlerExecutionChain(处理程序执行链,由处理程序对象和任何处理程序拦截器组成。)<br>
如果handler是一个执行链,则直接使用此,否则根据handler创建一个执行链
HandlerExecutionChain的构造方法<br>HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors)<br><b><i><font color="#c41230">在本例中执行到此步只有一个请求方法为games,ganmeController的handler</font></i></b>
添加拦截器.<b><i><font color="#c41230">本例中未配置,为空</font></i></b>
DispatcherServlet.getHandlerAdapter<br>返回适配的处理器
遍历handlerAdapters
1. RequestMappingHandlerAdapter
2. HttpRequestHandlerAdapter
3. SimpleControllerHandlerAdapter
handlerAdapters.supports(handler)
supportsInternal(handler)
此处判断请求方式,如果是get请求...
AbstracthandlerAdaptersget.LastModified
getLastModifiedInternal : 总是返回-1
AbstractHandlerMethodAdapter.handle<br><font color="#000000">使用HandlerAdapter来执行handler</font><br><i style="color: rgb(196, 18, 48); font-weight: bold;">到这里,我们执行完了方法,并获得了ModelAndView<br></i>此例中返回的是Null
RequestMappingHandlerAdapter.handleInternal
2. WebContentGenerator.checkAndPrepare (request, response, boolean lastModified) <br>设置缓存,默认不会执行
checkAndPrepare
applyCacheSeconds
invokeHandleMethod<br>返回ModelAndView
创建一个ServletWebRequest
getDataBinderFactory<br>创建 WebDataBinderFactory
获取handler的类型,此处为GameController
判断是否初始化过
createDataBinderFactory
getWebBindingInitializer
new ServletRequestDataBinderFactory
getModelFactory<br>创建ModelFactory
与getDataBinderFactory类似
createRequestMappingMethod<br>创建ServletInvocableHandlerMethod
new 一个 ServletInvocableHandlerMethod
initResponseStatus<br>判断是否有responseStatus注解
setHandlerMethodArgumentResolvers 等属性
new ModelAndViewContainer<br>创建ModelAndView容器
modelFactory.initModel<br>初始化model
invokeModelAttributeMethods<br>调用模型属性方法来填充模型。<br>
WebAsyncUtils.createAsyncWebRequest(request, response);<br>异步处理相关的处理
ServletInvocableHandlerMethod.invokeAndHandle<br><b><i><font color="#c41230">这里开始绑定参数和调用我们开发的方法</font></i></b>
InvocableHandlerMethod.invokeForRequest<br><h2><b><i><font color="#c41230">截止至此,我们调用了controller方法中并获得了方法的返回值<br></font></i></b><b><i><font color="#c41230">在本例中就是Game类</font></i></b></h2>
InvocableHandlerMethod.getMethodArgumentValues<br>获取当前请求的方法参数值。<br><b><i><font color="#c41230">获取到一个Object数组,里面分别存放着方法的参数:<br>Game,requst,response</font></i></b><br>
getMethodParameters<br>调用handlerMethod的parameters参数<br><h1><font color="#c41230"><b><i>?????什么时候放进去的???????</i></b></font></h1><br>
是一个MethodParameter类
MethodParameter是属于哪一个HandlerMthod类
method : 参数所在的方法
parameterType : 参数类型
遍历参数用于绑定
this.argumentResolvers.supportsParameter(parameter)<br>判断注册的方法是否支持指定的参数<br>从argumentResolver中获取
HandlerMethodArgumentResolverComposite.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);<br>参数解析,返回参数实例和值<br><b><i><font color="#c41230">本例中此处遍历三次,分别是Game,request,response</font></i></b>
getArgumentResolver(parameter)
从参数解析缓存中获取当前参数解析器,比如此例中的Game game
HandlerMethodArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);<br>从模型中解析参数,如果没有找到,则使用它的默认值实例化它。并通过数据绑定将请求<br>值填充进模型,并可选地验证@Valid注解是否出现在参数上。<br><h3><b><i><font color="#c41230">截止至此,我们获得了一个绑定对象,目标类型为Game,参数名称为game,下面要为它赋值</font></i></b></h3>
ModelFactory.getNameForParameter(parameter);
ModelFactory.getNameForParameter(parameter);<br><b><i><font color="#c41230">获取参数名称game</font></i></b>
annot = parameter.getParameterAnnotation(ModelAttribute.class);<br>判断字段是否被ModelAttribute注解<br>
String attrName = (annot != null) ? annot.value() : null;<br>如果被注解则取注解的值,但若注解和形参同时存在则取注解的值<br>
若未被注解则使用Conventions.getVariableNameForParameter取值
判断是否是数组
判断是否是集合,若是集合,但取值为Null则报错
否则直接取参数的类型
ClassUtils.getShortNameAsProperty<br>去除类型的完整包名,如com.jasmine.model.Game
如果从mavContainner中能根据参数名称获取到属性,则直接取,否则先创建该属性再取<br><b><i><font color="#c41230">此处获取的Game对象,只不过此时它的变量都为null</font></i></b>
binderFactory.createBinder<br>开始将参数值绑定到对象中
ServletRequestDataBinderFactory.createBinderInstance(target, objectName, webRequest)<br><b><i><font color="#c41230">参数分别为Game对象,"</font></i></b><font color="#c41230"><b><i>game</i></b></font><b><i><font color="#c41230">"字符串,request<br></font></i></b>最终会创建一个WebDataBinder对象,target为Game,名称为game
ExtendedServletRequestDataBinder.ExtendedServletRequestDataBinder(Game,game)<br>创建实例<br>ServletRequestDataBinder.ServletRequestDataBinder(target, objectName);<br>WebDataBinder.WebDataBinder(target, objectName);<br>DataBinder.DataBinder<br>这个会判断target参数是否是class java.util.Optional,如果不是就用绑定为原来的Game<br>
initBinder<br>初始化绑定器
set一些值
HandlerMethod
this.bean = handler; bean = controller<br> this.parameters = handlerMethod.parameters;
this.beanFactory = handlerMethod.beanFactory; 该bean的工厂<br>
this.beanType = handlerMethod.beanType; controller
this.method = handlerMethod.method; 请求路径对应的方法
this.bridgedMethod = handlerMethod.bridgedMethod;
ServletModelAttributeMethodProcessor.bindRequestParameters<br>绑定实参<br><h2><b><i><font color="#c41230">到这里结束我们binder属性中<br>的target对象的gameId的值变为了1</font></i></b></h2>
获取servletRequest
将绑定对象强转为ServletRequestDataBinder
ServletRequestDataBinder.bind(servletRequest)<br>开始绑定
根据request对象创建一个MutablePropertyValues<br><b><i><font color="#c41230">到这一步结束,我们获得一个MutablePropertyValues类<br>他包含我们的参数名称,参数值,用perpertyValues类的形式保存<br></font></i></b><h3><b><i><font color="#c41230">name = gameId , value = 1</font></i></b></h3>
ServletRequestParameterPropertyValues.ServletRequestParameterPropertyValues构造方法<br>
他会先调用WebUtils.getParametersStartingWith来获取一个Map对象<br>获取参数对应的值
Enumeration<String> paramNames = request.getParameterNames()<br>获取参数枚举
创建一个treeMap用来保存参数
paramNames.nextElement() <br>获取参数名gameId<br>
request.getParameterValues(paramName) <br>根据参数名获取参数值<br>
将此参数放入到map中<br>此时map中包含数据
key = gameId , value = 1
如果还有其他参数继续保存,否则返回这个map
MutablePropertyValues.MutablePropertyValues(Map original)<br>根据返回的map调用此类的构造方法<br>
这个类其实是propertyValue的实现类,用于保存一些容易变动的配置参数
<b><i><font color="#c41230">将map放入到名为propertyValueList的集合中</font></i></b>
addBindValues(mpvs, request)<br>子类可用于为请求添加额外绑定值的扩展点。在doBind(MutablePropertyValues)之前调用。默认实现为空。<br>
ServletRequestDataBinder.doBind(mpvs)<br>在实现绑定前要执行字段默认值和标记检查
checkFieldDefaults<br>检查默认值,对于以某种特殊字符开头的值,如果不存在,则使用指定值
checkFieldMarkers<br>检查以字段标记前缀开头的字段。如果字段标记存在表明字段存在于表单中。<br>如果属性值不包含相应的字段值,则该字段将被视为空,并将被适当重置。<br>
调用DataBinder.doBind
checkAllowedFields<br>删除不允许的字段值
PropertyAccessorUtils.canonicalPropertyName<br>移除字段旁边的单引号或双引号<br>{@code map['key']} -> {@code map[key]}<br>{@code map["key"]} -> {@code map[key]}<br>
checkRequiredFields<br>根据所需字段检查给定的属性值,在适当的地方生成缺失的字段错误。<br>
applyPropertyValues<br>将给定的属性值应用于目标对象。默认实现将所有提供的属性值应用为bean属性值。<br>
AbstractPropertyAccessor.setPropertyValues
BeanWrapperImpl.setPropertyValue(PropertyValue pv)<br>执行到此,我们将值set到了实体Game中
获取参数名称<br><i><b><font color="#c41230">gameId</font></b></i>
getBeanWrapperForPropertyPath(gameId);<br>对数据格式进行一些处理,详见此方法调用的<br>getFirstNestedPropertySeparatorIndex<br>并返回获取BeanWrapperImpl<br>它是一个bean的映射,其实就是Game类的映射
getPropertyNameTokens<br>将属性名解析为一个属性名标记PropertyTokenHolder
给PropertyValue的resolvedTokens属性赋值
setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv)<br>935行的方法
PropertyDescriptor pd = PropertyValue.resolvedDescriptor;
PropertyDescriptor类:(属性描述器)<br>JDK java.beans包下的一个类<br>
从pd中获取该参数的readMethod
执行readMethod.invoke(Game)获取旧值
convertForProperty<br><i><b><font color="#c41230">这里获得了根据类型转换后的参数的值 1</font></b></i>
new 了一个property类
BeanWrapperImpl.convertIfNecessary
convertIfNecessary
TypeConverterDelegate.convertIfNecessary<br>将指定属性的值转换为所需类型<br>
writeMethod.invoke(this.object, value);<br><b><i><font color="#c41230">执行setGameId方法写入值 <br>object = Game , value = 1;</font></i></b>
validateIfApplicable<br>检查方法是否有Validated注解
将Modelset到mavContainer中
doInvoke<br>执行方法,获取返回值returnValue
ReflectionUtils.makeAccessible(getBridgedMethod());<br>将方法设置为可执行<br>method.setAccessible(true);<br>
getBridgedMethod().invoke(getBean(), args);<br><b><i><font color="#c41230">调用此方法也就是我们的controller方法</font></i></b><br>
setResponseStatus<br>如果有responseStatus注解则设置返回响应值
this.returnValueHandlers.handleReturnValue
RequestResponseBodyMethodProcessor.handleReturnValue
writeWithMessageConverters<br>将给定的返回值写入给定的web请求<br>
getModelAndView
mavContainer.isRequestHandled()<br>这里会判断是否完全处理,若为true,则返回Null
applyDefaultViewName
mappedHandler.applyPostHandle
processDispatchResult
triggerAfterCompletion
resetContextHolders
requestAttributes.requestCompleted()
executeRequestDestructionCallback<br>单独启动一个线程执行请求完成后已注册执行的所有回调。<br>
updateAccessedSessionAttributes
publishRequestHandledEvent
OncePerRequestFilter.doFilter
0 条评论
下一页