博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC实战与源码分析
阅读量:4224 次
发布时间:2019-05-26

本文共 28738 字,大约阅读时间需要 95 分钟。

文章目录

SpringMVC的基本概念

MVC模型

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种用于设计创建 Web 应用程序表现层的模式。

enter description here

SpringMVC介绍

SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring FrameWork的后续产品,已经融合在SpringWeb Flow里面。Spring框架提供了构建 Web 应用程序的全功能 MVC 模块。使用Spring可插入的MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts2等。 SpringMVC已经成为目前最主流的 MVC 框架之一,并且随着Spring3.0的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。

  • 前端控制器(DispatcherServlet)
  • 请求到处理器映射(HandlerMapping)
  • 处理器适配器(HandlerAdapter)
  • 视图解析器(ViewResolver)
  • 处理器或页面控制器(Controller)
  • 验证器( Validator)
  • 命令对象(Command 请求参数绑定到的对象就叫命令对象)
  • 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)

SpringMVC与Struts2比较

共同点:

  • 它们都是表现层框架,都是基于MVC模型编写的;
  • 它们的底层都离不开原始ServletAPI;
  • 它们处理请求的机制都是一个核心控制器;

区别:

  • Spring MVC的入口是Servlet,而Struts2是Filter
  • Spring MVC是基于方法设计的(单例),而Struts2是基于类,Struts2每次执行都会创建一个动作类(多例)。所以Spring MVC会稍微比Struts2快些。
  • Spring MVC使用更加简洁,同时还支持 JSR303,处理ajax的请求更方便 (JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。)
  • Struts2的OGNL表达式使页面的开发效率相比Spring MVC更高些,但执行效率并没有比JSTL提升,尤其是struts2的表单标签,远没有html执行效率高。

入门Demo

enter description here

准备

利用Maven搭建一个webapp项目并将目录补全,并在pom.xml中引入坐标:

UTF-8
1.8
1.8
5.0.2.RELEASE
org.springframework
spring-context
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
javax.servlet
servlet-api
2.5
provided
javax.servlet.jsp
jsp-api
2.0
provided

配置

配置核心控制器,在web.xml中:

dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
dispatcherServlet
/

在resources目录下创建SpringMVC的配置文件springmvc.xml

Controller

创建处理请求的类Controller,用注解自动注入:

// 控制器类@Controllerpublic class HelloController {
@RequestMapping(path = "/hello") public String sayHello() {
System.out.println("Hello SpringMVC"); return "success"; }}

主页面index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>    Demo

入门程序

入门程序

成功页面WEB-INF/pages/success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>    Title

入门成功

于是在主页面点击超链接后自动跳转到success.jsp页面

总结

enter description here

基于组件方式:
enter description here

在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。

使用<mvc:annotation-driven>自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在SpringMVC.xml配置文件中使用<mvc:annotation-driven>替代注解处理器和适配器的配置。

RequestMapping注解

@RequestMapping注解用于建立请求URL和处理请求方法之间的对应关系。

