天天看点

springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

1.前言

Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中。正式名 称“ Spring Web MVC”来自其源模块的名称(spring-webmvc),但它通常被称为“ Spring MVC”。 从Servlet到SpringMVC 最典型的MVC就是JSP + servlet + javabean的模式。

springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

弊端:

  1. xml下配置servlet的映射非常麻烦 开发效率低
  2. 必须要继承父类、重写方法 侵入性强
  3. 如果想在一个Servlet中处理同一业务模块的的功能分发给不同方法进行处理非常麻烦
  4. 参数解析麻烦:单个参数(转换类型)—>pojo对象 Json文本—>pojo对象
  5. 数据响应麻烦:pojo对象—>json … Content-type
  6. 跳转页面麻烦, 对path的控制、 如果使用其他模板也很麻烦 、设置编码麻烦…等等…

所以SpringMVC 就是在Servlet的基础上进行了封装,帮我把这些麻烦事都给我们做了。

其实SpringMVC请求原理很简单:就是用一个DispatcherServlet 封装了一个Servlet的调度中心, 由调度中心帮我们调用我们的处理方法: 在这个过程中调度中心委托给各个组件执行具体工作 ,比如帮我们映射方法请求、帮我解析参数、 调用处理方法、响应数据和页面等

2.SpringMVC的具体执行流程:

Spring MVC 是围绕前端控制器模式设计的,其中:中央 Servlet DispatcherServlet 为 请求处理流程提供统一调度,实际工作则交给可配置组件执行。这个模型是灵活的且开放的,我们可以通过自己去定制这些组件从而进行定制自己的工作流。

springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet
  • DispatcherServlet: 前端调度器 , 负责将请求拦截下来分发到各控制器方法中
  • HandlerMapping: 负责根据请求的URL和配置@RequestMapping映射去匹配,匹配到会返回Handler(具体控制器的方法)
  • HandlerAdaper: 负责调用Handler-具体的方法-返回视图的名字Handler将它封装到 ModelAndView(封装视图名,request域的数据)
  • ViewReslover: 根据ModelAndView里面的视图名地址去找到具体的jsp封装在View对象中
  • View:进行视图渲染(将jsp转换成html内容 --这是Servlet容器的事情了) 最终response 到的客户端
  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。 a. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦 截器)一并返回给DispatcherServlet。
  3. DispatcherServlet根据处理器Handler获取处理器适配器 HandlerAdapter,执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
  4. 执行处理器Handler(Controller,也叫页面控制器)。 a. Handler执行完成返回ModelAndView b. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
  5. DispatcherServlet将ModelAndView传给ViewReslover视图解析器 a. ViewReslover解析后返回具体View
  6. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
  7. DispatcherServlet响应用户。

org.springframework.web.servlet.DispatcherServlet#doDispatch 这个方法体现了整个springmvc请求流程:

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);
				// 进行映射
				// 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain (包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				// 找到最合适的HandlerAdapter
				//DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter,
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				
				// Process last-modified header, if supported by the handler.  HTTP缓存相关
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				// 前置拦截器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					// 返回false就不进行后续处理了
					return;
				}

				// 执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
				// 执行处理器Handler(Controller,也叫页面控制器)。
				// Handler执行完成返回ModelAndView
				// HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				// 如果mv有  视图没有,给你设置默认视图
				applyDefaultViewName(processedRequest, mv);
				//后置拦截器
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			// 渲染视图
			// DispatcherServlet将ModelAndView传给ViewReslover视图解析器  
			// ViewReslover解析后返回具体View 
			// DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。 
			// DispatcherServlet响应用户。
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", 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);
				}
			}
		}
	}
           

3.Spring整合SpringMVC

特性: 说到Spring整合SpringMVC唯一的体现就是父子容器:

通常我们会设置父容器(Spring)管理Service、Dao层的Bean, 子容器 (SpringMVC)管理Controller的Bean .

子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。

在SSM框架整合的时候都曾在web.xml配置过这段:

<!‐‐spring 基于web应用的启动‐‐> 
<listener>
    <listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>
</listener>
<!‐‐全局参数:spring配置文件‐‐> 
<context‐param>但是它的作用是什么知道吗?   
    <param‐name>contextConfigLocation</param‐name>
    <param‐value>classpath:spring‐core.xml</param‐value>
</context‐param>
<!‐‐前端调度器servlet‐‐>
<servlet>
    <servlet‐name>dispatcherServlet</servlet‐name>
    <servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class>
    <!‐‐设置配置文件的路径‐‐>
    <init‐param>
        <param‐name>contextConfigLocation</param‐name>
        <param‐value>classpath:spring‐mvc.xml</param‐value> 
    </init‐param>
    <!‐‐设置启动即加载‐‐>
    <load‐on‐startup>1</load‐on‐startup>
</servlet>
<servlet‐mapping>
    <servlet‐name>dispatcherServlet</servlet‐name>
    <url‐pattern>/</url‐pattern>
</servlet‐mapping>
           

3.1 零配置(零xml)的放式来说明SpringMVC的原理!!

零配置SpringMVC实现方式: 那没有配置就需要省略掉web.xml 怎么省略呢?

在Servlet3.0提供的规范文档中可以找到2种方式:

  1. 注解的方式

    a. @WebServlet

    b. @WebFilter

    c. @WebListener

    但是这种方式不利于扩展,并且如果编写在jar包中tomcat是无法感知到的。

  2. SPI的方式

    在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式

    springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

