SpringWeb MVC 是怎么工作的,SpringMVC的原理,SpringMVC源码 分析。

介绍

SpringWeb MVC是Spring Framework中的一部分,当我们需要使用spring框架创建web应用的时候就需要引入springweb mvc。对于程序员来说,我们只需要增加@Controller ,@RequestMapping注解然后,浏览器中的请求就会到达springweb应用。我们只需要在 controller中编写相关逻辑即可。然而,请求是在哪里接收的?@Controller ,@RequestMapping注解做了什么?本文我们来探讨一下。

从一个项目开始

本文假定你已经能熟练的使用springmvc。为了展开后续的讨论,假设我们新建了一个spring-mvc-demo的项目。并由此项目来展开讨论。项目中有一个控制器,代码如下:

@Controller
@RequestMapping("/app")
public class AppController {
@RequestMapping(method=RequestMethod.GET,value="/hello")
public @ResponseBody String hello(HttpServletRequest request,String name) {
return "Hello,"+name;
}
}

控制器写好之后,我们将项目打车war包,放入tomcat容器中,并使用8080端口启动tomcat,运行项目,然后在浏览器中输入http://localhost:8080/app/hello?name=world.

我们在浏览器中可以看到:Hello,world的输出。

我们先记住这个例子,下面我们带着一些疑问继续看,这个请求是怎么被接收到的?请求是怎么交给AppController处理的?

Servlet是Java Web应用的基石

当你在浏览器中输入 http://loalhost:8080/ ,按下enter建,然后请求命中了服务器,这是怎么发生的?又如何从这个请求中得到浏览器中可见的页面?

本例中,我们给出的是一个简单的spring-mvc应用,并放入了tomcat中(springboot 内嵌tomcat启动其实也是一样的)。 Tomcat 是一个servlet容器,这点我想每个Java程序员都十分清楚,我们在没有使用spring-mvc之前,就是使用servlet+jsp来开发web应用。

由于Tomcat是一个web容器,每一个发送给Tomcat服务器的HTTP请求自然会被一个Java Servlet处理。所以,SpringMvc 必定有一个servlet,SpringWeb应用的入口必定是一个Servlet,这应该不难想到。

简单来说,Servlet是任何Java Web应用的核心组件(除非你不用servlet规范,比如你使用netty)。Servlet它是低层次的,并且不会像MVC那样强加于特定的编程模式。它只是可以让你写一个偶一个Servlet,一个HTTP Servlet可以接受一个HTTP请求,然后处理它,并返回一个响应。

而springmvc 就是使用了一个大的servlet,下面我们就来说这个大的servlet。

DispatcherServlet是Spring MVC的核心

上面我们已经提到Servlet 是Java web应用的基石Spring应用入口必定是一个Servlet,这个Servlet 其实就是DispatcherServlet

作为WEB应用的开发人员,我们真正想做的是抽象出以下繁琐和模板化的任务,并专注于有用的业务逻辑:

  • 映射一个HTTP请求到某个处理方法。

  • 将HTTP请求数据,和头信息转换成数据对象(DTO / domain object)。

  • 模型 - 视图 - 控制器 之间的交互。

  • 从DTO,域对象等生成响应

Spring DispatcherServlet提供了这些。它是Spring Web MVC框架的核心, 这个核心组件接收所有请求到您的应用程序。

DispatcherServlet具有很强的可扩展性。 例如,它允许您插入不同的现有或新适配器以执行大量任务:

  • 将请求映射到应该处理它的类或方法(HandlerMapping接口的实现)
  • 使用特定模式处理请求,例如常规servlet,更复杂的MVC工作流或者POJO bean中的方法(HandlerAdapter接口的实现)
  • 通过名称解析视图,允许您使用不同的模板引擎,XML,XSLT或任何其他视图技术(ViewResolver接口的实现)
  • 通过使用默认的Apache Commons文件上传实现或编写自己的MultipartResolver来解析multipart请求
  • 使用任何LocaleResolver实现解决语言环境,包括Cookie,会话,Accept HTTP标头或用于确定用户期望的语言环境的任何其他方式