@Target({
ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Mappingpublic @interface RequestMapping {
}

出现位置

  1. 类上: 请求URL的第一级访问目录,此处不写的话,就相当于应用的根目录,写的话需要以/开头。 它出现的目的是为了使我们的URL可以按照模块化管理:
    例如: 账户模块:
  • /account/add
  • /account/update
  • /account/delete …
    account的RequsetMapping写在类上,使URL更加精细。
  1. 方法上: 请求URL的第二级访问目录。

属性

  • value:用于指定请求的URL,它和path属性的作用是一样的;
  • method:用于指定请求的方式;
  • params:用于指定限制请求参数的条件,它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。 例如:
    params = {"accountName"},表示请求参数必须有accountName
    params = {"moeny!100"},表示请求参数中money不能是100。
  • headers:用于指定限制请求消息头的条件。

注意: 以上四个属性只要出现2个或以上时,他们的关系是与的关系。

// 控制器类@Controller@RequestMapping(path = "/user")public class HelloController {
/** * RequestMapping注解 * @return */ @RequestMapping(path = "/testRequestMapping", method = {
RequestMethod.GET}, params = {
"username=xiao"}, headers = {
"Accept"}) public String testRequestMapping() {
System.out.println("test RequestMapping..."); return "success"; }}

请求参数绑定

表单中请求参数都是基于key=value的。 SpringMVC绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。

请求参数绑定

请求参数为username,可以直接在Controller中的方法上绑定:

@Controller@RequestMapping("/param")public class ParamController {
@RequestMapping("testParam") public String teatParam(String username) {
System.out.println("执行了..." + username); return "success"; }}

绑定实体类型

创建一个pojo类型和pojo中的pojo:

public class Account implements Serializable {
private String username; private String password; private Double money; private User user; public Double getMoney() {
return money; } public void setMoney(Double money) {
this.money = money; } public String getUsername() {
return username; } public void setUsername(String username) {
this.username = username; } public String getPassword() {
return password; } public void setPassword(String password) {
this.password = password; } public User getUser() {
return user; } public void setUser(User user) {
this.user = user; } @Override public String toString() {
return "Account{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", money=" + money + ", user=" + user + '}'; }}

内部pojo:User

public class User implements Serializable {
private String uname; private Integer age;}

在前端传入参数:

把数据封装Account类中
姓名:
密码:
金额:
用户姓名:
用户年龄:

控制器代码:

/**     * 请求参数绑定到JavaBean的类中     * @param account     * @return     */    @RequestMapping("/saveAccount")    public String saveAccount(Account account) {
System.out.println(account); return "success"; }

可以在控制台看到参数被绑定:

Account{username='xiao', password='111', money=234.0, user=User{uname='xiaoge', age=22}}

解决中文乱码

characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
characterEncodingFilter
/*

绑定集合类型

public class Account implements Serializable{
private String username; private String password; private Double money; private List
list; private Map
map;}

把数据封装到Account,类中存在list和map集合:

姓名:
密码:
金额:
用户姓名:
用户年龄:
用户姓名:
用户年龄:

自定义类型转换器

SpringMVC可以自动将表单提交的字符串类型转换成各种类型。

但一些情况如果格式不正确会出现400错误

根据日期删除账户

日期格式不正确,应该为2018/01/01才能自动类型转换为date。

配置一个自己的类型转换器StringToDateConverter:

/** * 把字符串转换日期 */public class StringToDateConverter implements Converter
{
/** * String source 传入进来字符串 * @param source * @return */ public Date convert(String source) {
// 判断 if (source == null) {
throw new RuntimeException("请您传入数据"); } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try {
// 把字符串转换日期 return df.parse(source); } catch (Exception e) {
throw new RuntimeException("数据类型转换出现错误"); } }}

在springmvc.xml中配置该转换器:

获取Servlet原生API

把原生API直接写在控制的方法参数中使用即可。

/**     * 原生的API     * @return     */    @RequestMapping("/testServlet")    public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了..."); System.out.println(request); HttpSession session = request.getSession(); System.out.println(session); ServletContext servletContext = session.getServletContext(); System.out.println(servletContext); System.out.println(response); return "success"; }

常用注解

RequestParam

当传入的参数名称与控制器方法不同时,必须使用@RequestParam注解

RequestParam

控制器:

/** * 常用注解 */@Controller@RequestMapping("/anno")public class AnnoController {
@RequestMapping("/testRequestParam") public String testRequestParam(@RequestParam(name="name") String username){
System.out.println("执行了..."); return "success"; }}

RequestBody

作用: 用于获取请求体内容。直接使用得到是key=value&key=value…结构的数据。 get请求方式不适用。

属性: required:是否必须有请求体,默认值是:true。当取值为true时,get请求方式会报错。如果取值为false,get请求得到是null。

/**     * 获取到请求体的内容     * @return     */    @RequestMapping("/testRequestBody")    public String testRequestBody(@RequestBody String body){
System.out.println("执行了..."); System.out.println(body); return "success"; }

PathVariable

restful风格

Restful(表述性状态转移)风格的URL:

请求路径一样,可以根据不同的请求方式去执行后台的不同方法。简单的说就是:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。

核心思想就是每个资源都有它的状态,不同状态下,可对它进行的操作不一样。

restful风格的URL优点

  1. 结构清晰
  2. 符合标准
  3. 易于理解
  4. 扩展方便

PathVariable注解

作用:拥有绑定url中的占位符的。例如:url中有/delete/{id},{id}就是占位符。

入门案例

控制器:

/**     * PathVariable注解     * @return     */    @RequestMapping(value="/testPathVariable/{sid}")    public String testPathVariable(@PathVariable(name="sid") String id){
System.out.println("执行了..."); System.out.println(id); return "success"; }

RequestHeader

作用:获取指定请求头的值

属性:value:请求头的名称

/**     * 获取请求头的值     * @param header     * @return     */    @RequestMapping(value = "/testRequestHeader")    public String testRequestHeader(@RequestHeader(value = "Accept") String header, HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("执行了..."); System.out.println(header); return "success"; }

CookieValue

作用:用于获取指定cookie的名称的值

属性:value:cookie的名称

/**     * 获取Cookie的值     * @return     */    @RequestMapping(value="/testCookieValue")    public String testCookieValue(@CookieValue(value="JSESSIONID") String cookieValue){
System.out.println("执行了..."); System.out.println(cookieValue); return "success"; }

ModelAttribute

出现在方法上:表示当前方法会在控制器方法执行前线执行。

出现在参数上:获取指定的数据给参数赋值。

应用场景:当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。

/**     * ModelAttribute注解     * @return     */    @RequestMapping(value="/testModelAttribute")    public String testModelAttribute(@ModelAttribute("abc") User user){
System.out.println("testModelAttribute执行了..."); System.out.println(user); return "success"; } @ModelAttribute public void showUser(String uname, Map
map){
System.out.println("showUser执行了..."); // 通过用户查询数据库(模拟) User user = new User(); user.setUname(uname); user.setAge(20); user.setDate(new Date()); map.put("abc",user); }

showUser会先于testModelAttribute执行。

SeesionAttributes

用于多次执行控制器方法间的参数共享。

@Controller@RequestMapping("/anno")@SessionAttributes(value={
"msg"})//把msg=xxx存到session域public class AnnoController {
/** * SessionAttributes的注解 * @return */ @RequestMapping(value="/testSessionAttributes") public String testSessionAttributes(Model model){
System.out.println("testSessionAttributes..."); // 底层会存储到request域对象中 model.addAttribute("msg","美美"); return "success"; }}

响应数据和结果视图

返回值

字符串

controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址

@Controller@RequestMapping("/user")public class UserController {
@RequestMapping("/testString") public String testString(Model model){
System.out.println("testString方法执行了..."); User user = new User(); user.setUsername("xiao"); user.setPassword("916521"); user.setAge(22); model.addAttribute("user",user); return "success"; }}

可以通过EL表达式在jsp中输出Model中的pojo:

${user.username}${user.password}${user.age}

void

  1. 转发
@RequestMapping("/testVoid")    public void testString(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("testVoid方法执行了..."); //请求转发 request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response); return; }
  1. 重定向:
response.sendRedirect("testRetrunString")
  1. 通过response 指定响应结果,例如响应json 数据:
response.setCharacterEncoding("utf-8");response.setContentType("application/json;charset=utf-8");response.getWriter().write("json 串");

ModelAndView

ModelAndView是SpringMVC为我们提供的一个对象,该对象也可以用作控制器方法的返回值。

@RequestMapping("/testModelAndView")    public ModelAndView testModelAndView() {
// 创建ModelAndView对象 ModelAndView mv = new ModelAndView(); System.out.println("testModelAndView方法执行了..."); // 模拟从数据库中查询出User对象 User user = new User(); user.setUsername("小凤"); user.setPassword("456"); user.setAge(30); // 把user对象存储到mv对象中,底层也会把user对象存入到request对象 mv.addObject("user", user); // 跳转到哪个页面 mv.setViewName("success"); return mv; }

返回ModelAndView类型时,浏览器跳转只能是请求转发

转发和重定向

/**     * 使用关键字的方式进行转发或者重定向     * @return     */    @RequestMapping("/testForwardOrRedirect")    public String testForwardOrRedirect(){
System.out.println("testForwardOrRedirect方法执行了..."); // 请求的转发 // return "forward:/WEB-INF/pages/success.jsp"; // 重定向 return "redirect:/index.jsp"; }

ResponseBody响应json数据

用jQuery模拟ajax请求,首先需要放行jQuery的静态资源:

发送Ajax请求:

    

控制器收到json并自动解析成实体类,再将返回对象转换为json格式:

/**     * 模拟异步请求响应     */    @RequestMapping("/testAjax")    public @ResponseBody User testAjax(@RequestBody User user){
System.out.println("testAjax方法执行了..."); // 客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中 System.out.println(user); // 做响应,模拟查询数据库 user.setUsername("haha"); user.setAge(40); // 做响应 return user; }

文件上传

文件上传的前提

  • form表单的enctype取值必须是:multipart/form-data (默认值是:application/x-www-form-urlencoded) enctype:是表单请求正文的类型
  • method属性取值必须是Post
  • 提供一个文件选择域<input type=”file” />

引入依赖:

commons-fileupload
commons-fileupload
1.3.1
commons-io
commons-io
2.4

原理

enter description here

控制器代码

前端:

选择文件:

控制器:

/**     * SpringMVC文件上传     * @return     */    @RequestMapping("/fileupload2")    public String fileuoload2(HttpServletRequest request, MultipartFile upload) throws Exception {
System.out.println("springmvc文件上传..."); // 使用fileupload组件完成文件上传 // 上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); // 判断,该路径是否存在 File file = new File(path); if(!file.exists()){
// 创建该文件夹 file.mkdirs(); } // 说明上传文件项 // 获取上传文件的名称 String filename = upload.getOriginalFilename(); // 把文件的名称设置唯一值,uuid String uuid = UUID.randomUUID().toString().replace("-", ""); filename = uuid+"_"+filename; // 完成文件上传 upload.transferTo(new File(path,filename)); return "success"; }

跨服务器上传文件

应用服务器-------->图片服务器

/**     * 跨服务器文件上传     * @return     */    @RequestMapping("/fileupload3")    public String fileuoload3(MultipartFile upload) throws Exception {
System.out.println("跨服务器文件上传..."); // 定义上传文件服务器路径 String path = "http://localhost:9090/uploads/"; // 说明上传文件项 // 获取上传文件的名称 String filename = upload.getOriginalFilename(); // 把文件的名称设置唯一值,uuid String uuid = UUID.randomUUID().toString().replace("-", ""); filename = uuid+"_"+filename; // 创建客户端的对象 Client client = Client.create(); // 和图片服务器进行连接 WebResource webResource = client.resource(path + filename); // 上传文件 webResource.put(upload.getBytes()); return "success"; }

SpringMVC的异常处理

异常处理思路:Controller调用service,service调用dao,异常都是向上抛出的,最终由DispatcherServlet找异常处理器进行异常的处理。

enter description here

首先自定义异常类:

/** * 自定义异常类 */public class SysException extends Exception{
//存储提示信息 private String message; @Override public String getMessage() {
return message; } public void setMessage(String message) {
this.message = message; } public SysException(String message) {
this.message = message; }}

自定义异常处理器:

/** * 异常处理器 */public class SysExceptionResolver implements HandlerExceptionResolver {
@Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//获取到异常对象 SysException exception = null; if (e instanceof SysException) {
exception = (SysException) e; } else {
e = new SysException("系统正在维护"); } ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",e.getMessage()); mv.setViewName("error"); return mv; }}

对异常处理器组件进行注册:

这样配置后,当服务器再次出现异常时,可直接显示异常信息:

${errorMsg}

拦截器

SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。

可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。

拦截器和过滤器的功能比较类似,也有区别

  1. 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
  2. 拦截器是SpringMVC框架独有的。
  3. 过滤器配置了/* ,可以拦截任何资源。
  4. 拦截器只会对控制器中的方法进行拦截。

创建类,实现HandlerInterceptor接口,重写需要的方法:

/*** 自定义拦截器1* @author rt*/public class MyInterceptor1 implements HandlerInterceptor{
/** * controller方法执行前,进行拦截的方法 * return true放行 * return false拦截 * 可以使用转发或者重定向直接跳转到指定的页面。 */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
System.out.println("拦截器执行了..."); return true; }}

配置拦截器:

三个拦截器方法

  1. preHandle方法是controller方法执行前拦截的方法
  • 可以使用request或者response跳转到指定的页面
  • return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
  • return false不放行,不会执行controller中的方法。
  1. postHandle是controller方法执行后执行的方法,在JSP视图执行前。
  • 可以使用request或者response跳转到指定的页面
  • 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
  1. postHandle方法是在JSP执行后执行
  • request或者response不能再跳转页面了

拦截器链

补充:一个web应用部署在Tomcat下的目录结构

webapps  |- hello    |-index.html    |-WEB-INF      |-web.xml      |-classes      |-lib
  • hello:应用目录,hello就是应用的名称(在IDEA中创建项目时是web目录,执行时自动改名为项目名);
  • index.html:应用资源。应用下可以有多个资源,例如css、js、html、jsp等,也可以把资源放到文件夹中,例如:hello\html\index.html,这时访问URL为:http://localhost:8080/hello/html/index.html;
  • WEB-INF:这个目录名称必须是大写,这个目录下的东西是无法通过浏览器直接访问的,也就是说放到这里的东西是安全的;
  • web.xml:应用程序的部署描述符文件,可以在该文件中对应用进行配置;
  • classes:存放class文件的目录;
  • lib:存放jar包的目录。

SpringMVC源码分析

参考:

一个请求的流程

enter description here

首先,用户的浏览器发出了一个请求,这个请求经过互联网到达了我们的服务器。Servlet容器首先接待了这个请求,并将该请求委托给前端控制器DispatcherServlet进行处理。接着DispatcherServlet将该请求传给了处理器映射组件HandlerMapping,并获取到适合该请求的拦截器和处理器。在获取到处理器后,DispatcherServlet还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet通过处理器适配器HandlerAdapter调用处理器的逻辑,并获取返回值ModelAndView。之后,DispatcherServlet需要根据ModelAndView解析视图。解析视图的工作由ViewResolver完成,若能解析成功,ViewResolver会返回相应的视图对象View。在获取到具体的View对象后,最后一步要做的事情就是由View渲染视图,并将渲染结果返回给用户。

在请求过程中要经过许多组件:

enter description here
从上面的流程中可以看出,Spring MVC对各个组件的职责划分的比较清晰。DispatcherServlet负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。

DispatcherServlet

HttpServlet

HttpServlet从名字上就可看出,这个类是和HTTP协议相关。该类的关注点在于怎么处理HTTP请求,比如其定义了doGet方法处理GET类型的请求,定义了doPost方法处理POST类型的请求等。我们若需要基于Servlet写Web应用,应继承该类,并覆盖指定的方法。doGet和doPost等方法并不是处理的入口方法,所以这些方法需要由service方法调用才行,这个方法的实现如下:

@Overridepublic void service(ServletRequest req, ServletResponse res)    throws ServletException, IOException {
HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; // 调用重载方法,该重载方法接受 HttpServletRequest 和 HttpServletResponse 类型的参数 service(request, response);}protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod(); // 处理 GET 请求 if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req); if (lastModified == -1) {
// 调用 doGet 方法 doGet(req, resp); } else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) {
maybeSetLastModified(resp, lastModified); doGet(req, resp); } else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } // 处理 HEAD 请求 } else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); // 处理 POST 请求 } else if (method.equals(METHOD_POST)) {
// 调用 doPost 方法 doPost(req, resp); } else if (method.equals(METHOD_PUT)) {
doPut(req, resp); } else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp); } else {
String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); }}

第二个 service 方法中,该方法根据请求类型分发请求。我们可以根据需要覆盖指定的处理方法。

DispatcherServlet族谱

继承关系图:

enter description here

  • Aware
    在Spring中,Aware类型的接口用于向Spring“索要”一些框架中的信息。比如当某个bean实现了ApplicationContextAware接口时,Spring在运行时会将当前的ApplicationContext实例通过接口方法setApplicationContext传给该bean。
  • EnvironmentCapable
    EnvironmentCapable仅包含一个方法定义getEnvironment,通过该方法可以获取到环境变量对象。
  • HttpServletBean
    HttpServletBean 是 HttpServlet 抽象类的简单拓展。HttpServletBean 覆写了父类中的无参 init 方法,并在该方法中将 ServletConfig 里的配置信息设置到子类对象中,比如 DispatcherServlet。
  • FrameworkServlet
    FrameworkServlet是Spring Web框架中的一个基础类,该类会在初始化时创建一个容器。同时该类覆写了doGet、doPost等方法,并将所有类型的请求委托给doService方法去处理。doService是一个抽象方法,需要子类实现。
  • DispatcherServlet
    DispatcherServlet协调各个组件工作。除此之外,DispatcherServlet还有一个重要的事情要做,即初始化各种组件,比如HandlerMapping、HandlerAdapter等

DispatcherServlet源码简析

enter description here

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); // 获取可处理当前请求的处理器 Handler,对应流程图中的步骤② mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response); return; } // 获取可执行处理器逻辑的适配器 HandlerAdapter,对应步骤③ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 处理 last-modified 消息头 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; } } // 执行拦截器 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; } // 调用处理器逻辑,对应步骤④ mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return; } // 如果 controller 未返回 view 名称,这里生成默认的 view 名称 applyDefaultViewName(processedRequest, mv); // 执行拦截器 postHandle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) {
dispatchException = ex; } catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err); } // 解析并渲染视图 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 {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest); } } }}private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false; if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } if (mv != null && !mv.wasCleared()) {
// 渲染视图 render(mv, request, response); if (errorView) {
WebUtils.clearErrorRequestAttributes(request); } } else {
if (logger.isDebugEnabled()) {
... } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return; } if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null); }}protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; /* * 若 mv 中的 view 是 String 类型,即处理器返回的是模板名称, * 这里将其解析为具体的 View 对象 */ if (mv.isReference()) {
// 解析视图,对应步骤⑤ view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else {
view = mv.getView(); if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } if (logger.isDebugEnabled()) {
...} try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value()); } // 渲染视图,并将结果返回给用户。对应步骤⑥和⑦ view.render(mv.getModelInternal(), request, response); } catch (Exception ex) {
if (logger.isDebugEnabled()) {
...} throw ex; }}

