Spring原理之MVC

Posted demystify

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring原理之MVC相关的知识,希望对你有一定的参考价值。

一、Servlet基础

1、Servlet生命周期

      Servlet生命周期规定了Servlet如何被加载、 实例化、初始化、处理客户端请求,以及何时服务结束。通过javax.servlet.Servlet接口中的init、service、destory这些API来表示,所有Servlet必须直接或间接实现GenericServlet 或HttpServlet抽象类。

(1)、加载和实例化

      Servlet容器负责 Servlet的加载和实例化。可以发生在容器启动时,或者延迟初始化直到容器决定由请求需要处理时。

(2)、初始化

      Servlet对象被加载完毕后,容器需要初始化该Servlet实例。容器通过调用Servlet实例的init方法完成初始化,该方法接受唯一的ServletConfig接口实现的对象为参数。(每个Servlet实例对应一个ServletConfig, ServletConfig允许Servlet访问由Web应用配置信息提供的键-值对初始化参数,还提供给Servlet去访问一个ServletContext对象 )。

【】 初始化时的错误条件

       初始化阶段,servlet实现可能抛出UnavailableException或ServletException异常。在这种情况下,Servlet放置到活动服务中,而且Servlet容器必须释放它。如果初始化没有成功,destory方法不应该被调用。

2、请求处理

      Servlet完成初始化后,Servlet容器就可以使用它处理客户端请求。客户端请求由ServletRequest类型的request对象表示。Servlet封装响应并返回给请求的客户端,该响应由ServletResponse类型的response对象表示。

      HTTP请求场景下,容器提供的请求和响应对象类型分别是HttpServletRequest、HttpServletResponse。

      Servlet容器可以并发地发送多个请求到Servlet的service方法,Servlet开发者需要关心service方法的多线程并发处理。一种方案是Servlet容器可以通过串行化访问Servlet的 请求,或者维护一个 Servlet实例池完成该需求。对于没有实现SingleThreadModel接口的Servlet,但它的service方法(或者通过service分派的方法)是通过synchronized关键字定义的,Servlet容器不能使用实例化方案,并且只能使用序列化请求进行处理。

【】请求处理时的异常

       Servlet在处理一个请求时可能抛出ServletException或UnavaliableException异常。ServletException表示在处理请求时出现了一些错误,容器应该采取适当的措施清理掉这个请求。

       UnavailableException表示servlet目前无法处理请求,临时性的 或者永久性的。

        如果UnavaliableException表示的是一个永久性的不可用,Servlet容器必须从服务中移除这个Servlet,调用它的destory方法,并释放Servlet实例。所有被容器拒绝的请求,都会返回一个SC_NOT_FOUND(404)响应。

       如果UnavaliableException表示的是一个临时性的不可用,容器可以选择在临时不可用的这段时间内不路由任何请求到Servlet。所有在这段时间被容器拒绝的请求,都会返回一个SC_SERVICE_UNAVAILABLE(503)响应状态码。且同时会返回一个Retry-After头指示此Servlet什么时候可用。

【】异步处理

       有时,Filter、Servlet在生成响应之前必须 等待一些资源或事件,在Servlet中等待是一个低效的操作,因为这是阻塞操作,从而白白占用一个线程或其他一些受限资源。多个线程等待一个 缓慢资源,可能引起线程饥渴,且降低整个Web容器的服务质量。

       Servlet 3.0引入了异步处理请求的能力,使 线程可以返回到容器,从而执行更多任务。

3、终止服务(End of service)

      当Servlet 容器确定servlet应该从服务中移除时,将调用Servlet接口的destory方法。

      在servlet容器调用destory方法之前,它必须让当前正在执行service方法的任何线程完成执行,或者超过了服务器定义的时间限制。一旦调用了servlet实例的destory方法,容器无法再路由其他请求到该servlet实例了。如果容器需要再次使用该servlet,必须用该servlet类的一个新实例。在destory方法完成后,servlet容器必须释放servlet实例以便被垃圾回收。

