spring mvc 原理初探

原创
2018/01/11 16:36
阅读数 65

一、简单的springMVC的执行流程

  1. 请求访问由容器路由到dispatcherServlet的doDispatch方法上进行分配,在这一过程中只处理了作为servlet部分的逻辑,并未开始执行映射
  2. 在doDispatch方法中主要做了以下几点:
    • 校验是否为文件上传模式
    • 将request保存一份异步执行备份,以支持异步执行(异步执行是基于servlet的异步请求功能的封装)
    • (1)获取包含handler映射器和Integerceptor多个拦截器的执行链,在这一步获取的是一个可以处理该次请求的映射器类,并不是实际映射到的业务类
    • 当获取不到映射器则进行适当的错误处理
    • (2)获取adapaer适配的执行类
    • 对GET的缓存支持,通过lastModified检测修改时间。在我们使用@ResquestMapping注解进行映射时,映射器不支持lastModified,直接返回 -1,静态页面请求支持此方法
    • (3)执行前置拦截器链,全部成功继续执行
    • (4)执行映射器,到这一步之前并没有对具体的业务类操作,所以参数绑定的过程和映射为modelAndView过程都在这一步才真正开始
    • 检查是佛进行异步处理,当为异步处理时直接返回(异步处理主要是为了减轻请求线程的阻塞,提升并发量)
    • (5)拦截器后处理
    • (6)视图处理器处理
    • 最后在finally块中处理异步请求的返回,和文件上传的资源释放
	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); //检查是否需要转换成multipart处理
				multipartRequestParsed = (processedRequest != request); //调整标识用于释放资源

				//(1)获取对应的handler以及Interceptor拦截器链
				mappedHandler = getHandler(processedRequest);

                   

				if (mappedHandler == null) { //404处理
					noHandlerFound(processedRequest, response);
					return;
				}

				//(2)根据给定的handler获取支持的adpter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())

				// Process last-modified header, if supported by the handler.
                //当请求方法为Get时可以根据lastModified进行页面缓存
				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;
					}
				}

                //(3)执行拦截器链
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				//(4)处理handler
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                //是否注册为异步处理
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
                
				applyDefaultViewName(processedRequest, mv);
               //(5)拦截器后处理
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			//异常处理

            //(6)交给视图解析器进行视图处理
			processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
		}
	
		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);
				}
			}
		}
	}

 

二、HandlerMapping的选择

(1)、spring提供的HandlerMapping

  在这里仅讨论4.3.13版本的spring家族

SimpleUrlHandlerMapping:以注册键值对的方式映射路径

    在使用springmvc时,有一个静态文件映射处理的问题,也就是为了让spring将我们的html、css、js等静态资源放行需要配置一个指定路径,默认为    /**   /webjar/**两个,这两个路径就是存贮在SimpleUrlHandlerMapping中的,对应的则是我们指定的路径地址。

 BeanNameUrlHandlerMapping:通过注册的beanname绑定URL

    通过beanname绑定URL在形式上就类似于Struts2的映射方式,即一个类对应一个映射路径,这么做的好处呢,是可以支持一部分方法 ,类似于lastmodified,需要实现接口,这就只能在SimpleUrlHandlerMapping和beannameUrlHandlerMapping上实现。

<bean name="/hello" class="com.demo.HelloController"/>
<!-- 通过注册的类名称映射路径 -->

RequestMappingHandlerMapping:通过@RequestMapping注解映射的路径

 通过注解映射也是我们最常用的映射方式,这种映射路径被存贮在该类中

(2)、如何选择

    在执行getHandler(processedRequest);是以这样的顺序选择的:

    对注册的所有Handler进行循环循环顺序如下

  • SimpleUrlHandlerMapping  匹配一次,匹配的该对象只存储了浏览器图标一个路径 /**/ico
  • RequestMappingHandlerMapping 匹配一次,当注解的路径能匹配到则返回该对象,并退出循环
  • beannameUrlHandlerMapping 匹配一次,成功退出循环,返回该对象
  • SimpleUrlHandlerMapping  再次匹配,此处默认存储了静态资源映射设置好的映射路径,通常在我们将配置/**路径映射到静态资源后都会在此匹配成功,返回ResouceHttpHandlerMapping对象(代表静态资源处理的对象)

    通过映射的匹配顺序可以发现,所有的静态资源都是在类映射不成功的情况下才会进行静态资源查找。所以如果出现静态资源名与类的URL映射路径重名会出现无法访问静态资源的情况。

例如:/hello 映射到hello()方法,则/hello.html 也会匹配到这个路径上。(spring默认忽略扩展名)

获取匹配映射路径的对象

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
            /*在通过对比request获取对应的映射对象和拦截器对象,
            并封装到executionChain 执行链中返回*/
			HandlerExecutionChain handler = hm.getHandler(request);//关键语句
			}
           //略
		return null;
	}


protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		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;
			}
		}

 

三、执行器选择

(1)spring提供的执行器对象

1. SimpleControllerHandlerAdapter

适配实现Controller接口的类

2.RequestMappingHandlerAdapter

用于处理@RequestMapping注解的

3HttpRequestHandlerAdapter

该对象处理返回时,不会生成modelAndview对象,用于响应静态资源

(2)选择顺序

RequestMappingHandlerAdapter

HttpRequestHandlerAdapter

SimpleControllerHandlerAdapter

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部