百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

从Tomcat入口了解Spring MVC的请求处理流程

toyiye 2024-06-21 12:19 12 浏览 0 评论

Spring MVC是严格遵守java servlet规范的一种web框架,可以打包成war包交由web容器(Tomcat或者jetty)运行。接下来就来学习下spring mvc的运行过程以及其中的细节,如何和Tomcat无缝合作,如何和spring 本身的核心功能IOC、AOP合作。

MVC 实例

接下来就搭建一个基于maven的spring mvc的实例。

项目结构

StudentController 类

@Controller
@RequestMapping
public class StudentController {
 @RequestMapping(value = "/h")
 @ResponseBody
 public String getStudentInfo() {
 return "hello world";
 }
}

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_3_1.xsd"
 version="3.1">
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>WEB-INF/applicationContext.xml</param-value>
 </context-param>
 
 <servlet>
 <servlet-name>demo</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <init-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>WEB-INF/applicationContext.xml</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
 <servlet-name>demo</servlet-name>
 <url-pattern>/</url-pattern>
 </servlet-mapping>
 
 <listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
</web-app>

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 <context:component-scan base-package="com.demo.web" />
</beans>

pom.xml

// 插件部分
<build>
 <finalName>demo</finalName>
 <plugins>
 <plugin>
 <groupId>org.apache.tomcat.maven</groupId>
 <artifactId>tomcat7-maven-plugin</artifactId>
 <version>2.2</version>
 <configuration>
 <path>/</path>
 <uriEncoding>${file_encoding}</uriEncoding>
 <port>9912</port>
 <server>tomcat7</server>
 </configuration>
 </plugin>
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-war-plugin</artifactId>
 <version>2.5</version>
 </plugin>
 <!-- Java Compiler -->
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>3.3</version>
 <configuration>
 <source>${java_source_version}</source>
 <target>${java_target_version}</target>
 <encoding>${file_encoding}</encoding>
 </configuration>
 </plugin>
 </plugins>
 <resources>
 <resource>
 <directory>src/main/resources</directory>
 </resource>
 </resources>
</build>

然后通过mvn tomcat7:run就可以正常启动了

Tomcat 基础

在介绍spring mvc的工作原理之前,有必要介绍下web容器的一种Tomcat。Tomcat是一个开源的web容器,严格准守servlet规范,在Tomcat中包含了各种各样的组件,层层嵌套依赖,如下图所示

catalina是最核心的组件,也可以认为Tomcat是从它开始启动的,他持有1个server容器,server容器可以包含了多个service容器,每个service容器都持有了一个connector连接器以及一个engine,engine有层层包含了host、context、wrapper等。

其中engine、host、context、wrapper又分别存在各自的一个管道pipeline以及至少一个阀门valve,阀门可以为request和response添加任何外置的功能。

Tomcat启动是由各自容器的监听器调用启动的,按照上面所说的顺序依次执行启动的。

那我们常用的servlet是在哪里的么?他是被wrapper包装的,每一个wrapper持有一个servlet,所以在xml中配置了几个servlet,则就会存在多少个wrapper。并且servlet是通过ServletContext传递上下文的。在具体的URL映射的时候,会先根据各自的servlet的URL配置在Tomcat的mappingdata中体现,经过host、context、再到选择不同类型的wrapper(包含了wildcardWrappers、extensionWrappers、defaultWrapper、exactWrappers四种wrapper类型)最后才具体到某一个servlet请求上。

DispatcherServlet init 过程

DispatcherServlet类是spring mvc中实现的了servlet规范的实体类,实现了HttpServlet类,如下图

首先需要明确一点的是,DispatcherServlet也只是一个HttpServlet类,他是被wrapper调用的init(ServletConfig config)方法进入的,设置好config之后,进入到init()方法。

PS:在当前环境中,ServletConfig是StandardWrapperFacade类,可以从中获取到在xml配置的例如contextConfigLocation数据

HttpServletBean 类

 public final void init() throws ServletException {
 if (logger.isDebugEnabled()) {
 logger.debug("Initializing servlet '" + getServletName() + "'");
 }
 // Set bean properties from init parameters.
 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
 // 获取xml配置的属性,在下面贴的图片中可以看到,当DispatcherServlet没有任何配置的时候,就会抛出异常,说缺少必备的配置属性
 if (!pvs.isEmpty()) {
 try {
 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
 initBeanWrapper(bw);
 bw.setPropertyValues(pvs, true);
 // 这一步就是设置DispatcherServlet的属性值的操作
 }
 catch (BeansException ex) {
 if (logger.isErrorEnabled()) {
 logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
 }
 throw ex;
 }
 }
 // 开始真正的初始化DispatcherServlet类了
 initServletBean();
 if (logger.isDebugEnabled()) {
 logger.debug("Servlet '" + getServletName() + "' configured successfully");
 }
 }
 

