SpringMVC执行流程源码浅析

这么文章主要介绍两个问题:
1、什么是mvc
2、springmvc的主要执行流程

1、什么是mvc

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

2、SpringMVC

2.1 搭建工程

引入SpringMVC环境所需要的相关依赖。

       <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
             <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </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文件当中配置的

  <!-- springmvc前端控制器 -->
  <servlet>
  	<servlet-name>springMvc</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	<init-param>
  		<param-name>contextConfigLocation</param-name>
  		<param-value>classpath:SpringMvc.xml</param-value>
  	</init-param>
  	<!-- 在tomcat启动的时候就加载这个servlet -->
  	<load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
  	<servlet-name>springMvc</servlet-name>
  	<!-- 
  	*.action    代表拦截后缀名为.action结尾的
  	/ 			拦截所有但是不包括.jsp
  	/* 			拦截所有包括.jsp
  	 -->
  	<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步走。我在上述代码中已经用#标注出来。下面将一一介绍每个方法的具体执行过程:

#1getHandler(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.


#2HandlerAdapter 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");
	}

#3mappedHandler.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、进行参数格式化


#5mappedHandler.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。但是还未进行视图解析渲染数据


#6processDispatchResult(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当发生异常的时候,进行异常视图解析处理 ,这里就不作介绍。
#2render(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);
	}

#3mappedHandler.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方法(逆序)
发布了98 篇原创文章 · 获赞 44 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43732955/article/details/100605597