4、Request对象的 生命周期

       每个request对象只在servlet的service方法的作用域内,或过滤器的doFilter方法的作用域内有效,除非该组件启用了异步处理并调用了request对象的startAsync方法。在发生异步处理的情况下,request对象一直 有效,直到调用AsyncContext的complete方法。容器通常会重复利用request对象,以避免创建request对象的性能开销。

       不建议在上述范围之外保持startAsync方法还没有被调用的请求对象的引用,因为这样可能产生不确定的结果。

5、Response的生命周期

       每个响应对象是只有在servlet的service方法的范围内或在filter的doFilter方法范围内是有效的,除非该组件关联的请求对象已经开启异步处理。如果相关的请求已经启动异步处理,那么直到AsyncContext的complete方法被调用,请求对象一直有效。为了避免响应对象创建的性能开销,容器通常回收响应对象。注意,超出之上描述的作用范围可能导致不确定的行为。 

6、ServletContext

      ServletContext(Servlet上下文)接口 定义了servlet运行在Web应用的视图。容器供应商负责提供Servlet容器的ServletContext接口的实现。Servlet可以使用ServletContext 对象记录事件,获取URL引用的资源,存取当前上下文的其他Servlet可以访问的属性。

        每一个部署到容器的Web应用都有一个Servlet接口的实例与之关联。在容器分布在多台虚拟机的情况下,每个JVM的每个Web应用将有一个ServletContext实例。如果容器内的Servlet没有部署到Web应用中,则隐含的作为“默认”Web应用的一部分,并有一个默认的ServletContext。在分布式的容器中,默认的ServletContext是非分布式的且仅存在于一个JVM中。

7、过滤器

       过滤器是一种代码重用的技术,它可以改变HTTP请求的内容、响应,以及header信息。过滤器通常不产生响应或像servlet那样对请求作出响应,而是修改或调整到资源的请求,修改或调整来自资源的响应。

       当容器接收到传入的请求时,它将获取列表中的第一个过滤器并调用doFilter方法,传入ServletRequest和ServletResponse,和一个它将使用的FilterChain对象的引用。

       service方法必须和应用到servlet的所有过滤器运行在同一个线程中。

       过滤器的核心概念是包装请求或响应,以便它可以覆盖行为执行过滤任务。

二、SpringMVC

      Spring IoC容器是一个独立的模块,如果要在 web环境中使用,需要Spring为IoC设计一个启动过程,把IoC容器导入,并在Web容器中建立起来。这个启动过程是和web容器的启动过程 集成在一起的。Spring MVC是建立在IoC容器的基础上的。

<一>、上下文在Web容器中的启动

1、IoC容器启动的基本过程

      IoC容器的启动过程就是建立上下文的过程。ContextLoaderListener实现了Servlet规范中的ServletContextListener接口,IoC容器的建立是通过ContextLoaderListener的初始化建立的。在Servlet容器启动过程中会调用ServletContextListener的contextInitialized方法,具体的载入过程是ContextLoaderListener交给ContextLoader来完成的,在ContextLoader中,完成了两个IoC容器建立的基本过程,一个是在Web容器中建立起双亲IoC容器,另一个是生成相应的WebApplicationContext并将其初始化。

       载入IoC容器过程中的3个类的关系:

2、Web容器中的上下文设计

    由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上,还有一个与Web MVC相关的上下文(用来保存控制器DispatcherServlet需要的MVC对象),并作为根上下文的子上下文。

    为了方便在Web环境中使用IoC容器,Spring为Web应用提供了上下文的扩展接口WebApplicationContext。类层次关系如下:

       在WebApplicationContext中可以看到相关的常量设计,比如ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,这个常量用来索引在ServletContext中存储的根上下文。这个接口类只有一个getServletContext方法,通过它可以得到当前Web容器的Servlet上下文环境。

       在启动过程中,Spring会默认使用XmlWebApplicationContext作为IoC容器。XmlWebApplicationContext是从ApplicationContext继承下来的,在基本的ApplicationContext功能的基础上,增加了对Web环境和XML配置定义的处理。加载IoC容器的过程中,也会有loadBeanDefinition对BeanDefinition的载入。在Web环境中对定位BeanDefinition的Resource有特别的要求,默认的BeanDefiniton的配置路径为/WEB-INF/applicationContext.xml。