也就是让你在应用META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer ——即SPI规范

SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不要被这个名字唬到了, 其实很好理解的一个东西: 其实就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录 (META-INF/services)放上以接口全类名为命名的文件,文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法,从而完成扩展。

4.实现基于SPI规范的SpringMVC

  • 此类继承AbstractAnnotationConfigDispatcherServletInitializer
  • getRootConfigClasses 提供父容器的配置类
  • getServletConfigClasses 提供子容器的配置类
  • getServletMappings 设置DispatcherServlet的映射
package com.hzg.javaconfig.starter;

import com.hzg.javaconfig.config.RootConfig;
import com.hzg.javaconfig.config.WebAppConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * 在tomcat源码中:
 *  org.apache.catalina.startup.ContextConfig#lifecycleEvent(org.apache.catalina.LifecycleEvent
 *  	org.apache.catalina.startup.ContextConfig#configureStart
 *          org.apache.catalina.startup.ContextConfig#webConfig
 *             org.apache.catalina.startup.ContextConfig#processServletContainerInitializers
 *
 *	 //通过spi的机制加载 classpath下ServletContainerInitializer
 *	 WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
     detectedScis = loader.load(ServletContainerInitializer.class);
 *
 * Created by xsls on 2019/7/31.
 */

public class HzgStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	/**
	 * 方法实现说明:IOC 父容器的启动类
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[]{RootConfig.class};
	}

	/**
	 * 方法实现说明 IOC子容器配置 web容器配置
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[]{WebAppConfig.class};
	}

	/**
	 * 方法实现说明
	 * @return: 我们前端控制器DispatcherServlet的拦截路径
	 */
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}
}

           
package com.hzg.javaconfig.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;

import static org.springframework.context.annotation.FilterType.ASSIGNABLE_TYPE;


/**
* @desc: 类的描述:IOC根容器,不扫描Controller的注解
* @version: 1.0
*/
@Configuration
@ComponentScan(basePackages = "com.hzg",excludeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}),
		@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {

}
           
package com.hzg.javaconfig.config;

import com.hzg.javaconfig.interceptor.HzgInterceptor;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


/**
* @desc: web子容器
*/
@Configuration
@ComponentScan(basePackages = {"com.hzg"},includeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc   // = <mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer{

	/**
	 * 配置拦截器
	 * @return
	 */
	@Bean
	public HzgInterceptor HzgInterceptor() {
		return new HzgInterceptor();
	}

	/**
	 * 文件上传下载的组件
	 * @return
	 */
	@Bean
	public MultipartResolver multipartResolver() {
		CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
		multipartResolver.setDefaultEncoding("UTF-8");
		multipartResolver.setMaxUploadSize(1024*1024*10);
		return multipartResolver;
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(hzgInterceptor()).addPathPatterns("/*");
	}


	/**
	 * 方法实现说明:配置试图解析器
	 */
	@Bean
	public InternalResourceViewResolver internalResourceViewResolver() {
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setSuffix(".jsp");
		viewResolver.setPrefix("/WEB-INF/jsp/");
		return viewResolver;
	}
}
           
package com.hzg.javaconfig.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HzgInterceptor implements HandlerInterceptor {

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("HzgInterceptor....preHandle");
		return true;
	}
}
           

5.SPI的方式SpringMVC启动原理

流程图:

springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

SpringMVC 大致可以分为 启动 和请求

启动源码流程:

  1. 外置Tomcat启动的时候通过SPI 找到我们应用中的/META- INF/service/javax.servlet.ServletContainerInitializer
    springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet
  2. 调用SpringServletContainerInitializer.onStartUp()
    springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

    a. 调用onStartUp()前会先找到 @HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,传入到OnStartup的 webAppInitializerClasses参数中,并传入Servlet上下文对象。

    b. 重点关注这组类:他们组成了父子容器

    springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet
  3. 找到所有WebApplicationInitializer的实现类后,不是接口、不是抽象则通过反射进行实例化(所以,你会发现内部实现类都是抽象的,你想让其起作用我们必须添加一个自定义实现类,在下文提供我的自定义实现类)
  4. 调用所有上一步实例化后的对象的onStartup方法
    springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

6. 初始化ContextLoaderListener

ContextLoaderListener加载过程比较简单:

外置tomcat会帮我们调用ContextLoaderListener#contextInitialized 进行初始化

  1. xml的方式下会判断容器为空时创建父容器
  2. 在里面会调用父容器的refresh方法加载
  3. 将父容器存入到Servlet域中供子容器使用
springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet

7. 初始化DispatcherServlet

外置tomcat会帮我们调用DispatcherServlet#init() 进行初始化—>重点关注: initWebApplicationContext方法

  1. (getServletContext())获得父容器(从之前的 Servlet域中拿到)
  2. cwac.setParent(rootContext);给子容器设置父容器
  3. 调用configureAndRefreshWebApplicationContext(cwac);
springmvc01-spring请求流程源码解析1.前言2.SpringMVC的具体执行流程:3.Spring整合SpringMVC4.实现基于SPI规范的SpringMVC5.SPI的方式SpringMVC启动原理6. 初始化ContextLoaderListener7. 初始化DispatcherServlet