您的位置 首页 教育

[Study]SpringMVC

文章目录 一、概述及示例1.1 MVC 思想介绍1.2 SpringMVC 概述1.3 HelloWorld1…

文章目录

  • 一、概述及示例
    • 1.1 MVC 思想介绍
    • 1.2 SpringMVC 概述
    • 1.3 HelloWorld
    • 1.4 请求响应流程
  • 二、请求映射
    • 2.1 注解的使用位置
    • 2.2 value、method 属性
    • 2.3 params、headers 属性
    • 2.4 Ant 风格编程
    • 2.5 路径中的占位符
  • 三、请求参数获取
    • 3.1 原生 Servlet
    • 3.2 形参列表
    • 3.3 @RequestParam
    • 3.4 @RequestHeader
    • 3.5 @CookieValue
    • 3.6 POJO 对象
    • 3.7 中文乱码
  • 四、域数据共享
    • 4.1 原生 Servlet
    • 4.2 ModalAndView
    • 4.3 Model
    • 4.4 Map
    • 4.5 ModelMap
    • 4.6 request 域数据共享的选择
    • 4.7 session、servletContext 域数据共享
  • 五、视图
    • 5.1 SpringMVC 中的视图
    • 5.2 ThymeleafView
    • 5.3 InternalResourceView
    • 5.4 RedirectView
    • 5.5 view-controller
    • 5.6 配置 JSP 视图解析器
  • 六、RESTful
    • 6.1 RESTful 风格介绍
    • 6.2 HiddenHttpMethodFilter
    • 6.3 GET 请求获取信息
    • 6.4 POST 请求保存信息
    • 6.4 DELETe 请求删除信息
    • 6.5 PUT 请求修改信息
    • 6.6 DefaultServletHandler
  • 七、信息报文转换
    • 7.1 HttpMessageConverter
    • 7.2 @RequestBody
    • 7.3 RequestEntity
    • 7.4 @ResponseBody
    • 7.5 ReponseEntity
    • 7.6 @RestController
  • 八、文件上传、JSON、AJAX
    • 8.1 文件上传
    • 8.2 JSON 处理
    • 8.3 AJAX 处理
  • 九、拦截器
    • 9.1 拦截器的配置与使用
    • 9.2 多个拦截器的执行顺序
  • 十、异常处理器
    • 10.1 异常处理器概述
    • 10.2 异常处理器(xml)
    • 10.3 异常处理器(注解)
    • 十一、SpringMVC 总结
    • 11.1 常用组件
    • 11.2 WebApplicationContext 初始化流程
    • 11.3 DispatcherServlet 处理请求流程
    • 11.4 SpringMVC 的执行流程总结归纳
    • 11.5 SpringMVC 基于 xml 文件的配置
    • 11.6 SpringMVC 基于注解的配置

一、概述及示例
1.1 MVC 思想介绍

  1. MVC 是一种软件架构的思想,将软件按照模型(model)、视图(view)、控制器(controller)来划分

  2. M(Model):模型层,指工程中的 JavaBean,作用是处理数据。JavaBean分为以下两类

    1. 一类称为实体 Bean:用于存储业务数据,如 Student、User 等
    2. 一类称为业务 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问
  3. V(View):视图层,指工程中的 html 或 JSP 等页面,作用是与用户进行交互,展示数据

  4. C(Controller):控制层,指工程中的 Servlet,作用是接收请求和响应浏览器

  5. MVC 的工作流程

    1. 用户通过视图层(view)发送请求到服务器,在服务器中请求被 Controller 接收
    2. Controller 调用相应的 Model 层处理请求
    3. 模型层处理完毕后将结果返回到Controller
    4. Controller 根据请求处理的结果找到相应的 View
    5. 由视图层渲染数据后最终响应给浏览器

1.2 SpringMVC 概述

