spring源码剖析(九)springMVC源码剖析

           springMVC 相信大伙都用过,但是spring框架对于你请求的一个url 到你看到的返回结果,期间做了哪些出来呢,文件上传的封装?controller的寻找?过滤器的调用?AOP的调用?视图的解析?页面的跳转?  这些过程具体是怎么实现的,下面我们一一来向大家介绍springMVC的架构。



让我们来先从SpringMVC的初始化说起吧


SpringMVC的初始化

ContextLoaderListener初始化
我们先看看ContextLoaderListener大体的初始化逻辑时序图:


一般来说我们配置spring的时候都会在web.xml里面加上下面这段配置:
	<!--Spring ApplicationContext 载入 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
添加这段配置的目的在于,启动项目的时候让系统去回调ContextLoaderListener的contextInitialized()方法。


我们先来看看ContextLoaderListener的类继承的关系

我们看到ContextLoaderListener 继承了ServletContextListener ,由于系统启动的时候会回调ServletContextListener 的contextInitialized()方法, 所以我们可以知道,当容器启动的时候就会调用ContextLoaderListener 的contextInitialized()方法,如下:
	/**
	 * Initialize the root web application context.
	 */
	public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;
		}
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

上面这段回调的具体作用在用,项目启动的时候让系统去初始化WebApplicationContext,至于为什么要初始化WebApplicationContext呢,因为我们的DispatcherServlet需要用到它。

DispatcherServlet的初始化
让我们先看看DispatcherServlet的类继承结构先

先浏览DispatcherServlet的初始化逻辑时序图:





我们看到DispatcherServlet继承了HttpServlet类,关于HttpServlet,让我们来回顾一下普通的HttpServlet的生命周期
1)初始化阶段
  • servlet容器加载servlet类,把Servlet的.class文件中的数据读到内存中
  • servlet容器创建一个ServletConfig对象,ServletConfig对象包含了servlet的初始化配置信息。
  • servlet容器创建一个servlet对象。
  • servlet容器调用servlet对象的init方法进行初始化。
2)运行阶段
当servlet容器接受到一个请求的时候,servlet容器会创建servletRequest和servletResponse对象,然后调用service方法。

3)销毁阶段
当Web应用被终止时候,servlet容器会先调用servlet对象的destroy方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的servletConfig对象。


由于DispatcherServlet继承了HttpServlet类,所以当DispatcherServlet初始化的时候,首先会调用它的init方法。我们再HttpServletBean找到了init方法,如下:
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		try {
			//解析init-param参数,并封装到pvs中
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			//将当前的servlet类转换成bw类,目的是能配合init-param进行使用
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			//注册自定义属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			//空实现,留给子类覆盖
			initBeanWrapper(bw);
			//属性注入
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		//留给子类扩展
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

 这里的关键方法在于initServletBean(),这个方法里面主要是为了初始化WebApplicationContext所做的操作,下面我们来看看里面比较这个方法里面调用得到的比较重要的方法,跟着initServletBean调用的调用链,我们找到DispatcherServlet的initStrategies方法:

/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		//初始化MultipartResolver,这个主要是用来处理文件上传的
		initMultipartResolver(context);
		//初始化LocaleResolver,这个主要用来配置国际化的。
		initLocaleResolver(context);
		//初始化ThemeResolver,用于用控制不同的页面风格
		initThemeResolver(context);
		//初始化HandlerMappings,客户端发出request时候,DispatcherServlet会将
		//request提交给handlerMappers,然后handlerMappers根据WebApplicationContext的配置
		//传递回不同的Controller
		initHandlerMappings(context);
		//初始化适配器
		initHandlerAdapters(context);
		//异常处理
		initHandlerExceptionResolvers(context);
		//当处理器没有返回View对象获取视图名称,并且没有往response里面写数据的时候,
		//spring就会采用约定好的方式提供一个逻辑视图名称
		initRequestToViewNameTranslator(context);
		//初始化视图解析器
		initViewResolvers(context);
		//Srping提供了一个请求存储属性,可以提供给其他请求使用。
		initFlashMapManager(context);
	}

上面的初始化完成,那就说明DispatcherServlet已经可以开始 工作了,下面,让我看看DispatcherServlet的处理逻辑


DispatcherServlet处理逻辑

http有很多请求方式,有delete,post,get,options,trace,put这些方法,servlet是怎么处理这些方法的呢,让我们来看看FrameworkServlet的源代码;

	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

其实delete,options,trace以及put的请求方式都一样,都是最终会转交到processRequest方法去处理。
让我们看看大体的处理逻辑时序图:


下面我们来看看核心的处理逻辑代码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 {
				//如果是multipartContext类型的request,则转换为MultipartHttpServletRequest类型的request
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 根据request信息,寻找具体的处理handler
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 根据当前对的handler 查找对应的handlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				//处理 带last-modified的header
				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;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 真正激活handler 并返回视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//处理默认的视图名称,就是替视图加上前后缀
				applyDefaultViewName(request, mv);
				//interceptor的调用
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}//处理返回结果
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Error err) {
			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}




下面我们来一一解析,具体实施过程:

MultipartContont 类型的request处理
        如果处理的request带有文件上传处理,那么就会转换成MultipartHttpServletRequest类型的request


 根据request信息,寻找具体的处理handler
         这个里面的实现过程稍稍复杂一点,不过大体的逻辑还是不变的,具体是:
当spring初始化的时候,会把所有的控制器(controller)都加载到HandlerMapper 里面去,然后根据Request里面请求的url,定位到具体的控制器(controller)里面,然后返回handler,返回的handler,包含了拦截器执行的调用链。


根据当前对的handler 查找对应的handlerAdapter
           handlerAdapter也是在DispatcherServlet初始化的时候已经把所有的Adapter初始化好了,放到HandlerAdaptes里面去了,有人也许会问adapter是什么啊?我们看看具体的实现就知道了,下面是获取adapter的具体实现:
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			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");
	}

让我们再看看ha.support()是判断什么的:



如果适配器属于右侧中类型指定的适配器,那么就可以返回给程序使用,当然,里面我们最常用的就是controller类。具体实现大家有兴趣的可以深入的看看调用执行链。


处理 带last-modified的header
     如果处理器实现了LastModified接口,那么就可以做到缓存的效果,具体是:如果页面第一次请求成功了,在发起第二次请求的时候,如果请求头包含的时间与服务器端的返回的lastmodified时间一致,那么就会返回HTTP304的状态吗(只需要响应头,内容为空,这样就节省了带宽)。


真正激活handler 并返回视图
      这一步骤 具体的话就是调用用户的逻辑处理了。


处理默认的视图名称,就是替视图加上前后缀
      在spring配置的视图解析器里面,我们一般会配置返回视图的前缀以及视图指定的后缀名称,这一步骤的目的就是根据返回的视图配置前后缀的名称。



interceptor的调用
     在这里,执行拦截器的调用,下面是具体实现:

	/**
	 * Apply postHandle methods of registered interceptors.
	 */
	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);
			}
		}
	}

处理返回结果
        根据逻辑处理返回的结果,执行跳转,或者response的回写。





  好了,DispatcherServlet的大体初始化逻辑,和处理逻辑已经分析完了,以上的描述,如有不对,请各位大神指出,谢谢~



  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值