处理HTTP请求

首先,让我们来追踪一个简单的HTTP请求到达controller中的方法,然后返回到 浏览器/客户端的处理过程。

DispatcherServlet 有一个很长的继承关系。它的继承关系是这样的:

GenericServlet <- HttpServlet <- HttpServletBean <- FreamworkServlet <- DispatcherServlet

GenericServlet

GenericServlet是Servlet规范的一部分,它并不直接关注HTTP。它定义了一个service()方法用来接收传递过来的请求,并产生响应。(这里的请求和响应不是指HTTP请求)

public abstract void service(ServletRequest req, ServletResponse res) 

  throws ServletException, IOException;

注意,这里的参数中的ServletRequest,ServletResponse并不是和HTTP协议绑定的,Http有具体协议Servlet。

HttpServlet

顾名思义,HttpServlet类就是规范中定义的基于HTTP的Servlet实现。

更实际的说,HttpServlet是一个具有service()方法实现的抽象类,它通过HTTP方法类型分割请求,大致如下所示:

protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { String method = req.getMethod();
if (method.equals(METHOD_GET)) {
// ...
doGet(req, resp);
} else if (method.equals(METHOD_HEAD)) {
// ...
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
// ...
}

根据请求的不同, get,post方法会分别被不同方法处理。

HttpServletBean

上面我们展示过 DispatcherServlet的继承关系,在这个继承链中,HttpServletBean是进入spring的第一个层次。从HttpServletBean开始往下的几个servlet都是spring中的类。HttpServletBean 就是一个servlet,它继承自HttpServlet,就像是我们在使用servlet+jsp开发时候定义的servlet一样。

根据servlet的生命周期我们知道,servlet会被容器初始化,初始化时候,其init()方法会被调用。在springmvc框架中 HttpServletBean使用从web.xml或WebApplicationInitializer收到的servlet init-param值来注入bean的属性。

在请求应用程序的情况下,为这些特定的HTTP请求调用doGet(),doPost()等方法。

FrameworkServlet

FrameworkServlet将Servlet功能与Web应用程序上下文集成,实现ApplicationContextAware接口。但它也能够自行创建Web应用程序上下文。

正如上面所说,FrameworkServlet的超类HttpServletBean将init-param注入为bean属性。所以,如果servlet的contextClass init-param中提供了context类的名字,那么这个context类的实例将会被创建,用作应用的context。否则,将会使用XmlWebApplicationContext作为默认的。

DispatcherServlet: 统一请求处理

有了上面铺垫,我们这里可以开始关键的内容,即DispatcherServlet 统一请求处理。在Springmvc的项目中,我们通常会在web.xml配置一个servlet,如下:

    <servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

上面我们提到,DispatcherServlet继承关系,其父类中正儿八经的servlet类是HttpServletBean这个servlet类是应用启动入口。其生命周期的第一阶段init()方法完成初始化工作。

doService()方法设置请求信息

DispatcherServlet 初始化之后,便可以工作了。当请求到达之时,会调用其doService()方法。doService()方法的代码如下:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 删除一下‘非核心代码’方便查看
// 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());
try {
doDispatch(request, response);
}
finally {
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
} }

可以看到,doService()方法先设置一些 request信息,这些信息在后续的处理过程中会用到。设置完后,它调用doDispatch() 方法。

doDispatch()方法分发请求