SpringMVC 是 Spring 的一个后续产品,是 Spring 的一个子项目。SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案,在表述层框架历经 Strust、WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 JavaEE 项目表述层开发的首选方案。相比其它框架,SpringMVC 具有以下显著特点

  1. Spring 家族原生产品,与 IOC 容器等基础组件无缝对接
  2. 基于原生的 Servlet,通过功能强大的前端控制器 DispatcherServlet 对请求和响应进行统一处理
  3. 可插拔式组件即插即用,内部组件化程度高,想要什么功能配置相应组件即可
  4. 性能卓著,尤其适合现代大型、超大型互联网项目要求

1.3 HelloWorld

  1. 新建 maven 工程,给普通的 maven 工程添加 web 模块(添加 web.xml、指定工程上下文路径到 webapp)

  2. 在 pom.xml 中修改 maven 工程的打包方式为 war 包并引入 SpringMVC 相关依赖

    
    war
    
    
    
        
        
            org.springframework
            spring-webmvc
            5.3.1
        
        
        
            ch.qos.logback
            logback-classic
            1.2.3
        
        
        
            javax.servlet
            javax.servlet-api
            3.1.0
            provided
        
        
        
            org.thymeleaf
            thymeleaf-spring5
            3.0.12.RELEASE
        
    
    
  3. 在 web.xml 中注册前端控制器 DispatcherServlet

    
    
        DispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        <!-- 指定 SpringMVC 配置文件路径,若不指定则 mvc.xml 应位于 WEB-INF 目录下且名称为 -servlet.xml -->
        
            contextConfigLocation
            classpath:mvc.xml
        
        
        1
    
    
        DispatcherServlet
        <!-- / 不能匹配 .jsp 请求路径的请求(避免在访问 jsp 页面时,该请求被 DispatcherServlet 处理,从而找不到相应的页面)
             
    @Controller
    public class HelloController {
        
        @RequestMapping("/")
        public String toIndex() {
            // 返回视图名称即 hello.html,返回 hello 后由 Thymeleaf 视图解析器解析
            return "hello";
        }
    }
    
  4. 创建前端页面 hello.html

    
    
        
        Hello World
    
    
    

    Hello World

  5. 部署 Tomcat 访问

  6. 项目结构如下

  7. 访问成功

1.4 请求响应流程

  1. 浏览器发送请求,若请求地址符合前端控制器的 url-pattern,该请求就会被前端控制器 DispatcherServlet 捕获
  2. 前端控制器会读取 SpringMVC 的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中 @RequestMapping 注解的 value 属性值进行匹配
  3. 若匹配成功,该注解所标识的控制器方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析
  4. 视图解析器加上前缀和后缀组成视图的路径,通过 Thymeleaf 对视图进行渲染,最终转发到视图所对应页面

二、请求映射
2.1 注解的使用位置

  1. @RequestMapping 标识一个类:设置映射请求路径初始信息
  2. @RequestMapping 标识一个方法:设置映射请求路径的具体信息
@Controller
@RequestMapping("/test")
public class RequestMappingController {
	// 此时浏览器具体请求路径为:/test/testRequestMapping
    @RequestMapping("/testRequestMapping")
    public String testRequestMapping(){
        return "success";
    }
}

2.2 value、method 属性

  1. @RequestMapping 注解的 value 属性通过请求的请求地址匹配请求映射(value 属性必不可少)。value 属性是一个 String 类型的数组,表示该控制器方法可以响应多个对应的请求地址

    @RequestMapping(value = {"/testRequestMapping", "/test"})
    public String testRequestMapping(){
        return "success";
    }
    
    
    
    
        
        首页
    
    	
        get request
  2. @RequestMapping 注解的 method 属性是通过请求的请求方式来匹配请求映射。method 属性是一个 RequestMethod 类型的数组,表示该请求映射能够匹配多种请求方式的请求(不配置 method 属性的时表示当前控制器方法可以匹配所有请求方式的请求)

    @RequestMapping(value = {"/testRequestMapping", "/test"}, method = {RequestMethod.GET, RequestMethod.POST})
    public String testRequestMapping(){
        return "success";
    }
    
  3. 若当前请求的请求地址满足请求映射的 value 属性,但是请求方式不满足 method 属性,则浏览器报错 405:Request method ‘POST’ not supported

  4. method 的派生注解:目前浏览器只支持 get 和 post,若在 form 表单提交时,为 method 设置了其他请求方式的字符串(put 或 delete),则按照默认的请求方式 get 进行处理

    1. @PostMapping:处理 post 请求的映射(增)
    2. @DeleteMapping:处理 delete 请求的映射(删)
    3. @PutMapping:处理 put 请求的映射(改)
    4. @GetMapping:处理 get 请求的映射(查)