FrameworkServlet 类

 protected final void initServletBean() throws ServletException {
 try {
 this.webApplicationContext = initWebApplicationContext();
 // 这个是初始化当前dispatchservlet的webApplicationContext参数
 // 然后该参数中会持有spring的IOC容器等信息,通过这个参数可以获取bean数据
 // 具体细节可看下面的代码
 initFrameworkServlet();
 // 未做任何事情,由子类实现,不过暂时为空
 }
 catch (ServletException ex) {
 this.logger.error("Context initialization failed", ex);
 throw ex;
 }
 catch (RuntimeException ex) {
 this.logger.error("Context initialization failed", ex);
 throw ex;
 }
 }
 
 protected WebApplicationContext initWebApplicationContext() {
 WebApplicationContext rootContext =
 WebApplicationContextUtils.getWebApplicationContext(getServletContext());
 // 从当前的上下文中查找是否存在跟上下文信息(通过ContextLoaderListener加载的都会存在的)
 WebApplicationContext wac = null;
 if (this.webApplicationContext != null) {
 wac = this.webApplicationContext;
 if (wac instanceof ConfigurableWebApplicationContext) {
 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
 if (!cwac.isActive()) {
 if (cwac.getParent() == null) {
 cwac.setParent(rootContext);
 }
 // 重新再刷新一次WebApplicationContext持有的内容信息
 configureAndRefreshWebApplicationContext(cwac);
 }
 }
 }
 if (wac == null) {
 wac = findWebApplicationContext();
 }
 if (wac == null) {
 // 创建一个新的WebApplicationContext信息,并设置好其parent
 // 这里需要好好看看,同样的拆分为两部分执行
 // 1、创建WebApplicationContext类,也就是XmlWebApplicationContext实体类,并设置好其环境、配置属性、父类等信息
 // 2、调用configureAndRefreshWebApplicationContext,同样的设置好其关于servlet的上下文的属性信息,最后调用wac.refresh()
 // PS: refresh()就开始了spring IOC的解析存储操作了
 wac = createWebApplicationContext(rootContext);
 // 不过这里有一步需要注意到,在refresh()中,最后会有onApplicationEvent()的操作,他会调用在DispatcherServlet类的initStrategies方法,完成URL映射、Template等操作
 // 并且设置this.refreshEventReceived为true
 }
 if (!this.refreshEventReceived) {
 // 完成了上面的刷新操作就不要再刷新了
 // 这里的onRefresh还是会调用initStrategies方法
 // 殊途同归罢了
 onRefresh(wac);
 }
 if (this.publishContext) {
 // 把当前的上下文信息也保存到ServletContext中
 // 如果注意到的函数开头的rootContext的获取方法会发现也是通过这样的方式获取的
 String attrName = getServletContextAttributeName();
 getServletContext().setAttribute(attrName, wac);
 }
 return wac;
 }

这样就完成了DispatcherServlet的初始化操作了,接下来就可以具体处理http请求了,当前这其中遗漏了很多重要的的点,例如

  • rootContext 这是怎么一回事,是必须的么,和applicationContext.xml又有什么关系呢?
  • xml配置的context-param和servlet的init-param有什么区别?

这几个点会在后续的学习笔记中再了解其原理,当前主要是介绍DispatcherServlet以及相关的东西。

DispatcherServlet URL映射以及请求处理 过程

Tomcat会通过的DispatcherServlet的servlet-mapping的属性匹配到合适的wrapper,再关联到具体的DispatcherServlet,也就意味着在web.xml确实可以配置多个servlet,只是在spring mvc中常用的就这一个而已。

HTTP请求,最后都会打到DispatcherServlet类的doService方法中,设置一些属性之后,又来到了doDispatch方法中,这个方法是核心也是最重要的http请求处理的方法,通过URL选择合适的controller,选择具体的modelandview,再渲染生成数据回调等等操作

 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);
 mappedHandler = getHandler(processedRequest);
 // 通过URL找到合适的controller,并存储在HandlerExecutionChain对着中
 // 还会检测是否进行cors跨域操作,如果存在跨域就会按照跨域的要求去处理
 // @CrossOrigin(origins="http://test.com") 可以直接放在controller的注解上
 if (mappedHandler == null || mappedHandler.getHandler() == null) {
 // 没有找到合适的处理handle,就是404了
 // 这就可以自定义配置404页面以及跳转等信息
 // response.sendError(HttpServletResponse.SC_NOT_FOUND)
 // 这里又可以引出一个问题了,如何配置404页面
 noHandlerFound(processedRequest, response);
 return;
 }
 // 检测当前获取的controller是否合适,并且得到合适的HandlerAdapter
 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 // 如果符合可以使用缓存机制,减少不必要的请求
 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)) {
 // 使用spring本身的拦截器前置处理
 return;
 }
 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 // 真正的处理请求,在本demo中会调到AnnotationMethodHandlerAdapter类中执行handle方法,返回的mv是ModelAndView
 // 解析当前handler中包含了所有的方法,匹配其中合适的方法之后,invoke调用
 // 不过这里需要注意到,类似于返回json的请求,是不需要模板渲染的,此时mv返回的是null,不过具体的json数据已经填入到了responseBody中
 applyDefaultViewName(processedRequest, mv);
 // 如果mv
 mappedHandler.applyPostHandle(processedRequest, response, mv);
 // spring 拦截器的后置处理
 }
 catch (Exception ex) {
 dispatchException = ex;
 }
 catch (Throwable err) {
 dispatchException = new NestedServletException("Handler dispatch failed", err);
 }
 // 处理结果了,如果存在异常会把exception带上,例如500错误等,按照异常处理
 // 如果存在了模板,需要经过render处理
 // 否则就直接把得到的数据当做body返回
 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);
 }
 }
 }
 }

到这里就完成了对一个普通的http请求的处理全过程,不过还是存在诸多问题没有去分析,如图

  • cors跨域是什么,如何使用
  • URL映射规则是如何完成的,以及和Tomcat的URL映射有什么关联么?
  • 模板是如何被渲染的,在xml中如何设置不同的模板的?

原创推荐

「系列教程」手写RPC框架(1),看看100个线程同时调用情况如何

「系列教程」手写RPC框架(3) zookeeper如何进行服务治理的?

「面试」new String("abc")和"abc"有什么区别?反编译看看原理吧

「源码」SpringBoot启动原理,你是否清楚?

「深入」HashMap再学习,相比Java7,Java8源码你了解多少

「面试」LRU了解么?看看LinkedHashMap如何实现LRU算法

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码