Spring MVC基本原理

Spring MVC基本原理

Spring MVC是Spring开源项目在Web开发上的一个扩展,方便我们在开发Web项目的时候使用IOC来管理我们的bean对象,我们都知道Tomcat是作为web项目的一个容器(准确点说是servlet容器),既然要在web项目中使用Spring框架,我们就必须知道,Tomcat与Spring是如何地结合,来方便我们进行web项目开发。

在开始之前,先提出以下两个问题

  • Tomcat在启动SpringMVC web项目的时候,是如何创建Spring容器?
  • Spring作为bean对象的容器,与Tomcat作为web容器是什么区别?

通过对以上问题的探索,我们基本上可以理清Spring MVC的基本设计思路

一、Tomcat启动原理

1.Tomcat基本框架

2.Tomcat启动流程
tomcat启动从执行 org.apache.catalina.startup.Bootstrap.main()方法开始。main方法主要实现的逻辑如下源码:

    public static void main(String args[]) {

        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else {
                // When running as a service the call to stop will be on a new
                // thread so make sure the correct class loader is used to
                // prevent a range of class not found exceptions.
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

我们看到在main方法中,Bootstrap首先会新建一个本类对象,然后再调用对象的init方法,init方法执行完成后,因为我们没有传参数,所以默认是start,执行start

    public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();
        }
        Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
        method.invoke(catalinaDaemon, (Object [])null);
    }

我们看到在执行start方法的时候,通过反射的方法调用了Catalina的Start方法、
Catalina.java中start方法源码

    public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal(sm.getString("catalina.noServer"));
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        if (log.isInfoEnabled()) {
            log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
        }

        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        if (await) {
            await();
            stop();
        }
    }

getServer().start() 获取Server,执行server的start方法,启动服务器
然后依次启动服务器的各个组件
Engine 引擎
表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。
Host 虚拟主机
它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml。
Context上下文
代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context对应于一个Web Application,一个Web Application由一个或者多个Servlet实例组成。
Wrapper包装器
代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

二、Spring MVC启动流程

Spring MVC中我们通常会在web.xml文件中这样配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>
    <!--  定义Spring容器加载器  -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
<!--  定义Spring MVC前端控制器  -->
    <servlet>
        <servlet-name>mvc-test</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--SpringMVC配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc-test</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

在 Servlet API中有一个ServletContextListener接口,它能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。在ServletContextListener接口中定义了处理ServletContextEvent 事件的两个方法contextInitialized()和contextDestroyed()。
  ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的方法。由于在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。
DispatchServlet初始化
DispatchServlet名如其义,它的本质上是一个Servlet,所以web容器会默认执行其中的init 方法,DispatchServlet实现了FrameworkServlet接口,类结构如下

通过以上类图,我们可以看出DispatchServlet是交由Spring容器进行管理的
阅读源码后,我们发现
DispatchServlet在进行初始化的时候,可以将它的init-param指定的Spring配置文件 作为BeanDefinition。
DispatchServlet 它提供了整合web javabean和spring application context的整合方案
所以在Spring容器中,我们可以得出以下总结
Tomcat在启动的时候,读取web.xml文件
通过ContextLoaderListener初始Root上下文,并将其作为一个Spring父容器
通过DispatcherServlet初始化WebMVC上下文,并指定Root上下文作为web mvc上下文的父容器
DispatcherServlet 的 init方法

在init方法中,会初始化Spring容器的九大组件

protected void initStrategies(ApplicationContext context) {
		// 初始化 MultipartResolver:主要用来处理文件上传.如果定义过当前类型的bean对象,那么直接获取,如果没有的话,可以为null
		initMultipartResolver(context);
		// 初始化 LocaleResolver:主要用来处理国际化配置,基于URL参数的配置(AcceptHeaderLocaleResolver),基于session的配置(SessionLocaleResolver),基于cookie的配置(CookieLocaleResolver)
		initLocaleResolver(context);
		// 初始化 ThemeResolver:主要用来设置主题Theme
		initThemeResolver(context);
		// 初始化 HandlerMapping:映射器,用来将对应的request跟controller进行对应
		initHandlerMappings(context);
		// 初始化 HandlerAdapter:处理适配器,主要包含Http请求处理器适配器,简单控制器处理器适配器,注解方法处理器适配器
		initHandlerAdapters(context);
		// 初始化 HandlerExceptionResolver:基于HandlerExceptionResolver接口的异常处理
		initHandlerExceptionResolvers(context);
		// 初始化 RequestToViewNameTranslator:当controller处理器方法没有返回一个View对象或逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,spring将会采用约定好的方式提供一个逻辑视图名称
		initRequestToViewNameTranslator(context);
		// 初始化 ViewResolver: 将ModelAndView选择合适的视图进行渲染的处理器
		initViewResolvers(context);
		// 初始化 FlashMapManager: 提供请求存储属性,可供其他请求使用
		initFlashMapManager(context);
	}

到此Spring MVC启动工作完成
我们再来看一下Spring MVC的请示处理过程

三、Spring MVC运行原理

了解Servlet的同鞋都知道,当request到达后,会调用servlet的service方法,父类HttpServlet给了一个默认的实现,通过Http Method,调用对应的doXXX方法(doGet、doPost、doPut等等)

在DispatcherServlet中它的父类有FrameworkServlet实现了service方法

@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		// 获得请求方法
		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		// 处理patch请求
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
			processRequest(request, response);
		}
		else {
			// 处理其他类型的请求,实际是调用HttpServlet中的service方法(根据请求方法,doXXX(req,reps))
			super.service(request, response);
		}
	}

处理流程如下图

最终调用DispatcherServlet的doService方法,再调用doDispatcher方法
doDispatch方法源码如下

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 实际处理时所用的request,如果不是上传请求,则直接使用接收到的request,否则封装成上传类型的request
		HttpServletRequest processedRequest = request;
		// 处理请求的处理器链(包含处理器和对应的interceptor)
		HandlerExecutionChain mappedHandler = null;
		// 是不是上传请求的标志
		boolean multipartRequestParsed = false;

		// 获取异步管理器
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			// 封装model和view的容器
			ModelAndView mv = null;
			// 处理请求过程中抛出的异常,但是不包含渲染过程中抛出的异常
			Exception dispatchException = null;

			try {
				// 检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
				processedRequest = checkMultipart(request);
				// 设置上传请求的标志
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器们)
				mappedHandler = getHandler(processedRequest);
				//  如果获取不到,则根据配置抛出异常或返回404错误
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 获得当前handler对应的HandlerAdapter对象
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				// 处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器会在返回的请求头里包含一个last_modified的属性,
				// 代表资源最后时什么时候修改的,在浏览器以后发送请求的时候,会同时发送之前接收到的Last_modified.服务器接收到带last_modified的请求后,
				// 会跟实际资源的最后修改时间做对比,如果过期了返回新的资源,否则直接返回304表示未过期,直接使用之前缓存的结果即可
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					// 获取请求中服务器端最后被修改时间
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				// 执行响应的Interceptor的preHandler
				// 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 真正的调用handler方法,也就是执行对应的方法,并返回视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// 如果需要异步处理,直接返回
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				// 当view为空时,根据request设置默认的view
				applyDefaultViewName(processedRequest, mv);
				// 执行响应的interceptor的postHandler方法
				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);
			}
			// 处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
			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);
				}
			}
		}
	}

posted @ 2021-04-26 14:47  心若向阳花自开  阅读(308)  评论(0编辑  收藏  举报