这么文章主要介绍两个问题:
1、什么是mvc
2、springmvc的主要执行流程
MVC全名是Model View
Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
如我们传统的经典的mvc模式、JSP+Servlet+JavaBean 。
那么MVC框架又是什么呢?
答:是为了解决传统MVC模式(Jsp + Servlet + JavaBean)的一些问题而出现的框架。
传统mvc模式的问题
1、所有的Servlet和Servlet映射都要配置在web.xml中,如果项目太大,web.xml就太庞大,并且不能实现模块化管理。
2、接收参数比较麻烦,需要通过request.getParamter()然后再将接收的参数 设置到model当中 不同直接通过model接收
3、…
常用的mvc框架有
struts
webwork
Struts2(因2012年爆出系统漏洞问题、现在已经很少使用)
Spring MVC
这篇文章也就将介绍上面提到的常用MVC框架中的一个SpringMVC
引入SpringMVC环境所需要的相关依赖。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.3.23.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
创建用于测试的User
@DateTimeFormat(pattern=“yyyy-MM-dd”)是用于参数格式化的。
public class User {
private String userName;
private int age;
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date date;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
创建一个UserController
@Controller
public class UserController {
@PostMapping("/test")
public String test(User user, Model model){
model.addAttribute("user",user);
return "hello";
}
}
要了解SpringMVC的执行过程,那么就先稍微介绍一下它的几个核心重要组件:
1、DispatcherServlet对请求URL进行解析,根据配置规则,所有的请求都会经过这个核心控制器
2、HandlerMapping处理器映射器:根据请求获得相应的Handler已经相关连接器。返回一个HandlerExecutionChain,处理器执行器链
3、HandlerAdapter处理器适配器:根据得到的处理器执行链,获得合适的处理器适配器,然后处理器适配器调用目标Handler方法
4、ViewResolver视图解析器:目标Handler被调用之后,或返回一个ModelAndView,而视图解析器的目的就是解析它。获得对应的View后model数据。
上面4个组件我们需要配置添加到spring容器当中:
1、视图解析器需要自己配置,并且添加到spring容器当中
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 真正的页面路径 = 前缀 + 去掉后缀名的页面名称 + 后缀 -->
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
2、使用注解添加最新的处理器映射器和处理器适配器
<mvc:annotation-driven />
3、核心控制器DispatcherServlet,是我们在web.xml文件当中配置的
<servlet>
<servlet-name>springMvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:SpringMvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springMvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
现在我们开始分析一下底层是如何执行的
先来到页面,填写user的信息,然后提交表单
在DispatcherServlet
核心控制器的doDispatch方法,需要重点关注这个方法。这是处理页面发送过来的请求的主要方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//根据请求processedRequest 获得相应的mappedHandler #1
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获得方法适配器 #2
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用所有拦截器的 applyPreHandle方法 #3
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行目标方法 返回ModelAndView #4
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 设置视图的名称
applyDefaultViewName(processedRequest, mv);
// 方法调用链条调用所有拦截器的applyPostHandle方法 #5
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 开始处理ModelAndView #6
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
部分代码省略........
}
上面这个方法在执行过程中,重要的步骤可以分为7步走。我在上述代码中已经用#
标注出来。下面将一一介绍每个方法的具体执行过程:
#1
getHandler(processedRequest)根据请求processedRequest 获得相应的mappedHandler (HandlerExecutionChain) 代码如下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 遍历容器中所有的处理器映射器 然后通过处理器映射器 返回 HandlerExecutionChain(hander+interceptors)
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
如果获取的mappedHandler为null获得目标方法为null,那么将返回404.
#2
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())根据目标方法Handler来 获得方法适配器 HandlerAdapter
代码如下:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//同样是获得容器中所有的方法适配器
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
// 如果找到支持该hanler的适配器 那么返回
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
#3
mappedHandler.applyPreHandle(processedRequest, response) boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
获得容器中的所有拦截器调用拦截器的preHandle方法。(如果该方法返回false;那么就直接return,方法结束。)
由此可以知道preHandle方法的调用时期是。目标方法执行之前。
#4
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());执行目标方法,返回ModelAndView
,执行目标方法时候主要进行参数绑定,而参数绑定主要分为3个方面:
1、参数转换 2、参数校验 3、参数格式化
ha.handle---->handleInternal()----->invokeHandlerMethod()------>invokeAndHandle()----->invokeForRequest()
invokeForRequest
方法的源码如下:
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获得方法参数 #a
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;
}
上面的invokeForRequest
方法中,主要分为两个步骤 1、获得方法参数 2、利用反射调用目标方法。主要看看获得方法参数时 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;
}
// 返回解析之后的参数
return args;
部分代码省略.......
}
在获得方法参数的过程,原来经过了一个方法resolveProvidedArgument(parameter, providedArgs)
来解析每一个参数。而我们上面提到的参数绑定,也就是在这里方法中执行的。代码如下:
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//部分代码省略......
// 是否有ModelAttributes注解标注的model 如果有那么取出域中的改model ;如果没有那么新创建出一个model
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, webRequest));
// 创建binder
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 进行参数绑定:
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindRequestParameters(binder, webRequest);
就是对参数进行绑定的真正操作:(具体代码就不分析了)
1、进行参数转换 内置120个参数转换器(可以自定义)
2、进行参数校验
3、进行参数格式化
#5
mappedHandler.applyPostHandle(processedRequest, response, mv);调用链条调用所有拦截器的applyPostHandle方法 源码如下
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
获得容器当中所有的拦截器。逆序调用所有拦截器的postHandle
方法。
由此该方法的调用时期是。目标方法执行完毕。返回了ModelAndView。但是还未进行视图解析渲染数据
#6
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);这操作主要就是进行处理结果ModelAndView。我们可以发现。不论我们目标Handler
的返回值是什么,返回结果都会是ModelAndView
。代码如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
// 如果有异常 进行异常处理 #1
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
// 解析mv #2
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
// 调用拦截器的AfterCompletion方法 #3
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
上述代码主要分为3个步骤。
#1
当发生异常的时候,进行异常视图解析处理 ,这里就不作介绍。
#2
render(mv, request, response);方法。进行解析具体代码如下:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获得区域信息
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
// 部分代码省略....
if (mv.isReference()) {
//解析视图 通过视图解析器
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
// 部分代码省略....
// 视图渲染,填充数据
view.render(mv.getModelInternal(), request, response);
// 部分代码省略....
}
}
resolveViewName方法如下:
获得容器中所有的视图解析器,然后将视图名字 解析成对应的View 然后返回
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
view.render(mv.getModelInternal(), request, response) 将逻辑视图解析成 物理视图然后渲染数据。
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
// 将 将要展示的数据合并
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
// 将数据 暴露到request域当中
//并进行 转发或重定向 跳转到物理视图
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
#3
mappedHandler.triggerAfterCompletion(request, response, null)
代码如下:
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
// 获得所有的 拦截器 逆序调用afterCompletion方法
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
这里是逆序调用拦截器的afterCompletion。
由此可知拦截器的afterCompletion的调用时期是,视图渲染完成之后。
到此处,将SpringMVC的大致执行过程讲述了一遍,那么我们进行总结一下:
1、核心控制器拦截请求
2、处理器映射器根据请求返回执行器链条(handler+inteceptors)
如果没有请求对应的handler 那么就抛出404错误
3、根据目标方法找到合适的处理器适配器
4、调用拦截的post方法(顺序调用)
如果返回null直接结束
5、调用目标方法、返回modelAndView
1)解析参数时进行参数绑定:
1、参数绑定
2、参数转换(内置120个转换器)
3、参数校验
6、调用拦截器的post方法;(逆序调用)
7、处理结果ModelAndView(view+ModelMap)
1、如果是 ModelAndViewDefiningException错误视图;handlerExceptionResolver错误解析器进行解析
2、如果正常返回ModelAndView,那么进行视图解析【render方法】逻辑视图===》物理视图
1、解析区域信息
2、视图解析器解析视图
获得所有容器中的视图解析器,进行遍历【InternalResourceViewResolver】
找到能解析的的视图解析器,解析完成返回view (InternalResourceView,RedirectView)
3、view 视图进行视图渲染,填充数据
暴露model对象到request域当中、获得转发器,进行转发
或者重定向
4、调用拦截器的compare方法(逆序)