2.3 params、headers 属性

  1. @RequestMapping 注解的 params 属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系

    1. param:请求必须携带 param 参数
    2. !param:请求不能携带 param 参数
    3. param=value:请求必须携带 param 参数且 param=value
    4. param!=value:请求必须携带 param 参数且 param!=value
    // 请求必须携带 username 和 password 参数且值为 bear(否则报错 400参数不匹配)
    // 请求头中必须携带 Connection 和 Host 参数且值为指定值(否则报错 404)
    @RequestMapping(
        value = "/testParamsAndHeaders",
        params = {"username=bear", "password=bear"},
        headers = {"Connection=keep-alive", "Host=localhost:8080"})
    public String testParamsAndHeaders() {
        return "success";
    }
    
    1. testParamsAndHeaders
    
  2. headers 属性的使用方法与 params 一致

2.4 Ant 风格编程

// 1. ? 匹配任意单个字符
@RequestMapping("/?testAnt")
// 2. * 零到多个字符
@RequestMapping("testAnt")

1. testAnt -> ?

2. testAnt -> *

3. testAnt -> **

2.5 路径中的占位符

@RequestMapping("/testPath/{username}/{password}")
public String testPath(@PathVariable("username") String username, @PathVariable("password") String password) {
    System.out.println("username:" + username);
    System.out.println("password:" + password);
    return "success";
}

测试 RequestMapping 中的占位符

三、请求参数获取
3.1 原生 Servlet

// 不推荐(侮辱 SpringMVC?)
@RequestMapping("/testServletApi")
public String testServletApi(HttpServletRequest request) {
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println(username + " " + password);
    return "success";
}
测试 Servlet Api 获取请求参数

3.2 形参列表

@RequestMapping("/testParam")
public String testParam(String username, String password, String[] hobby) {
    System.out.println(username + " " + password);
    System.out.println(Arrays.toString(hobby));
    return "success";
}

3.3 @RequestParam

@RequestMapping("/testParam")
public String testParam(@RequestParam(value = "user_name", required = false, defaultValue = "bear") String username) {
    System.out.println(username);
    return "success";
}

3.4 @RequestHeader

  1. @RequestHeader 是将请求头信息和控制器方法的形参创建映射关系
  2. @RequestHeader 注解一共有三个属性:value、required、defaultValue,用法同 @RequestParam

3.5 @CookieValue

  1. @CookieValue 是将 cookie 数据和控制器方法的形参创建映射关系
  2. @CookieValue 注解一共有三个属性:value、required、defaultValue,用法同 @RequestParam
  3. 第一次创建 session 会话时,服务器创建 Cookie 并响应给浏览器(浏览器 Cookie 信息存在于响应报文中),此后的请求 Cookie 存在于请求报文中

3.6 POJO 对象

// 若请求参数中不存在的 POJO 对象中的字段时则对应字段值为 null
@RequestMapping("/testPojo")
public String testPojo(User user) {
    System.out.println(user);
    return "success";
}