doService()方法最终调用了doDispatch(),看名知意,这个方法是做分发工作的。其代码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
throws Exception {
//删除一些代码方便阅读
HandlerExecutionChain mappedHandler = null;
try {
ModelAndView mv = null;
Exception dispatchException = null; try {
// 删除一些代码方便阅读
mappedHandler = getHandler(processedRequest, false);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex; // 这里捕获了异常TypeMismatchException
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
}
finally {
// 删除一些代码
}
}

这个方法主要作用就是找到合适的 handler 来处理请求。handler通常是一个某个类型的对象,并且不限定特定的接口。因此spring需要找到这个handler的适配器。这个Handler通常是一个HandlerMethod实例,

为了找到与请求匹配handler,spring需要从已注册的HandlerMapping接口实现类里边去找。这个查找过程就是在上面的getHandler() 方法完成得到的是一个HandlerExecutionChain。 这里体现了责任链模式

这个getHandler() 会遍历一个HandlerMapping的map。由于我们一般都使用注解形式:@Controller,@RequestMapping注解。因此这里找到HandlerMapping实现就是RequestMappingHandlerMapping

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

getHandlerAdapter()方法找到最终的handler适配器,找到的适配器就是RequestMappingHandlerAdapter,(因为我们使用的是@RequestMapping注解形式)。

本例中,我们定义了AppController 的hello()方法,并用@Controller,@RequestMapping对其分别进行注解,因此这里得到的适配器HandlerAdapter 所适配HandlerMethod就是 AppController 的hello()方法的 。

HandlerAdapter处理请求

上面通过 确定了HandlerAdapter之后,就要执行handle() 方法了,即上面代码中,try语句块里边的ha.handle()。handle()方法定义为:

ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;

这个方法有两种处理方式:

  • 自己将数据写到response,然后return null

  • 返回一个ModelAndView 对象,让DispatcherServlet自己来处理后面渲染工作。

HandlerAdapter有多重类型,例如

SimpleControllerHandlerAdapter处理spring mvc 的controller实例(注意,不要把这里的controller实例和@Controller注解POJO混淆,这里controller 指的是org.springframework.web.servlet.mvc.Controller ),并返回ModelAndView,代码如下:

public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
return ((Controller) handler).handleRequest(request, response);
}

SimpleServletHandlerAdapter 适配的是 Servlet作为request handler的情况,Servlet是不知道MovelAndView的,所以,它的方法并不负责渲染页面,因此没有返回ModelAndView,只是返回null:

public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
((Servlet) handler).service(request, response);
return null;
}

RequestMappingHandlerAdapter 就是我们上面提到的,用来处理@Controller和@RequestMapping注解的handler。

渲染视图

handle()方法调用之后, DispatcherServlet 可以得到一个ModelAndView,当然也可能是null。对于ModelAndView不为null的时候,DispatcherServlet 将会调用render()方法。ModelAndView中可能已经包含了一个view或者只是一个view的名字。如果controller方法指定的是一个字符串形式的视图名字,那么就需要进行试图查找:

for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}

render()方法完成之后,最终的HTML页面会被发送至浏览器端。

当然,springmvc不仅能渲染出页面,也可以返回JSON形式或者XML形式。这种情况controller方法一般都是由@RequestBody标注的。这种情况就需要 HttpMessageConverter,例如渲染JSON的时候可以使用Jackson包,我们要返回的对象将由,MappingJackson2HttpMessageConverter来转换。


到此,我们就大概说完了springmvc的整个流程。所以,springmvc其实就是一个大的Servlet,接收请求,分发执行请求,我们的每一个controller中的方法都是一个handler,然后最终渲染视图。

spring源码分析400异常处理流程及解决方法

spring如何启动的?这里结合spring源码描述了启动过程