3、ContextLoader的设计与实现

         ContextLoader是Spring应用在Web容器中的启动器。

      ServletContext定义了Servlet运行在web应用的全局视图。ServletContextListener是ServletContext的监听者,如果ServletContext发生变化,会触发相应的事件, 监听者一直对这些事件进行监听。比如,在服务器启动时,ServletContext被创建,ServletContextListener的contextInitialized()方法会被调用,服务器关闭时,ServletContext将被销毁时ServetContextListener的contextDestroyed()方法会被调用。

         因此,可以看到,当服务器启动时,SerContextListener通过contextInitialized方法将具体的初始化工作交给ContextLoader,ContextLoader 完成根上下文的创建。这个根上下文在Web容器中是单例,会被存到web容器的ServletContext中去。存取这个根上下文的路径是 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的属性中定义的路径,默认为:ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"

       ContextLoader对IoC容器初始化的伪代码如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) 
    // 先判断在ServletContext中是否已经存在根上下文,如果存在就抛出异常
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) 
		throw new IllegalStateException("不能初始化上下文,因为已经存在一个根上下文");
	

	try 
		//创建根上下文,并先存储到本地变量中,确保即使ServletContext挂了,根上下文也是可用的。
		if (this.context == null) 
			this.context = createWebApplicationContext(servletContext);
		
			
		//创建的根上下文是ConfigurableWebApplicationContext类型
		ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			
		// 如果上下文还没有被刷新(尚未提供服务),就根据根上下文加载双亲上下文;
		ApplicationContext parent = loadParentContext(servletContext);
			
		// 并将双亲上下文关联到根上下文中;
		cwac.setParent(parent);
		
		// 配置并刷新根上下文(IoC容器加载过程)
		configureAndRefreshWebApplicationContext(cwac, servletContext);
			
		// 将创建的根上下文存储到ServletContext的属性中,属性名为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		return this.context;
	
	catch (RuntimeException | Error ex) 
		logger.error("上下文初始化失败");
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	

上述过程就是IoC容器在Web容器中的启动过程。

<二>、Spring MVC的设计与实现

      DispatcherServlet处理流程:

      在整个Spring MVC框架中,DispatcherServlet处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作。我们先看一下请求处理的大致流程:(Servlet容器以Tomcat为例)

(1)、Tomcat启动,对DispatcherServlet进行实例化,然后调用它的init()方法进行初始化,在这个初始化过程中完成了:对web.xml中初始化参数的加载;建立WebApplicationContext(Spring MVC的IoC容器);进行组件的初始化;

(2) 、客户端发出请求,由Tomcat接收到这个请求,如果匹配DispatcherServlet在web.xml中配置的映射路径,Tomcat就将请求转交给DispatcherServlet处理;

(3)、DispatcherServlet从容器中取出所有HandlerMapping实例并遍历,每个HandlerMapping会根据请求信息,通过自己实现类中的方式去找到处理该氢气的Handler,并且将这个Handler与一堆HandlerInterceptor封装成一个HandlerExecutionChain对象,一旦有一个HandlerMapping可以找到Handler则退出循环。

(4)、DispatcherServlet取出HandlerAdapter组件,根据已经找到的Handler,再从所有HandlerAdapter中找到可以处理该Handler的HandlerAdapter对象;

(5)、执行HandlerExecutionChain中所有拦截器的preHandler()方法,然后再利用HandlerAdapter执行Handler,执行完成得到ModelAndView,再一次调用拦截器的postHandler()方法;

(6)、利用ViewResolver将ModelAndView或是Exception(可解析为ModelAndView)解析成View,然后View会调用render()方法在根据ModelAndView中的数据渲染出页面;

(7)、最后再依次调用拦截器的afterCompletion()方法,这一次请求就结束了。