3.7 中文乱码

  1. 在 web.xml 中配置编码过滤器 CharacterEncodingFilter ,需将其配置为第一个过滤器,因为过滤器的拦截顺序安装配置的顺序依次拦截

    
    
        CharacterEncodingFilter
        org.springframework.web.filter.CharacterEncodingFilter
        
            encoding
            UTF-8
        
        
            forceRequestEncoding
            true
         
        
            forceResponseEncoding
            true
        
    
    
        
        CharacterEncodingFilter
        
    @GetMapping("/employee/{id}")
    public String getEmployeeById(@PathVariable("id") Integer id, Model model){
        Employee employee = employeeDao.get(id);
        model.addAttribute("employee", employee);
        return "employee_update";
    }
    
    
    
    
    
        
        Update Employee
    
    
    
    
        
        
        lastName:
    email:
    gender:male female

    @PutMapping("/employee")
    public String updateEmployee(Employee employee){
        employeeDao.save(employee);
        return "redirect:/employee";
    }
    

    6.6 DefaultServletHandler

    <!-- 处理静态资源,若只设置该标签,则只能访问静态资源,其他请求无法访问
         此时必须开启注解驱动  以处理其它非静态资源请求
    -->
    
    
    

    七、信息报文转换
    7.1 HttpMessageConverter

    HttpMessageConverter(报文信息转换器)可以实现将请求报文转换为 Java 对象,或将 Java 对象转换为响应报文。HttpMessageConverter 提供了两个注解和两种类型:@RequestBody、@ResponseBody、RequestEntity、ResponseEntity 以实现数据间的转换

    7.2 @RequestBody

    // @RequestBody 获取请求体内容
    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String requestBody){
        // Output:username=admin&password=123456
        System.out.println("requestBody:"+requestBody);
        return "success";
    }
    
        用户名:
    密码:

    7.3 RequestEntity

    // RequestEntity 获取请求头、请求体内容
    @PostMapping("/testRequestEntity")
    public String testRequestEntity(RequestEntity requestEntity) {
        System.out.println("请求头:" + requestEntity.getHeaders());
        System.out.println("请求体:" + requestEntity.getBody());
        System.out.println("请求方法:" + requestEntity.getMethod());
        System.out.println("请求地址:" + requestEntity.getUrl());
        return "success";
    }
    

    首页

    用户名:
    密码:

    7.4 @ResponseBody

    用 @ResponseBody 标识一个控制器方法,方法的返回值就是响应到浏览器的数据。

    @ResponseBody
    @RequestMapping("/testResponseBody")
    public String testResponseBody(){
        // 此时浏览器原请求页面打印 success
        return "success";
    }
    

    7.5 ReponseEntity

    ResponseEntity 用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。可以通过设置自定义的 ResponseEntity 实现文件下载

    @RequestMapping("/download")
    public ResponseEntity download(HttpSession session) throws IOException {
        ServletContext servletContext = session.getServletContext();
        // 获取文件在服务器中真实的磁盘路径
        String realPath = servletContext.getRealPath("/static/img/img.png");
        InputStream inputStream = new FileInputStream(realPath);
        // 创建与文件输入流中字节数等大的字节数组
        byte[] buffer = new byte[inputStream.available()];
        inputStream.read(buffer);
        // 创建 HttpHeaders 对象设置响应头信息
        MultiValueMap headers = new HttpHeaders();
        // 设置下载的方式及下载文件名
        headers.add("Content-Disposition", "attachment;filename=1.jpg");
        // 设置响应状态码
        HttpStatus httpStatus = HttpStatus.OK;
        // 创建 ResponseEntity 对象
        ResponseEntity responseEntity = new ResponseEntity(buffer, headers, httpStatus);
        inputStream.close();
        return responseEntity;
    }
    

    7.6 @RestController

    @RestController 注解是 SpringMVC 提供的一个复合注解,标识在控制器的类上,就相当于为当前类添加了 @Controller 注解,并且为其中的每个方法添加了 @ResponseBody 注解

    八、文件上传、JSON、AJAX
    8.1 文件上传

    1. 在 pom.xml 中引入文件上传所需依赖

          commons-fileupload
          commons-fileupload
          1.3.1
      
      
    2. 在 mvc.xml 中配置文件解析器

      
      
      
    3. 前端 form 表单提交文件数据: form 表单的请求方式必须为 post,并且添加属性 enctype=”multipart/form-data”

          Photo:
      PDF:
    4. 后台控制器接收并保存文件数据:SpringMVC 中将上传的文件封装到 MultipartFile 对象中,通过此对象可以获取文件相关信息

      @PostMapping("/upload")
      public String upload(HttpSession session, MultipartFile photo, MultipartFile pdf) throws IOException {
          // 获取指定目录在磁盘上的真实路径
          String realPath = session.getServletContext().getRealPath("/upload");
          File directory = new File(realPath);
          // 若目录不存在则新建目录
          if (!directory.exists()) {
              directory.mkdir();
          }
      
          // 获取浏览器上传的文件的文件名并获取文件名后缀
          String photoName = photo.getOriginalFilename();
          String photoSuffix = photoName.substring(photoName.lastIndexOf("."));
          String pdfName = pdf.getOriginalFilename();
          String pdfSuffix = pdfName.substring(pdfName.lastIndexOf("."));
          // 以系统生成的随机 UUID 作为新的文件名
          String photoPath = realPath + File.separator + UUID.randomUUID() + photoSuffix;
          String pdfPath = realPath + File.separator + UUID.randomUUID() + pdfSuffix;
      
          // 保存文件到磁盘目录
          pdf.transferTo(new File(pdfPath));
          photo.transferTo(new File(photoPath));
          return "success";
      }
      

    8.2 JSON 处理

    1. pom.xml 中引入 JSON 依赖

          com.fasterxml.jackson.core
          jackson-databind
          2.12.1
      
      
    2. 在 mvc.xml 中开启注解驱动,此时在 HandlerAdaptor 中会自动装配一个消息转换器 MappingJackson2HttpMessageConverter,可以将响应到浏览器的 Java 对象自动转换为 JSON 格式的字符串

      
      
      
    3. 在控制器方法上使用 @ResponseBody 注解进行标识,将 Java 对象直接作为控制器方法的返回值返回,就会自动转换为 JSON 格式的字符串

      @ResponseBody
      @RequestMapping("/testResponseUser")
      public User testResponseUser(){
          // 浏览器 Output:{"id":1001,"username":"admin","password":"123456","age":23,"sex":"男"}
          return new User(1001,"admin","123456",23,"男");
      }
      

    8.3 AJAX 处理

    @ResponseBody
    @GetMapping("/testAjax")
    public String testAjax(User user) {
        System.out.println(user);
        return "hello,ajax";
    }
    
    
    

    九、拦截器
    9.1 拦截器的配置与使用

    1. 自定义拦截器需要自定义类实现 HandlerInterceptor 接口

      @Component
      public class FirstInterceptor implements HandlerInterceptor {
          // 控制器方法执行之前执行 preHandle() ,其 boolean 类型的返回值表示是否调用相应的控制器方法
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              System.out.println("FirstInterceptor preHandle()");
              return  true;
          }
          
          // 控制器方法执行之后执行 postHandle()
          @Override
          public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
              System.out.println("FirstInterceptor postHandle()");
          }
      
          // 处理完视图和模型数据,视图渲染完毕之后执行 afterComplation()
          @Override
          public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
              System.out.println("FirstInterceptor afterCompletion()");
          }
      }
      
    2. 在 mvc.xml 中配置拦截器

      
      
          
          
          
          
      
      
      
      
          
              
              <mvc:mapping path="
      @ControllerAdvice
      public class ExceptionHandler {
          @org.springframework.web.bind.annotation.ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
          public ModelAndView handleException(Exception ex) {
              ModelAndView modelAndView = new ModelAndView();
              // 异常发生时转发到的视图名称
              modelAndView.setViewName("error");
              // request 域中共享的错误信息,key 为 ex,value 为 ex
              modelAndView.addObject("ex", ex);
              return modelAndView;
          }
      }
      

      十一、SpringMVC 总结
      11.1 常用组件

      组件名 描述 作用

      DispatcherServlet前端控制器,不需要工程师开发,由框架提供统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求HandlerMapping处理器映射器,不需要工程师开发,由框架提供根据请求的 url、method 等信息查找 Handler,即控制器方法Handler处理器(控制器),需要工程师开发在 DispatcherServlet 的控制下通过 Handler 对具体的用户请求进行处理HandlerAdapter处理器适配器,不需要工程师开发,由框架提供通过 HandlerAdapter 对处理器(控制器方法)进行执行ViewResolver视图解析器,不需要工程师开发,由框架提供进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectViewView视图即页面将模型数据通过页面展示给用户
      11.2 WebApplicationContext 初始化流程

      1. 调用 FrameworkServlet 类中的 initWebApplicationContext 方法实现 context 对象的初始化,在初始化方法中先调用其同类的方法 createWebApplicationContext 创建 context 对象(37 行)

        
        protected WebApplicationContext initWebApplicationContext() {
            WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            WebApplicationContext wac = null;
        
            if (this.webApplicationContext != null) {
                // A context instance was injected at construction time -> use it
                wac = this.webApplicationContext;
                if (wac instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                    if (!cwac.isActive()) {
                        // The context has not yet been refreshed -> provide services such as
                        // setting the parent context, setting the application context id, etc
                        if (cwac.getParent() == null) {
                            // The context instance was injected without an explicit parent -> set
                            // the root application context (if any; may be null) as the parent
                            cwac.setParent(rootContext);
                        }
                        configureAndRefreshWebApplicationContext(cwac);
                    }
                }
            }
            if (wac == null) {
                // No context instance was injected at construction time -> see if one
                // has been registered in the servlet context. If one exists, it is assumed
                // that the parent context (if any) has already been set and that the
                // user has performed any initialization such as setting the context id
                wac = findWebApplicationContext();
            }
            if (wac == null) {
                // No context instance is defined for this servlet -> create a local one
                // 1. 创建 WebApplicationContext
                wac = createWebApplicationContext(rootContext);
            }
        
            if (!this.refreshEventReceived) {
                // Either the context is not a ConfigurableApplicationContext with refresh
                // support or the context injected at construction time had already been
                // refreshed -> trigger initial onRefresh manually here.
                synchronized (this.onRefreshMonitor) {
                    // 2. 刷新 WebApplicationContext
                    onRefresh(wac);
                }
            }
        
            if (this.publishContext) {
                // Publish the context as a servlet context attribute.
                // 将 IOC 容器在应用域共享
                String attrName = getServletContextAttributeName();
                getServletContext().setAttribute(attrName, wac);
            }
        
            return wac;
        }
        
        
        
        protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
            Class contextClass = getContextClass();
            if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
                throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
            }
            // 通过反射创建 IOC 容器对象
            ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        
            wac.setEnvironment(getEnvironment());
            // 设置父容器
            wac.setParent(parent);
            String configLocation = getContextConfigLocation();
            if (configLocation != null) {
                wac.setConfigLocation(configLocation);
            }
            configureAndRefreshWebApplicationContext(wac);
        
            return wac;
        }
        
      2. 接着调用刷新上下文对象的方法 onRefresh(wac)(46 行),该方法在 FrameworkServlet 的子类 DispatcherServlet 中进行了重写并调用了 initStrategies(context)方法初始化策略,即初始化各个组件

        
        @Override
        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
        
        
        protected void initStrategies(ApplicationContext context) {
            initMultipartResolver(context);
            initLocaleResolver(context);
            initThemeResolver(context);
            initHandlerMappings(context);
            initHandlerAdapters(context);
            initHandlerExceptionResolvers(context);
            initRequestToViewNameTranslator(context);
            initViewResolvers(context);
            initFlashMapManager(context);
        }
        

      11.3 DispatcherServlet 处理请求流程

      1. FrameworkServlet 重写了 HttpServlet 中的 service() 和 doXxx(),这些方法中调用了 FrameworkServlet 类中的 processRequest(request, response)。在此方法中又调用了 doService(request, response) 方法处理请求(子类 DispatcherServlet 重写了 doService(request, response) 方法)

        protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
            long startTime = System.currentTimeMillis();
            Throwable failureCause = null;
        
            LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
            LocaleContext localeContext = buildLocaleContext(request);
        
            RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        
            initContextHolders(request, localeContext, requestAttributes);
        
            try {
                // 执行服务,doService() 是一个抽象方法,在 DispatcherServlet 中进行了重写
                doService(request, response);
            }
            catch (ServletException | IOException ex) {
                failureCause = ex;
                throw ex;
            }
            catch (Throwable ex) {
                failureCause = ex;
                throw new NestedServletException("Request processing failed", ex);
            }
        
            finally {
                resetContextHolders(request, previousLocaleContext, previousAttributes);
                if (requestAttributes != null) {
                    requestAttributes.requestCompleted();
                }
                logResult(request, response, failureCause, asyncManager);
                publishRequestHandledEvent(request, response, startTime, failureCause);
            }
        }
        
      2. DispatcherServlet 中的 doService() 方法调用同类的 doDispatch() 方法分发处理请求

        @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 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);
            }
        
            RequestPath requestPath = null;
            if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
                requestPath = ServletRequestPathUtils.parseAndCache(request);
            }
        
            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);
                    }
                }
                if (requestPath != null) {
                    ServletRequestPathUtils.clearParsedRequestPath(request);
                }
            }
        }
        
        
        
        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);
        
                    // Determine handler for the current request.
                    
                    mappedHandler = getHandler(processedRequest);
                    if (mappedHandler == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }
        
                    // Determine handler adapter for the current request.
                   	// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        
                    // Process last-modified header, if supported by the handler.
                    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;
                        }
                    }
        			
                    // 1. 调用拦截器的 preHandle()
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
        
                    // Actually invoke the handler.
                    // 2. 由处理器适配器调用具体的控制器方法(controller),最终获得 ModelAndView 对象
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
        
                    applyDefaultViewName(processedRequest, mv);
                    // 3. 调用拦截器的 postHandle()
                    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);
                }
                // 4. 处理模型数据和渲染视图,完成后调用拦截器的 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);
                    }
                }
            }
        }
        
        // 处理模型数据、渲染视图、调用拦截器的 afterCompletion()
        private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                           @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                           @Nullable 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);
                }
            }
        
            // Did the handler return a view to render?
            if (mv != null && !mv.wasCleared()) {
                // 处理模型数据和渲染视图
                render(mv, request, response);
                if (errorView) {
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("No view rendering, null ModelAndView returned.");
                }
            }
        
            if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Concurrent handling started during a forward
                return;
            }
        
            if (mappedHandler != null) {
                // Exception (if any) is already handled..
                // 调用拦截器的afterCompletion()
                mappedHandler.triggerAfterCompletion(request, response, null);
            }
        }
        

      11.4 SpringMVC 的执行流程总结归纳

      1. 用户向服务器发送请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获

      2. DispatcherServlet 对请求 URL 进行解析,得到请求资源标识符(URI),判断请求 URI 对应的映射:

        请求映射失败:查看是否配置了 mvc:default-servlet-handler 处理静态资源,如果没配置,则报错 404(DispatcherServlet)

      3. 请求映射成功,根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 执行链对象的形式返回

      4. DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter,如果成功获得 HandlerAdapter,此时将开始执行拦截器的 preHandler(…) 方法(按定义顺序正序执行)

      5. 提取 Request 中的模型数据,填充 Handler入参,开始执行 Handler(Controller) 方法处理请求。在填充 Handler 的入参过程中,根据配置,Spring 将做一些额外的工作:

        1. HttpMessageConveter: 将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息
        2. 数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等
        3. 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
        4. 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中
      6. Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象

      7. 此时将开始执行拦截器的 postHandle(…) 方法(按定义顺序逆序执行)

      8. 根据返回的 ModelAndView(此时会判断是否存在异常:如果存在异常,则执行 HandlerExceptionResolver 进行异常处理)选择一个适合的 ViewResolver 进行视图解析,根据 Model 和 View 来渲染视图

      9. 渲染视图完毕执行拦截器的 afterCompletion(…) 方法(按定义顺序逆序执行)

      10. 将渲染结果返回给客户端

      11.5 SpringMVC 基于 xml 文件的配置

      1. pom.xml 引入相关依赖

        
        war
        
        
        
            
            
                org.springframework
                spring-webmvc
                5.3.1
            
            
            
                ch.qos.logback
                logback-classic
                1.2.3
            
            
            
                javax.servlet
                javax.servlet-api
                3.1.0
                provided
            
            
            
                org.thymeleaf
                thymeleaf-spring5
                3.0.12.RELEASE
            
            
            
                com.fasterxml.jackson.core
                jackson-databind
                2.12.1
            
            
            
                commons-fileupload
                commons-fileupload
                1.3.1
            
        
        
      2. web.xml 配置过滤器、前端控制器

        
        
            CharacterEncodingFilter
            org.springframework.web.filter.CharacterEncodingFilter
            
                encoding
                UTF-8
            
            
                forceRequestEncoding
                true
            
            
                forceResponseEncoding
                true
            
        
        
            CharacterEncodingFilter
            
        public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
            
            @Override
            protected Class[] getRootConfigClasses() {
                return new Class[]{SpringConfig.class};
            }
        
            
            @Override
            protected Class[] getServletConfigClasses() {
                return new Class[]{SpringMvcConfig.class};
            }
        
            
            @Override
            protected String[] getServletMappings() {
                return new String[]{"/"};
            }
        
            
            @Override
            protected Filter[] getServletFilters() {
                // 编码过滤器
                CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8", true);
                // 请求方法过滤器
                HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
                return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
            }
        }
        
      3. Spring 的配置类

        @Configuration
        public class SpringConfig {
        }
        
      4. SpringMVC 的配置类

        @Configuration
        @ComponentScan("com.bear.mvc.controller")
        @EnableWebMvc
        public class SpringMvcConfig implements WebMvcConfigurer {
            
            @Override
            public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
                configurer.enable();
            }
        
            
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                FirstInterceptor firstInterceptor = new FirstInterceptor();
                // 添加拦截规则
                registry.addInterceptor(firstInterceptor).addPathPatterns("
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("index");
            }
        
            
            @Override
            public void configureHandlerExceptionResolvers(List resolvers) {
                SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
                Properties prop = new Properties();
                // 添加异常及跳转的页面名称
                prop.setProperty("java.lang.ArithmeticException", "error");
                exceptionResolver.setExceptionMappings(prop);
                // 设置共享异常信息的键
                exceptionResolver.setExceptionAttribute("ex");
                resolvers.add(exceptionResolver);
            }
        
            
            @Bean
            public CommonsMultipartResolver multipartResolver() {
                return new CommonsMultipartResolver();
            }
        
            
            @Bean
            public ITemplateResolver templateResolver() {
                WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
                ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
                templateResolver.setPrefix("/WEB-INF/pages/");
                templateResolver.setSuffix(".html");
                templateResolver.setCharacterEncoding("UTF-8");
                templateResolver.setTemplateMode(TemplateMode.HTML);
                return templateResolver;
            }
        
            
            @Bean
            public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
                SpringTemplateEngine templateEngine = new SpringTemplateEngine();
                templateEngine.setTemplateResolver(templateResolver);
                return templateEngine;
            }
        
            
            @Bean
            public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
                ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
                viewResolver.setCharacterEncoding("UTF-8");
                viewResolver.setTemplateEngine(templateEngine);
                return viewResolver;
            }
        }
        

本文来自网络,不代表0514资讯网立场,转载请注明出处:https://www.0514zx.com/info/6374.html
0514zx.com

作者: 0514zx.com

优质职场领域创作者
联系我们

联系我们

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部