SpringMVC是怎么工作的,SpringMVC的工作原理的更多相关文章

  1. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  2. springMVC 返回类型选择 以及 SpringMVC中model,modelMap.request,session取值顺序

    springMVC 返回类型选择 以及 SpringMVC中model,modelMap.request,session取值顺序 http://www.360doc.com/content/14/03 ...

  3. 周鸿祎——不要抱着打工心态去工作,而是把工作当创业(附读书笔记) good

    360周鸿祎反而最为开明,他说“不要抱着打工心态去工作,而是把工作当创业”,就像是,鼓励你拿着公司的工资,锻炼自己的能力,为自己以后创业积累资源和人脉(读书笔记:真是天底下最好的机会,天底下没有比工作 ...

  4. 把Excel工作簿的每个工作表提取出来保存为新工作簿

    平台:MS office 2010 任务:有个excel工作簿,其中有上百个工作表,要求把每一个工作表全部保存为新工作簿,如果一个一个复制出来太傻了,可以用excel自带的VB解决. 方法:打开工作簿 ...

  5. springmvc 项目完整示例07 设置配置整合springmvc springmvc所需jar包springmvc web.xml文件配置

    前面主要是后台代码,spring以及mybatis的整合 下面主要是springmvc用来处理请求转发,展现层的处理 之前所有做到的,完成了后台,业务层和持久层的开发完成了 接下来就是展现层了 有很多 ...

  6. excel如何将一个工作薄中的工作表生成独立的工作薄

    excel如何将一个工作薄中的工作表生成独立的工作薄  '用vba代码 Sub 另存所有工作表为工作簿() Dim sht As Worksheet Application.ScreenUpdatin ...

  7. 【集中工作薄】 当前文件夹中所有Excel文件中 多个工作簿的第一个工作表 复制到工作簿中

    功能:当前文件夹中所有Excel文件中 多个工作簿的第一个工作表 复制到工作簿中 Sub Books2Sheets() '定义对话框变量 Dim fd As FileDialog Set fd = A ...

  8. springMVC入门(二)------springMVC入门案例

    简介 本案例主要完成了springMVC的基本配置,可针对响应的HTTP URL返回数据与视图 一.###web.xml的配置 要使springMVC生效,首先需要对web.xml进行配置,配置spr ...

  9. 换工作?试试远程工作「GitHub 热点速览 v.22.40」

    近日,潜在某个技术交流群的我发现即将毕业的小伙伴在焦虑实习.校招,刚好本周 GitHub 热榜有个远程工作项目.不妨大家换个思路,"走"出去也许有更多的机会.当然,除了全球的远程工 ...

随机推荐

  1. NOIP 2019游记

    Update on 2019.4.20 禁赛预定

  2. MySQL物理备份 xtrabackup

    MySQL 备份之 xtrabackup | innobackupex Xtrabackup 介绍 Xtrabackup 是一个对 InnoDB 做数据备份的工具,支持在线热备份(备份时不影响数据读写 ...

  3. package.json 中script脚本传入参数问题

    "build:test": "cross-env BUILD_ENV=dev nuxt build", 最近项目中通过传入自定义参数区分测试和正式环境,但是发现 ...

  4. macos + vs code + grep 进行多文件搜索

    macos下,打开vs code后,选择view菜单,点击terminal, 在vs code中打开控制台.执行以下grep命令: grep -w "p" -n -r  /User ...

  5. windows 7 命令修改IP地址

    netsh interface ip set address "本地连接"  static 172.17.15.97 255.255.255.0 172.17.12.1

  6. eclipse出现jdk版本更新导致无法启动

    启动出现的问题,截图: 解决办法: 1.找到自己jdk安装的bin目录,我的安装目录是:F:\jdk\bin 2.修改eclipse安装目录下的eclipse.ini,添加 -vmF:\jdk\bin ...

  7. npx 是什么?

    参考链接:https://www.jianshu.com/p/cee806439865

  8. 2018-2019-2 20165234 《网络对抗技术》 Exp0 Kali安装 Week1

    Week1 kali安装 一.下载系统镜像文件 首先下载系统镜像,进入kali官网,在Downloads中选择Download Kali Linux. 我选择的是64位版本,点击HTTP下载镜像文件. ...

  9. BUGKU login3

    先看的wp,呢么来复现一遍,emmmmmm,尝试一波,用户名输入admin后,密码随便输,发现提示password error,呢么填其他用户名的话,发现提示username does not exi ...

  10. [Kubernetes]谈谈容器跨主机网络

    继上篇文章:[Kubernetes]浅谈容器网络,自己给自己挖的坑,这篇文章来谈谈容器跨主机网络. 要理解容器"跨主通信"的原理,就要来谈谈 Flannel 这个项目. Flann ...