handlerAdapter

handlerAdapter对应的是Controller,有三种最常用的HandlerAdapter,分别是:

  1. RequestMappingHandlerAdapter
  2. HttpRequestHandlerAdapter
  3. SimpleControllerHandlerAdapter

RequestMappingHandlerAdapter主要是适配注解类处理器,注解类处理器就是我们经常使用的@Controller的这类处理器。

HttpRequestHandlerAdapter主要是适配静态资源处理器,静态资源处理器就是实现了HttpRequestHandler接口的处理器,这类处理器的作用是处理通过SpringMVC来访问的静态资源的请求。

SimpleControllerHandlerAdapter是Controller处理适配器,适配实现了Controller接口或Controller接口子类的处理器。

对于实现Controller接口的控制类,执行接口方法即可,对于注解方法的处理器,需要通过反射得到参数名,再映射参数,再执行(invoke)方法

转载地址:http://ltgmi.baihongyu.com/

你可能感兴趣的文章
Web前端学习笔记——JavaScript之事件、创建元素、节点操作
查看>>
Web前端学习笔记——JavaScript之正则表达式、伪数组、垃圾回收
查看>>
Web前端学习笔记——JavaScript 之继承、函数进阶
查看>>
Web前端学习笔记——JavaScript之面向对象游戏案例:贪吃蛇
查看>>
不做单元测试?小心得不偿失!嵌入式系统单元测试工具,自动生成测试用例
查看>>
一种实用的联网汽车无线攻击方法及车载安全协议
查看>>
光靠欺骗检测是不够的:对抗多目标跟踪的攻击
查看>>
基于微区块链的V2X地理动态入侵检测
查看>>
面向V2C场景的ADAS数字孪生模型构建方法
查看>>
Comma2k19数据集使用
查看>>
面向自动驾驶车辆验证的抽象仿真场景生成
查看>>
一种应用于GPS反欺骗的基于MLE的RAIM改进方法
查看>>
自动驾驶汽车GPS系统数字孪生建模(一)
查看>>
自动驾驶汽车GPS系统数字孪生建模(二)
查看>>
CUDA 学习(五)、线程块
查看>>
CUDA 学习(八)、线程块调度
查看>>
CUDA 学习(九)、CUDA 内存
查看>>
CUDA 学习(十一)、共享内存
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十四章 生化尖兵
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十五章 超级马里奥64
查看>>