# Spring MVC
# 基本概念
- 转发与重定向页面字符串不经过视图解析器处理,无拼串过程;其中
/
表示项目根路径 - 视图对象是无状态的,因此不会有线程安全问题
# BindingAwareModelMap
(“隐含模型”)
- 控制器方法的返回值可以为
ModelAndView
类型,该对象既包含视图信息,又包含模型数据信息,其中模型数据信息置于request
域中- 在
Controller
类上标注@SessionAttributes
注解,可将(value
或type
属性指定的)模型数据信息置于**request
域以及session
域**中
- 在
@ModelAttribute
标注的方法在控制器方法前运行,与控制器方法共享同一个BindingAwareModelMap
对象
# 注意事项
# Controller获取前端传入的请求参数
# GET
# POST
application/json
:使用@RequestBody
对象接收
x-www-form-urlencoded
:使用HttpServletRequest
获取请求参数,亦可直接使用POJO、@RequestBody
字符串,或HttpEntity<T>
对象(可以获取所有请求头)接收
# 数据格式化与数据校验
- 在POJO字段上添加格式规则注解
- 使用JSR 303实现类,在POJO字段上添加校验规则注解,在
Controller
方法入参处添加@Valid
/@Validated
注解(标注该注解的入参后紧跟BindingResult
对象,封装校验结果)
# 异常处理
# HandlerExceptionResolver
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
处理部分预设异常
# 支持RESTful请求方法
配置HiddenHttpMethodFilter
前端携带_method
参数,参数值为delete
或put
(在过滤器中统一转为大写)
HttpMethodRequestWrapper
重写了父类HttpServletRequest
的getMethod()
方法,将传入的(而非request本身的)方法参数封装为新的请求方法对外返回
# 请求处理流程
# Spring MVC九大组件
MultipartResolver
LocaleResolver
ThemeResolver
HandlerMapping
集合HandlerAdapter
集合HandlerExceptionResolver
集合ThemeResolver
RequestToViewNameTranslator
FlashMapManager
ViewResolver
集合
# 处理器处理请求映射及视图解析器解析页面字符串流程(Spring 4.x旧版本)
- 客户端(浏览器)发送请求,最终请求至
DispatcherServlet
DispatcherServlet
根据请求信息调用HandlerMapping
,解析请求对应的Handler
- 解析对应的
Handler
,交由对应HandlerAdapter
适配器处理 HandlerAdapter#handle(...)
处理目标请求,执行业务逻辑- 处理器返回
ModelAndView
对象通过适配器传递给DispaterServlet
,其中Model
是返回的数据对象,View
为逻辑View ViewResolver
根据逻辑View查找实际View
DispaterServlet
将返回的Model
传给View
(视图渲染)DispaterServlet
将View
返回给请求者(浏览器)
participant HttpServlet
participant HttpServletBean
participant FrameworkServlet
participant DispatcherServlet
note over HttpServlet: doGet(req, res)/doPost(req, res)
note over FrameworkServlet: processRequest(req, res)
note over DispatcherServlet: doService(req, res) -> doDispatch(req, res)
# DispatcherServlet.doDispatch(req, res)
checkMultipart(req)
,判断是否为文件上传请求getHandler(processedRequest)
,获取能处理当前请求的控制器/处理器(若没有符合条件的控制器/处理器,则重定向至404页面,除非用户在配置中显式要求抛出NoHandlerFoundException
),其中包含请求所经过的拦截器信息interceptorList
该方法遍历
HandlerMapping[]
(处理器映射),获取控制器方法映射信息,构造HandlerExecutionChain
对象返回// getHandler() // XXXHandlerMapping中保存有不同请求映射信息的(LinkedHashMap) handlerMap属性 for(handlerMapping hm: this.handlerMappings) { // ... HandlerExecutionChain handler = hm.getHandler(req); // return handler }
- (优先匹配)动态方法对应
DefaultAnnotationHandlerMapping
或RequestMethodHandlerMapping
对,handlerMap
/urlMapping
属性中保存有控制器方法(动态方法)请求映射信息(/xxx=XXXController@addr
),需要借助<mvc:annotation-driven>
实现 - 静态资源对应
SimpleUrlHandlerMapping
对象,handlerMap
属性中保存有(其他)请求的映射信息(/**=DefaultServletHttpRequestHandler@addr
,即所有请求直接由Tomcat接管),需要借助<mvc:default-servlet-handler>
实现
- (优先匹配)动态方法对应
getHandlerAdapter(mappedHandler.getHandler())
,获取能执行控制器方法的适配器ha
该方法从
HandlerAdapter[]
中遍历控制器方法适配器(HttpRequestHandlerAdapter
/SimpleControllerHandlerAdapter
/AnnotationMethodHandlerAdapter
),如果适配器支持当前处理器,则返回该适配器!mappedHandler.applyPreHandle(processedRequest, response)
,遍历interceptorList
进行preHandle()
方法调用(启用多个拦截器时,不论是否有拦截器不放行请求,已放行拦截器的afterCompletion()
方法都会执行)boolean applyPreHandle(HttpServletRequest req, HttpServletResponse res) { if (getInterceptors() != null) { for (int i = 0; i < getInterceptors().length; i++) { HandlerIntercoptor interceptor = getInterceptors()[i]; if (!interceptor.preHandle(req, res, this.handler)) { triggerAfterCompletion(req, res, null); return false; } this.interceptorIndex = i; // 已有i个拦截器正常执行 } } return true; }
mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
,适配器ha
执行目标方法,返回ModelAndView
对象(自动封装可能返回的视图名字符串)目标控制器方法的反射执行流程:
ha.handle(processedRequest, response, mappedHandler.getHandler())
判断是否标注有
SessionAttribute
注解,若存在则放入SessionAttributeStore
invokeHandlerMethod(processedRequest, response, mappedHandler)
- 获取当前处理器的
ServletHandlerMethodResolver
- 通过方法解析器定位当前请求对应的控制器方法
- 创建
ServletHandlerMethodInvoker
和BindingAwareModelMap
对象
- 获取当前处理器的
HandlerMethodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel)
遍历所有
SessionAttribute
名,将SessionAttributeStore
中存在的SessionAttribute
放入implicitModel
对象遍历所有
ModelAttributeMethod
,获取ModelAttributeMethod
方法的参数:Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
- 解析
@ModelAttribute
方法的参数(参见下一节加粗部分) - 根据
@ModelAttribute
注解中value
属性值确定attrName
(若未显式配置该属性则赋值为方法返回值首字母小写字符串),并放入implicitModel
对象 - 传入
args[]
数组,反射执行@ModelAttribute
方法(attributeMethodToInvoke
)
- 解析
再次调用
resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel)
获取目标控制器方法的参数,并真正通过反射执行目标控制器方法解析方法参数的流程(原始):
创建
args[]
数组,长度为method.getParameterTypes()
长度遍历方法参数,尝试获取参数信息:
若标有注解,则对每一类型的注解分别进行判断,返回对应不同类型注解的成员变量
若某一参数没找到任何注解信息:
判断该参数是否为
Servlet
原生组件对象判断是否为
Model
或Map
实现类,若是则将implicitModel
对象赋值给args[]
对应元素判断是否为其他类型的原生
api
(SessionStatus
/HttpEntity
/Errors
)判断该参数是否为简单类型(基本类型对象),若是则将
paramName
赋为空串若参数为
POJO
类型,则将attrName
赋为空串// 将控制器方法的入参信息传递给WebDataBinderFactory工厂对象,创建DataBinder实例 WebDataBinder resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler) { // 若attrName为空串,则赋值为参数类型首字母小写 // 将封装的参数对象赋值给bindObject返回 }
封装
POJO
类型bindObject
对象流程:- 在
implicitModel
中寻找key
为attrName
的对象,若存在则赋值给bindObject
返回 - 若该属性标注有
SessionAttribute
注解,则尝试从Session
中获取value
属性标注的对象或key
为attrName
的对象 - 否则利用反射创建名为
attrName
值的空POJO
对象
使用
doBind(webDataBinder, webRequest, ...)
方法,使用请求参数对上述对象中的属性进行一一绑定请求参数绑定流程:
DataBinder
调用ConversionService
对象进行数据类型转换与数据格式化,并进行入参对象的属性绑定自定义类型转换器:
- 实现
Converter
接口 - 注册自定义
FormattingConversionServiceFactoryBean
- 可在
<mvc:annotiation-driven>
属性中配置自定义的ConversionService
- 实现
DataBinder
调用多个Validator
对象对已经完成属性绑定的入参对象进行数据合法性校验,数据绑定结果保存至BindingResult
对象中
- 在
根据上一步返回的参数信息(标有注解时根据注解信息确定参数值,否则返回上一步中所得到的对象),将参数对象放入
args[]
的对应元素中
传入
args[]
数组,真正反射执行目标控制器方法
mappedHandler.applyPostHandle(processedRequest, response, mv)
逆序执行(已完成的)拦截器的postHandle()
方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
,转发到目标页面若有异常传入,先处理异常
mv = processHandlerException(req, res, handler, exception); // 遍历所有HandlerExceptionHander进行异常解析
render(mv, req, res)
View view = resolveViewName(mv.getViewName, mv.getModelInternal, locale, req)
View view = viewResolver.resolveViewName(viewName, locale)
遍历所有可用的视图解析器(默认即为
InternalResourceViewResolver
,优先级最低)根据视图名获取View
对象(RedirectView
/InternalResourceView
):- 从
this.ViewCreationCache
中尝试获取View
对象 View view = createView(viewName, locale)
- 进行实际的前后缀拼串过程
- 将
View
对象放入this.ViewCreationCache
- 从
view.render(mv.getModelInternal, req, res)
转发到目标页面Map<String, Object> mergedModel = createMergedOutputModel(model, req, res); prepareResponse(req, res); renderMergedOutputModel(mergedModel, req, res); // 对需要在页面展示的ModelMap进行渲染
renderMergedOutputModel(mergedModel, req, res)
:exposeModelAsRequestAttribute(model, requetsToExpose)
将model
中的各属性放入request
中- 获取视图地址和
Servlet
原生转发器(RequestDispatcher
) requestDispatcher.forward(requestToExpose, response)
若页面正常处理,执行
afterCompletion()
方法
若上述步骤出现异常,则在
catch
块执行afterCompletion()
方法(afterCompletion()
方法总会执行)
# <mvc:annotation-driven>
- 自动注册
RequestMappingHandlerMapping
/RequestMappingHandlerAdaptor
/ExceptionHandlerExceptionResolver
三个组件 AnnotationDrivenBeanDefinitionParser
解析<mvc:annotation-driven>
标签
# HttpMessageConverter
// HttpMessageConverter
boolean canRead(Class<?>, MediaType);
boolean canWrite(Class<?>, MediaType);
list<MediaType> getSupportedMediaTypes();
T read(Class<? extends T>, HttpInputMessage); // 如@RequestBody封装对象流程
void write(T, MediaType, HttpOutputMessage); // 如@ResponseBody返回数据