参看:https://www.cnblogs.com/tengyunhao/p/7518481.html

    DispatcherServlet的启动过程就是Spring MVC的 启动过程。在完成对ContextLoaderListener的初始化之后,Web容器开始初始化DispatcherServlet。DispatcherServlet会建立自己的上下文持有Spring MVC的Bean对象,在建立这个自己持有的IoC容器时,会从ServletContext中得到根上下文作为DispatcherServlet持有上下文的双亲上下文,再对自己持有的上下文进行初始化,最好把自己持有的这个上下文保存到ServletContext中,供以后检索和使用。

DispatcherServlet类的继承关系如下:

     DispatcherServlet的工作大致可以分为两个 部分:一个是初始化,有initServletBean()启动,通过initWebApplicationContext()方法最终调用DispatcherServlet的initStrategies方法。另一个是对HTTP请求进行响应。

     作为一个Servlet,Web容器会调用Servlet的doGet()和doPost()方法,在经过FrameworkServlet的processRequest()简单处理后,会调用DispatcherServlet的doService()方法,在这个方法调用中封装了doDispatch()。

1、DispatcherServlet的启动和初始化

     DispatcherServlet在初始化过程中会建立自己的上下文,并将根上下文设置为双亲上下文。根上下文是和Web应用相对应的一个上下文,而DispatcheServlet持有的上下文是和Servlet对应的一个上下文。 一个根上下文可以作为许多Servlet上下文的双亲上下文。

2、MVC处理HTTP分发请求

 (1)、HandlerMapping的配置和设计原理

    在DispatcherServlet初始化完成时,在上下文环境中的所有HandlerMapping都会被加载。DispatcherServlet中有一个List存放当前Servlet用到的HandlerMapping,List中的一个元素对应一个具体的handlerMapping,每个handlerMapping持有一个名为handlerMap的HashMap,将URL和handler作为键值对。

 HandlerMapping接口定义了getHandler方法,通过这个方法可以获得HTTP请求对应的HandlerExecutionChain。HandlerExecutionChain的实现中持有一个Interceptor链和一个handler对象,这个handler对象就是HTTP请求对应的Controller,拦截器链中的拦截器可以为handler对象提供功能的增强。

(2)、使用 HandlerMapping完成请求的映射处理

      HandlerMapping的getHandler方法是实际 使用HandlerMapping完成请求映射的地方,在getHandler方法中经过一系列对HTTP请求进行解析和匹配handler操作,得到了与请求对应的 HandlerExecutionChain。HandlerExecutionChain是增强版的controller。至此HTTP请求与对应的controller的映射已经绑定完成。

(3)、Spring MVC对HTTP请求的分发处理

      对HTTP请求的处理是在servlet的doService()方法中完成的。DispatcherServlet与其他HttpServlet一样,可以通过doService()来响应HTTP的请求。

      DispatcherServlet的doService方法,代码如下:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception 
	logRequest(request);

	// Keep a snapshot of the request attributes in case of an include,
	// to be able to restore the original attributes after the include.
	Map<String, Object> attributesSnapshot = null;
	if (WebUtils.isIncludeRequest(request)) 
		attributesSnapshot = new HashMap<>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) 
			String attrName = (String) attrNames.nextElement();
			if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) 
				attributesSnapshot.put(attrName, request.getAttribute(attrName));
			
		
	

	// Make framework objects available to handlers and view objects.
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

	if (this.flashMapManager != null) 
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) 
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
	

	try 
		doDispatch(request, response);
	
	finally 
		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) 
			// Restore the original attribute snapshot, in case of an include.
			if (attributesSnapshot != null) 
				restoreAttributesAfterInclude(request, attributesSnapshot);
			
		
	

      对请求的处理实际上是由doDispatch()来完成,时序图如下:

(4)、Spring MVC视图的呈现

   Spring MVC对常用的视图都提供了支持,比如JPS/JSTL视图、FreeMaker 视图。Velocity视图、Excel、PDF视图等。

View视图的继承关系如下:

 

以上是关于Spring原理之MVC的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC系列之Spring MVC的前端控制器(DispatcherServlet)工作流程及原理

#yyds干货盘点#30个类手写Spring核心原理之MVC映射功能

Spring系列之手写一个SpringMVC

Spring MVC官方文档学习笔记之DispatcherServlet

spring mvc 是啥

Java 之Spring MVCSpring MVC面试题