实习------SpringMVC 框架学习

news2024/9/26 5:18:31

Spring MVC 是什么 (了解)

Spring MVC(全称 Spring Web MVC)是 Spring 框架提供的一款基于 MVC 模式的轻量级 Web 开发框架,是 Spring 为表示层(UI)开发提供的一整套完备的解决方案。

注:三层架构分为表示层(UI)、业务逻辑层(BLL)、数据访问层(DAL),表示层则包含前台页面和后台 Servlet

Spring MVC 使用 MVC 架构模式的思想,将 Web 应用进行职责解构,把一个复杂的 Web 应用划分成模型(Model)、控制器(Contorller)以及视图(View)三层,有效地简化了 Web 应用的开发,降低了出错风险,同时也方便了开发人员之间的分工配合。

Spring MVC 各层的职责如下:

  • Model:负责对请求进行处理,并将结果返回给 Controller;

  • View:负责将请求的处理结果进行渲染,展示在客户端浏览器上;

  • Controller:是 Model 和 View 交互的纽带;主要负责接收用户请求,并调用 Model 对请求处理,然后将 Model 的处理结果传递给 View。

Spring MVC 本质是对 Servlet 的进一步封装,其最核心的组件是 DispatcherServlet,它是 Spring MVC 的前端控制器,主要负责对请求和响应的统一地处理和分发。Controller 接收到的请求其实就是 DispatcherServlet 根据一定的规则分发给它的。

Spring MVC 框架内部采用松耦合、可插拔的组件结构,具有高度可配置性,比起其他的 MVC 框架更具有扩展性和灵活性。此外,Spring MVC 的注解驱动(annotation-driven)和对 REST 风格的支持,也是它最具有特色的功能。

Spring MVC 的常用组件

Spring MVC 的常用组件如下表。

组件

提供者

描述

DispatcherServlet

框架提供

前端控制器,它是整个 Spring MVC 流程控制中心,负责统一处理请求和响应,调用其他组件对用户请求进行处理。

HandlerMapping

框架提供

处理器映射器,根据请求的 url、method 等信息查找相应的 Handler。

Handler

开发人员提供

处理器,通常被称为 Controller(控制器)。它可以在 DispatcherServlet 的控制下,对具体的用户请求进行处理。

HandlerAdapter

框架提供

处理器适配器,负责调用具体的控制器方法,对用户发来的请求来进行处理。

ViewResolver

框架提供

视图解析器,其职责是对视图进行解析,得到相应的视图对象。常见的视图解析器有 ThymeleafViewResolver、InternalResourceViewResolver 等。

View

开发人员提供

视图,它作用是将模型(Model)数据通过页面展示给用户。

Spring MVC 的特点

Spring MVC 具有以下特点:

  • Spring MVC 是 Spring 家族原生产品,可以与 IoC 容器等 Spring 基础设施无缝对接;

  • Spring MVC 支持各种视图技术,例如 JSP、Thymeleaf、 JSP 和 FreeMaker 等。

  • Spring MVC 基于原生的 Servlet 实现,通过功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理;

  • Spring MVC 对表示层各细分领域需要解决的问题全方位覆盖,并提供一整套全面的解决方案;

  • 代码清新简洁,大幅度提升开发效率;

  • 内部组件化程度高,可插拔式组件即插即用,想要使用什么功能,配置相应组件即可;

  • 性能卓著,尤其适合现代大型、超大型互联网项目的开发。

MVC 设计模式 (了解)

MVC 模式,全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种软件架构模式,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。

通常情况下,一个完整的 Java Web 应用程序,其结构如下图所示。

图1:Java Web 应用的结构

MVC 模式将应用程序划分成模型(Model)、视图(View)、控制器(Controller)等三层,如下图所示。

图2:MVC 模式

分层

描述

Model(模型)

它是应用程序的主体部分,主要由以下 2 部分组成:

  • 实体类 Bean:专门用来存储业务数据的对象,它们通常与数据库中的某个表对应,例如 User、Student 等。

  • 业务处理 Bean:指 Service 或 Dao 的对象,专门用于处理业务逻辑、数据库访问。

一个模型可以为多个视图(View)提供数据,一套模型(Model)的代码只需写一次就可以被多个视图重用,有效地减少了代码的重复性,增加了代码的可复用性。

View(视图)

指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。

Controller(控制器)

通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。

在这个过程中,Controller 层不会做任何业务处理,它只是 View(视图)层和 Model (模型)层连接的枢纽,负责调度 View 层和 Model 层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。

MVC模式 VS 三层架构

和 MVC 模式类似,三层架构同样将系统划分成了 3 层:

  • 表示层(UI):用来实现与用户的交互,接收用户请求,并将请求交给业务逻辑层(BLL)和数据访问层(DAL)进行处理,最后将处理结果返回给用户。

  • 业务逻辑层(BLL):起到承上启下的作用,接收表示层传递来的请求,并针对业务对数据进行处理,以实现业务目标。

  • 数据访问层(DAL):用于实现与数据库的交互和访问,例如从数据库中获取数据、保存或修改数据库中的数据等。

虽然三层架构和 MVC 模式一样,都是将应用划分成了 3 层,但它们的划分方式是不同的。

下图展示了三层架构的划分方式,我们可以很清楚地分辨出它与 MVC 模式的不同。

图3:三层架构

从上图可以看出,三层架构是由表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三个层次构成的,而 MVC 则是由视图(View)层、控制(Controller)层以及模型(Model)层,且它们之间并不是一一对应的。

三层架构和 MVC 模式中各层对应关系如下:

  • 三层架构中的表示层(UI)包含 HTML、JSP 等前台页面以及后台的 Servlet,即它相当于 MVC 模式中的 View 层 + Controller 层。

  • 三层架构中的业务逻辑层(BLL),则只包含了 Service 接口及其实现类(Servicelmpl)的代码,即它相当于 MVC 模式中 Model 层的一部分,并不包含 Dao 和实体类。

  • 三层架构中的数据访问层(DAL),则只包含了 Dao 接口及其实现类(DaoImpl)的代码,即它相当于 MVC 模式中 Model 层的一部分,并不包含 Service 和实体类。

三层架构将应用中的各个模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)等三层,各层之间采用接口相互访问,并通过实体类作为数据传递的载体。不同的实体类一般对应于数据库中不同的数据表,且实体类的属性与数据库表的字段名一一对应 。

从上面的划分方式来看,三层架构和 MVC 模式确实是不一样的,但从它们的核心来看,两者又是一样的,它们的核心都是“分层、解耦”。

MVC 的工作流程

MVC 的工作流程如下:

  1. 用户发送请求到服务器;

  1. 在服务器中,请求被控制层(Controller)接收;

  1. Controller 调用相应的 Model 层处理请求;

  1. Model 层处理完毕将结果返回到 Controller;

  1. Controller 再根据 Model 返回的请求处理结果,找到相应的 View 视图;

  1. View 视图渲染数据后最终响应给浏览器。

MVC 的优点

MVC 模式具有以下优点:

  • 降低代码耦合性:在 MVC 模式中,三层之间相互独立,各司其职。一旦某一层的需求发生了变化,我们就只需要更改相应层中的代码即可,而不会对其他层中的代码造成影响。

  • 有利于分工合作:在 MVC 模式中,将应用系统划分成了三个不同的层次,可以更好地实现开发分工。例如,网页设计人员专注于视图(View)层的开发,而那些对业务熟悉的开发人员对 Model 层进行开发,其他对业务不熟悉的开发人员则可以对 Controller 层进行开发。

  • 有利于组件的重用:在 MVC 中,多个视图(View)可以共享同一个模型(Model),大大提高了系统中代码的可重用性。

MVC 的不足

MVC 模式存在以下不足之处:

  • 增加了系统结构和实现的复杂性:对于简单的应用,如果也严格遵循 MVC 模式,按照模型、视图与控制器对系统进行划分,无疑会增加系统结构的复杂性,并可能产生过多的更新操作,降低运行效率。

  • 视图与控制器间的联系过于紧密:虽然视图与控制器是相互分离的,但它们之间联系却是十分紧密的。视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了它们的独立重用。

  • 视图对模型数据的低效率访问:视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。

MVC 并不适合小型甚至中型规模的项目,花费大量时间将 MVC 应用到规模并不是很大的应用程序中,通常会得不偿失,因此对于 MVC 设计模式的使用要根据具体的应用场景来决定

*Spring MVC 和 Struts2的区别(了解)

*Spring MVC 视图解析器(熟悉)

Spring MVC 执行流程 (熟悉)

Spring MVC 执行流程如图 1 所示。

图1:Spring MVC 工作流程

SpringMVC 的执行流程如下。

  1. 用户通过浏览器发起一个 HTTP 请求,该请求会被 DispatcherServlet(前端控制器)拦截;

  1. DispatcherServlet 调用 HandlerMapping(处理器映射器)找到具体的处理器(Handler)及拦截器,最后以 HandlerExecutionChain 执行链的形式返回给 DispatcherServlet。

  1. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);

  1. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(即 Controller 控制器)对请求进行处理;

  1. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC 的底层对象,包括 Model 数据模型和 View 视图信息);

  1. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;

  1. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;

  1. ViewResolver 解析完成后,会将 View 视图并返回给 DispatcherServlet;

  1. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);

  1. 视图负责将结果显示到浏览器(客户端)。

Spring MVC 常用组件

Spring MVC 的常用组件共有 6 个,它们分别是: DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。

下表对各个组件的功能说明如下

组件

提供者

说明

DispatcherServlet(前端控制器)

由框架提供

它是 Spring MVC 的核心,其本质就是一个 Servlet。

它负责将所有的请求进行统一分发,相当于一个 Spring MVC 的流程控制中心,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

HandlerMapping(处理器映射器)

由框架提供

负责根据请求的 url、method 等信息查找响应的 Handler 处理器(即 Controller 控制器方法)。

Handler(处理器)

由开发人员提供

也就是我们常说的 Controller 控制器,负责在 DispatcherServlet 的控制下,对具体的用户请求进行处理。

HandlerAdapter(处理器适配器)

由框架提供

它负责根据 HandlerMapping 映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)方法。

ViewResolver(视图解析器)

由框架提供

通过 ModelAndView 对象中的 View 信息对逻辑视图名进行解析,将其解析成真正的视图 View(例如 ThymeleafView、InternalResourceView、RedirectView 等),并返回给 DispatcherServlet。

View(视图)

View 对象本身由框架提供,但视图所对应的前端页面(例如 JSP、HTML)则需要开发人员自行编写

将 Model 模型数据通过页面展示给用户。

@Controller 和@RequestMapping 注解(熟悉)

@Controller 注解

@Controller 注解可以将一个普通的 Java 类标识成控制器(Controller)类,示例代码如下。

package net.biancheng.controller;

import org.springframework.stereotype.Controller;

@Controller
public class IndexController {
    // 处理请求的方法
}

Spring MVC 是通过组件扫描机制查找应用中的控制器类的,为了保证控制器能够被 Spring MVC 扫描到,我们还需要在 Spring MVC 的配置文件中使用 <context:component-scan/> 标签,指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下),示例代码如下。

<!-- 使用扫描机制扫描控制器类,控制器类都在net.biancheng.controller包及其子包下 -->
<context:component-scan base-package="net.biancheng.controller" />

@RequestMapping 注解

@RequestMapping 注解是 Spring MVC 中最常被用到的注解之一。它通常被标注在控制器方法上,负责将请求与处理请求的控制器方法关联起来,建立映射关系。

Spring MVC 的前端控制器(DispatcherServlet)拦截到用户发来的请求后,会通过 @RequestMapping 注解提供的映射信息找到对应的控制器方法,对这个请求进行处理。

@RequestMapping 注解的使用方式

@RequestMapping 既可以标注在控制器类上,也可以标注在控制器方法上。

1. 修饰方法

当 @RequestMapping 注解被标注在方法上时,value 属性值就表示访问该方法的 URL 地址。当用户发送过来的请求想要访问该 Controller 下的控制器方法时,请求路径就必须与这个 value 值相同,示例代码如下。

@Controller
public class HelloController {

    @RequestMapping("/login")
    public String welcome() {
        return "login";
    }
}
2. 修饰类

当 @RequestMapping 注解标注在控制器类上时,value 属性的取值就是这个控制器类中的所有控制器方法 URL 地址的父路径。也就是说,访问这个 Controller 下的任意控制器方法都需要带上这个父路径。

@Controller
@RequestMapping(value = "/springmvc")
public class HelloController {

    @RequestMapping("/login")
    public String welcome() {
        return "login";
    }
}

例如,在上面的控制类中,用户想要访问 HelloController 中的 welcome() 方法,请求的地址就必须带上父路径“/springmvc”,即请求地址必须为“/springmvc/login”。

@RequestMapping 注解的属性

@RequestMapping 注解中提供了多个可用属性,下面我们就对其中几个比较常用的属性进行介绍。

1. value 属性

在 @RequestMapping 注解中,value 属性用来设置控制器方法的请求映射地址。所有能够匹配到该请求映射地址的请求,都可以被该控制器方法处理,示例代码如下。

@RequestMapping(value = "/register")

value 属性是 @RequestMapping 注解的默认属性,如果我们在 @RequestMapping 注解中只设置了一个 value 属性,则该属性名可以被省略,示例代码如下。

//省略 value 属性名
@RequestMapping( "/register")

2. name 属性

name 属性相当于方法的注释,用于解释这个方法是用来干什么的,使方法更易理解。

例如,下面的代码表示 getUsers() 方法是一个用来获取用户信息的控制器方法。

@RequestMapping(value = "toUser",name = "获取用户信息")
public String getUsers() {
    ……
}

3. method 属性

method 属性用来设置控制器方法支持的请求方式。如果一个控制器方法没有设置 @RequestMapping 注解的 method 属性,则说明该控制器方法支持全部请求类型,可以处理所有类型的请求。

method 属性的取值是一个 RequestMethod 类型的数组,表示一个控制器方法支持多种方式的请求,常用的请求方式有 GET、POST、DELETE、PUT 等。

例如,控制器方法只支持 GET 方式的请求,代码如下。

@RequestMapping(value ="/toUser",method = RequestMethod.GET)

我们也可以为同一个控制器方法指定支持多种类型的请求。例如,一个方法既支持 GET 方式的请求,也支持 POST 方式的请求,代码如下。

@RequestMapping(value = "/toUser",method = {RequestMethod.GET,RequestMethod.POST})

4. params 属性

params 属性用于指定请求中的参数,只有当请求中携带了符合条件的参数时,控制器方法才会对该请求进行处理。

我们可以通过以下 4 种表达式来对请求的参数进行配置。

序号

表达式

含义

"param"

请求中必须携带名为 param 的参数

"!param"

与表达式 ① 的含义完全相反,请求中不能携带名为 param 的参数

"param=value"

请求中必须携带名为 param 的参数,且参数的取值必须为:value

"param!=value"

与表达式 ③ 的含义完全相反,请求中不能携带参数:param = value。

params 属性的取值是一个字符串类型的数组,表示只有请求中同时携带了 params 属性指定的全部参数时,控制器方法才会对该请求进行处理。

例如,控制器方法 testParam() 的代码如下:

@RequestMapping(value = "/testParam", params = {"name=C语言中文网", "url=http://c.bianheng.net"})
@ResponseBody
public String testParam() {
    return "success";
}

以上代码表示,只有当请求中同时携带 name 和 url 两个请求参数,且参数值必须分别为 “C语言中文网” 和“http://c.biancheng.net”时,控制器方法 testParam() 才会对该请求进行处理 。

5. headers 属性

headers 属性用于设置请求中请求头信息,只有当请求中携带指定的请求头信息时,控制器方法才会处理该请求。

我们可以通过以下 4 种表达式来指定请求中的请求头信息。

序号

表达式

含义

"header"

请求必须携带请求头信息:header

"!header"

与表达式 ① 的含义完全相反,请求中不能携带请求头信息:header

"header=value"

请求中必须携带请求头信息:header=value 。

"header!=value"

与表达式 ③ 的含义完全相反,请求中不能携带请求头信息:header=value。

header 属性是一个字符换类型的数组,表示只有当请求同时携带数组中规定的所有头信息时,控制器方法才会对该请求进行处理。

例如,控制器方法 method() 的代码如下。

@RequestMapping(value = "toUser",headers = "Referer=http://c.biancheng.net")
public String metnod() {
    ……
}

在以上代码中,只有当请求的头信息中包含“Referer=http://c.biancheng.net”时,控制器方法 method() 才会处理该请求。

Spring MVC 传递参数 (熟悉)

1、使用Model对象(常用)

(1)使用model对象往前台传递数据 attribute

(2)在jsp中接收从后台传递过来的参数

2、使用HttpServletRequest对象

(1)使用HttpServletRequest对象往前台传递数据

(2)jsp中接收,同上!!!

3、使用Map对象

把Model改成Map即可。

Spring MVC 重定向和转发(熟悉)

请求转发(Forward)

请求转发是一种在服务器内部的资源跳转方式,简单来说,当客户浏览器发送http请求到web服务器中,web服务器接受请求后调用内部servlet方法完成请求处理和转发资源给同一个web容器下的另一资源做处理,最后将目标资源response给客户

工作原理及特点

比如图中web容器(Tomcat)有两个资源(A和B),若浏览器请求资源A的时候,资源A处理了一部分,然后跳转到资源B,让资源B接着处理,资源B处理完成后做出响应到客户端。资源A跳转到资源B的这个过程就叫转发。

实现代码

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    //获取请求转发器对象,调用forward()方法,请求转发
    RequestDispatcher requestDispatcher = request.getRequestDispatcher("跳转资源的路径").forward(requset,response);
}

特点

1. 请求转发资源间共享数据

图中资源A处理了一部分数据后把其他数据转交给资源B处理,所以在这过程中资源之间数据是共享的。

2. 浏览器地址栏路径不发生变化,只能转发到当前服务器的内部资源,浏览器只做了一次请求

在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

Response完成重定向(Redirect)

重定向(Redirect)是一种资源跳转的方式,一般用来解决登录进入主页、跨域访问、系统维护等等都使用重定向技术,比如当系统进行维护时,用户若发起请求,系统将会告诉浏览器重定向访问指定的url。

简单来说,当浏览器发起请求给资源A时,资源A发现浏览器发起的请求自身处理不了,但是知道资源B可以处理,这时候资源A就会告诉浏览器说这次请求处理不了,请找资源B处理并且告诉资源B的访问路径,浏览器会自动去请求资源B。

工作原理及特点

客户浏览器发送http请求后,web服务器中资源A接受请求后无法处理,资源A将会发送302状态码响应及对应新的location(资源B)给客户浏览器,客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址。

实现代码

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    //旧方法
    //response.setStatus(302);
    //response.setHeader("location","资源B的路径");
    
    //其中资源B的路径需要添加虚拟目录
    response.sendRedirect("资源B的路径");
}

特点

1. 浏览器地址栏路径发生变化

2. 可以重定向到任意位置的资源(服务器内部、外部均可)

3. 浏览器发起两次请求,不能在多个资源使用request共享资源

重定向和请求转发的区别

  • 转发和重定向是两种不同的请求处理方式,转发是服务器行为,重定向是客户端行为。

  • 请求转发过程中,数据在同一个web服务器中得到共享,因为浏览器只做了一次访问请求,浏览器地址栏路径不发生变化,为同一个request域;而重定向中,浏览器做了两次请求,浏览器地址栏路径发生变化,请求并不是同一个request域。

Spring MVC怎么样设定重定向和转发的?

一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和redirect: 当成指示符,其后的字符串作为 URL 来处理。

(1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"

@RequestMapping("/springMvc")
@Controller
public class handler {
    //测试转发
    @RequestMapping("/testRedirect")
    public String testRedirect(){
        System.out.println("testRedirect");
        return "forward:/index.jsp";
    }
 
}

(2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"

@RequestMapping("/springMvc")
@Controller
public class handler {
    //测试重定向
    @RequestMapping("/testRedirect")
    public String testRedirect(){
        System.out.println("testRedirect");
        return "redirect:/index.jsp";
    }
 
}

*@ModelAttribute 注解 (熟悉)

Spring MVC 类型转换器(了解)

为什么仅仅通过一些注解,控制器方法就能够得到各种类型的参数,其实这都要归功于 Spring MVC 的类型转换机制。

Spring 提供了一种 Converter(类型转换器)的类型转换工具。在 Spring MVC 中,它的作用是在控制器方法对请求进行处理前,先获取到请求发送过来的参数,并将其转换为控制器方法指定的数据类型,然后再将转换后的参数值传递给控制器方法的形参,这样后台的控制器方法就可以正确地获取请求中携带的参数了。

内置的类型转换器

Spring MVC 框架默认提供了许多内置的类型转换器,主要包括以下几种类型。

1)标量转换器

名称

作用

StringToBooleanConverter

String 到 boolean 类型转换

ObjectToStringConverter

Object 到 String 转换,调用 toString 方法转换

StringToNumberConverterFactory

String 到数字转换(例如 Integer、Long 等)

NumberToNumberConverterFactory

数字子类型(基本类型)到数字类型(包装类型)转换

StringToCharacterConverter

String 到 Character 转换,取字符串中的第一个字符

NumberToCharacterConverter

数字子类型到 Character 转换

CharacterToNumberFactory

Character 到数字子类型转换

StringToEnumConverterFactory

String 到枚举类型转换,通过 Enum.valueOf 将字符串转换为需要的枚举类型

EnumToStringConverter

枚举类型到 String 转换,返回枚举对象的 name 值

StringToLocaleConverter

String 到 java.util.Locale 转换

PropertiesToStringConverter

java.util.Properties 到 String 转换,默认通过 ISO-8859-1 解码

StringToPropertiesConverter

String 到 java.util.Properties 转换,默认使用 ISO-8859-1 编码

2)集合、数组相关转换器

名称

作用

ArrayToCollectionConverter

任意数组到任意集合(List、Set)转换

CollectionToArrayConverter

任意集合到任意数组转换

ArrayToArrayConverter

任意数组到任意数组转换

CollectionToCollectionConverter

集合之间的类型转换

MapToMapConverter

Map之间的类型转换

ArrayToStringConverter

任意数组到 String 转换

StringToArrayConverter

字符串到数组的转换,默认通过“,”分割,且去除字符串两边的空格(trim)

ArrayToObjectConverter

任意数组到 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换

ObjectToArrayConverter

Object 到单元素数组转换

CollectionToStringConverter

任意集合(List、Set)到 String 转换

StringToCollectionConverter

String 到集合(List、Set)转换,默认通过“,”分割,且去除字符串两边的空格(trim)

CollectionToObjectConverter

任意集合到任意 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换

ObjectToCollectionConverter

Object 到单元素集合的类型转换

Spring MVC 对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。因此,通常情况下 Spring MVC 提供的这些类型转换器可以满足开发人员大多数的类型转换需求的。

注意:在使用内置类型转换器时,请求参数输入值需要与接收参数类型相兼容,否则会报 400 错误。

Spring MVC 数据格式化(了解)

在实际的项目开发中,经常会涉及到一些需要进行格式化的数据,例如金额、日期等。以金额为例,当金额为 10 万元时,在比较正式的场合往往要写成 ¥100000;而日期也可以被写作多种格式,例如 yyyy-MM-dd、yyyy-MM-dd hh:ss:mm 等。这些数据都要经过一定的格式化处理才能够正常使用。

Formatter 接口

Spring 提供了一个 Formatter<T> 接口, T 表示目标数据类型,它被称为格式化转换器。

Formatter 的作用与 Converter(类型转换器)相似,都是可以将一种数据类型转换成另一种数据类型。但不同的是,Formatter 的源类型必须是 String 类型,而 Converter 的源类型可以是任意数据类型。

内置格式化转换器

Spring MVC 默认提供了多个内置的格式化器,通过它们,我们可以轻松地实现对日期(Date)类型和数值(Number)类型数据的格式化工作,具体如下。

内置格式化器

说明

NumberFormatter

实现 Number 与 String 之间的解析与格式化。

CurrencyFormatter

实现 Number 与 String 之间的解析与格式化(带货币符号)。

PercentFormatter

实现 Number 与 String 之间的解析与格式化(带百分数符号)。

DateFormatter

实现 Date 与 String 之间的解析与格式化。

虽然 Formatter 与 Converter 存在一定的差异,但格式化转换本质上还是属于“类型转换”的范畴,因此在 Spring MVC 中 Formatter(格式化转换器)实际上是委托给 Converter 机制实现的。

Spring 的格式化模块中定义了一个 FormattingConversionService 类,它是 ConversionService 接口(类型转换器的服务接口)的实现类。它与其他的类型转换器实现类不同,它不仅具有类型转换功能,还具有格式化转换功能。

Spring MVC 还专门为 FormattingConversionService 提供了一个名为 FormattingConversionServiceFactroyBean 的工厂类,它主要作用就是在 Spring 上下文中构造 FormattingConversionService 的实例。

除此之外,FormattingConversionServiceFactroyBean 还为以下 2 个格式化注解提供了支持,使得我们可以更加方便地使用 Spring MVC 内置的格式化器,对数据进行格式化转换。

格式化注解

说明

@DateTimeFormat 注解

使用该注解,能够实现对日期类型数据的格式化

@NumberFormat 注解

使用该注解,能够实现对数值类型数据的格式化

我们知道,只要 Spring MVC 的配置文件中配置一个 <mvc:annotation-driven> 标签,Spring MVC 在启动时就会自动初始化一个 FormattingConversionServiceFactoryBean 类型的实例:ConversionService。

也就是说,我们只要在 Spring MVC 配置文件中配置了 <mvc:annotation-driven> 标签,就可以直接在 Spring MVC 项目中使用 @NumberFormat 注解和 @DateTimeFormat 注解,对 Number 类型和 Date 类型数据进行格式化转换。

日期格式化

@DateTimeFormat 注解可对 java.util.Date、java.util.Calendar、java.long.Long 等时间类型的数据进行标注,以实现对日期类型的数据进行格式化处理。

@DateTimeFormat 注解主要包含以下 3 个属性。

属性

类型

说明

pattern

String

用于指定解析或格式化日期时间的模式,其常用取值包括 yyyy-MM-dd、yyyy-MM-dd hh:mm:ss 等。

iso

DateTimeFormat.ISO

用于指定解析或格式化日期时间的 ISO 模式,其取值有 4 种:

  • DATE:yyyy-MM-dd

  • TIME:hh:mm:ss:SSSZ

  • DATE_TIME:yyyy-MM-dd hh:mm:ss:SSSZ

  • NONE:无

style

String

用于指定日期时间的格式。

该属性由两个字符组成,第一个字符表示日期的格式,第二个字符表示时间的格式。

  • S:短日期/时间格式

  • M:中日期/时间格式

  • L:长日期/时间格式

  • F:完整日期/时间格式

其默认值为“SS”,即日期和时间都采用短格式。

数值格式化

@NumberFormat 注解可以用来格式化任何数字基本类型(如 int、long 等)或 java.lang.Number 类型的实例(如 BigDecimal、Integer 等)。

@NumberFormat 注解拥有两个互斥的属性,如下表。

属性

类型

说明

style

NumberFormat.Style

该属性用于指定数值的样式类型,其取值有以下 4 种:

  • DEFAULT:默认类型

  • NUMBER:正常数值类型

  • PERCENT:百分数类型

  • CURRENCY:货币数值类型

pattern

String

该属性用于自定义数值的样式星星,例如 #,###

代码演示:http://c.biancheng.net/spring_mvc/9678.html

*Spring MVC 表单标签库(了解)

Spring MVC JSON 数据交互(熟悉)

Spring MVC 在传递数据时,通常都需要对数据的类型和格式进行转换。而这些数据不仅可以常见的 String 类型,还可以是 JSON 等其他类型。

JSON 是近些年一种比较流行的数据格式,它与 XML 相似,也是用来存储数据的。但相较于 XML,JSON 数据占用的空间更小,解析速度更快。因此,使用 JSON 数据进行前后台的数据交互也是一种十分常见的手段。

JSON 概述

JSON(JavaScript Object Notation,JS 对象标记)是一种轻量级的数据交互格式。与 XML 一样,JSON 也是一种基于纯文本的数据格式。通过它,我们不仅能够传递 String、Number、Boolean 等简单类型的数据,还可以传递数组、Object 对象等复杂类型的数据。

JSON 支持 2 种数据结构,它们分别是对象结构和数组结构。

1. 对象结构

JSON 的对象结构以“{”开始,以“}”结束,中间则由 0 个或多个以英文的逗号(即“,”)分隔的 key/value 对构成。

对象结构的语法结构如下。

{
    key1:value1,
    key2:value2,
    ...
}

其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。

例如,一个 person 对象包含姓名、密码、年龄等信息,使用 JSON 的表示形式如下:

{
    "pname":"张三",
    "password":"123456",
    "page":40
}

2. 数组结构

JSON 的数组结构以“[”开始、以“]”结束,中间部分由 0 个或多个以英文的逗号(即“,”)分隔的值列表组成。

数组结构的语法结构如下:

{
    value1,
    value2,
    ...
}

例如,一个数组中包含了 String、Number、Boolean、null 多种类型的数据,使用 JSON 的数组结构表示形式如下。

[
    "c语言中文网",
    123456789,
    true,
    null
]

上述两种(对象、数组)数据结构也可以分别组合构成更加复杂的数据结构。

例如,一个 student 对象包含 sno、sname、hobby 和 college 等多个属性,其 JSON 表示形式如下。

{
    "sno":"201802228888",
    "sname":"张三",
    "hobby":[
        "篮球",
        "足球"
    ],
    "college":{
        "cname":"清华大学",
        "city":"北京",
        "code":100000
    }
}

JSON 数据转换

为了实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了一个默认的 MappingJackson2HttpMessageConverter 类,来处理 JSON 格式请求和响应。通过它,我们既可以将 Java 对象转换为 JSON 数据,也可以将 JSON 数据转换为 Java 对象。

引入依赖包

想要使用 MappingJackson2HttpMessageConverter 对 JSON 数据进行转换,第一步就是要将 Jackson 的依赖包引入到 Spring MVC 项目中。

  • jackson-annotations-x.x.x.jar:JSON 转换的注解包

  • jackson-core-x.x.x.jar:JSON 转换的核心包

  • jackson-databind-x.x.x.jar:JSON 转换的数据绑定包

以上这些 Jackson 的开源依赖包都可以通过“https://mvnrepository.com/artifact/com.fasterxml.jackson.core”下载得到。

JSON 转换注解

Spring MVC 为我们提供了两个十分重要的与 JSON 格式转换相关的注解,它们分别是 @RequestBody 和 @ResponseBody。

注解

位置

说明

@RequestBody

方法的形参上

该注解用于将请求体中的数据绑定到控制器方法的形参上。

@ResponseBody

方法上

该注解用于将控制器方法的返回值,直接作为响应报文的响应体响应到

浏览器上。

代码演示:http://c.biancheng.net/spring_mvc/9679.html

Spring MVC 拦截器 (熟悉)

拦截器(Interceptor)是 Spring MVC 提供的一种强大的功能组件。它可以对用户请求进行拦截,并在请求进入控制器(Controller)之前、控制器处理完请求后、甚至是渲染视图后,执行一些指定的操作。

在 Spring MVC 中,拦截器的作用与 Servlet 中的过滤器类似,它主要用于拦截用户请求并做相应的处理,例如通过拦截器,我们可以执行权限验证、记录请求信息日志、判断用户是否已登录等操作。

Spring MVC 拦截器使用的是可插拔式的设计,如果我们需要某一拦截器,只需在配置文件中启用该拦截器即可;如果不需要这个拦截器,则只要在配置文件中取消应用该拦截器即可。

定义拦截器

想要在 Spring MVC 项目中使用拦截器,第一步就是要对拦截器类进行定义。

Spring MVC 在 org.springframework.web.servlet 包中提供了一个 HandlerInterceptor 接口,该接口包含 3 个方法,如下表。

方法名

返回值

说明

preHandle ()

boolean

该方法在控制器方法之前执行,其返回值用来表示是否中断后续操作。

  • 返回值为 true 时,表示继续向下执行;

  • 返回值为 false 时,表示中断后续的操作。

postHandle ()

void

该方法会在控制器方法调用之后,解析式图之前执行。我们可以通过此方法对请求域中的模型(Model)数据和视图做出进一步的修改。

afterCompletion ()

void

该方法会在整个请求完成后,即视图渲染结束之后执行。我们可以通过该方法实现资源清理、日志记录等工作。

我们可以通过实现 HandlerInterceptor 接口,重写其方法,来实现对拦截器类的定义,示例代码如下。

package net.biancheng.c.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle 执行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle 执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion 执行");
    }
}

配置拦截器

在定义完拦截器后,我们还需要在 Spring MVC 的配置文件中使用 <mvc:interceptors> 标签及其子标签对拦截器进行配置,这样这个拦截器才会生效。

在 Spring MVC 的配置文件中,<mvc:interceptors> 标签用于定义一组拦截器,其包含多个常用的子标签,具体说明如下表。

标签

一级子标签

二级子标签

说明

<mvc:interceptors>

<bean>

-

<mvc:interceptors> 标签的子标签,用于定义一个全局拦截器,对所有的请求进行拦截。

<ref>

-

<mvc:interceptors> 标签的子标签,用于定义一个全局拦截器的引用,对所有的请求进行拦截。

<mvc:interceptors> 标签的 <ref> 子标签不能单独使用,它需要与 <bean> 标签(<mvc:interceptors> 标签内或 <mvc:interceptors>标签外)或 @Component 等注解配合使用,以保证<ref> 标签配置的拦截器是 Spring IOC 容器中的一个组件。

<mvc:interceptor>

<mvc:mapping>

<mvc:interceptor> 标签用于定义一个指定拦截路径的拦截器。

<mvc:mapping> 标签则是<mvc:interceptor> 的子标签,用来配置拦截器拦截的路径,该路径则是通过其属性“path”中定义的。例如,path 的属性值为“/**”时,表示拦截所有请求;而“/hello”则表示拦截路径为“/hello”的请求。

<mvc:exclude-mapping>

<mvc:exclude-mapping> 为<mvc:interceptor> 的子标签,用来配置不需要被拦截器拦截的路径。

<bean>

<bean> 为<mvc:interceptor> 的子标签,用来定义一个指定了拦截路径的拦截器。

在了解完 <mvc:interceptors> 标签及其子标签的含义后,接下来我们就来讲解如何通过它们来对拦截器进行定义。

1. 通过 <bean> 子标签配置全局拦截器

我们可以在 Spring MVC 的配置文件中,通过 <mvc:interceptors> 标签及其子标签 <bean> ,将我们自定义的拦截器配置成了一个全局拦截器。该拦截器会对项目内所有的请求进行拦截,配置代码如下。

<!--配置拦截器-->
<mvc:interceptors>
    <bean class="net.biancheng.c.interceptor.MyInterceptor"></bean>
</mvc:interceptors>

2. 通过 <ref> 子标签配置全局拦截器

除了 <bean> 标签外,我们还可以在 <mvc:interceptors> 标签中通过子标签 <ref> 定义一个全局拦截器引用,对所有的请求进行拦截。

<!--将自定义的拦截器放到 ioc 容器中-->
<bean id="interceptor" class="net.biancheng.c.interceptor.MyInterceptor"></bean>
<!--配置拦截器-->
<mvc:interceptors>
    <!--通过 ref 配置全局拦截器-->
    <ref bean="interceptor"></ref>
</mvc:interceptors>

注意:<mvc:interceptors> 标签的 <ref> 子标签不能单独使用,它需要与 <bean> 标签(<mvc:interceptors> 标签内或<mvc:interceptors>标签外)或 @Component 等注解配合使用,以保证 <ref> 标签配置的拦截器是Spring IOC 容器中的组件。

3. 通过<mvc:interceptor>子标签对拦截路径进行配置

我们还可以在 Spring MVC 的配置文件中通过 <mvc:interceptors> 标签的子标签 <mvc:interceptor>,对拦截器拦截的请求路径进行配置,示例配置如下。

<!--配置拦截器-->
<mvc:interceptors>
    <!--拦截器 1-->
    <mvc:interceptor>
        <!--配置拦截器拦截的请求路径-->
        <mvc:mapping path="/**"/>
        <!--配置拦截器不需要拦截的请求路径-->
        <mvc:exclude-mapping path="/login"/>
        <mvc:exclude-mapping path="/"/>
        <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截-->
        <bean class="net.biancheng.c.interceptor.MyInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

需要注意的是,在 <mvc:interceptor> 中,子元素必须按照上述代码的配置顺序进行编写,即 <mvc:mapping> → <mvc:exclude-mapping> → <bean> 的顺序,否则就会报错。其次,以上这三种配置拦截器的方式,我们可以根据自身的需求以任意的组合方式进行配置,以实现在 <mvc:interceptors> 标签中定义多个拦截器的目的。

拦截器的执行流程

拦截器的执行流程如下图所示。

图1:单个拦截器处理流程

拦截器处理流程的步骤如下:

  1. 当请求的路径与拦截器拦截的路径相匹配时,程序会先执行拦截器类(MyInterceptor)的 preHandl() 方法。若该方法返回值为 true,则继续向下执行 Controller(控制器)中的方法,否则将不再向下执行;

  1. 控制器方法对请求进行处理;

  1. 调用拦截器的 postHandl() 方法,此时我们可以对请求域中的模型(Model)数据和视图做出进一步的修改;

  1. 通过 DispatcherServlet 的 render() 方法对视图进行渲染;

  1. 调用拦截器的 afterCompletion () 方法,完成资源清理、日志记录等工作。

多个拦截器的执行流程

在大型的企业级项目中,通常都不会只有一个拦截器,开发人员可能会定义许多不同的拦截器来实现不同的功能。在程序运行期间,拦截器的执行是有一定的顺序的,该顺序与拦截器在配置文件中定义的顺序有关。

假设一个项目中包含两个不同的拦截器:Interceptor1 和 Interceptor2,它们在配置文件中定义的顺序为:Interceptor1 → Interceptor2。下面我们就通过一个拦截器流程图来描述下多个拦截器的执行流程。

图3:多个拦截器的执行流程

从上面的执行流程图可以看出,当存在多个拦截器同时工作时,它们的 preHandle() 方法会按照拦截器在配置文件中的配置顺序执行,但它们的 PostHandle() 和 afterCompletion() 方法则会按照配置顺序的反序执行。

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--开启组件扫描-->
    <context:component-scan base-package="net.biancheng.c"></context:component-scan>

    <!-- 配置 Thymeleaf 视图解析器 -->
    <bean id="viewResolver"
          class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 视图前缀 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!-- 视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--    &lt;!&ndash; view-name:设置请求地址所对应的视图名称&ndash;&gt;-->
    <mvc:view-controller path="/hello" view-name="success"></mvc:view-controller>

    <!-- 当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签-->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!--    &lt;!&ndash;静态资源映射&ndash;&gt;-->
    <mvc:default-servlet-handler/>

    <!--配置拦截器-->
    <mvc:interceptors>
        <!--拦截器 1-->
        <mvc:interceptor>
            <!--配置拦截器拦截的请求路径-->
            <mvc:mapping path="/**"/>
            <!--配置拦截器不需要拦截的请求路径-->
            <mvc:exclude-mapping path="/login"/>
            <mvc:exclude-mapping path="/"/>
            <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截-->
            <bean class="net.biancheng.c.interceptor.MyInterceptor"></bean>
        </mvc:interceptor>

        <!--拦截器 MyInterceptor2-->
        <mvc:interceptor>
            <!--配置拦截器拦截的请求路径-->
            <mvc:mapping path="/**"/>
            <!--配置拦截器不需要拦截的请求路径-->
            <mvc:exclude-mapping path="/login"/>
            <mvc:exclude-mapping path="/"/>
            <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截-->
            <bean class="net.biancheng.c.interceptor.MyInterceptor2"></bean>
        </mvc:interceptor>

        <!--拦截器 MyInterceptor3-->
        <mvc:interceptor>
            <!--配置拦截器拦截的请求路径-->
            <mvc:mapping path="/**"/>
            <!--配置拦截器不需要拦截的请求路径-->
            <mvc:exclude-mapping path="/login"/>
            <mvc:exclude-mapping path="/"/>
            <!--定义在 <mvc:interceptors> 下,表示拦截器只对指定路径的请求进行拦截-->
            <bean class="net.biancheng.c.interceptor.MyInterceptor3"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
</beans>

从控制台输出的内容可知:

  • 由于这三个拦截器在 Spring MVC 的配置文件中是按照 MyInterceptor → MyInterceptor2 → MyInterceptor3 的顺序配置的,因此这三个拦截器的执行顺序也是 MyInterceptor → MyInterceptor2 → MyInterceptor3;

  • 由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此 MyInterceptor3 和它之前的拦截器 MyInterceptor 和 MyInterceptor2 中的 preHandle() 都会执行。

  • 由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此 MyInterceptor、MyInterceptor2 和 MyInterceptor3 的 postHandle() 方法都不会执行。

  • 由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此只有 MyInterceptor 和 MyInterceptor2 的 afterCompletion() 方法执行了。

*Spring MVC 数据校验 (了解)

Spring MVC 异常处理 (熟悉)

在实际的应用开发中,经常会不可避免地遇到各种可预知的、不可预知的异常,此时我们就需要对这些异常处理,以保证程序正常运行。

Spring MVC 提供了一个名为 HandlerExceptionResolver 的异常处理器接口,它可以对控制器方法执行过程中出现的各种异常进行处理。

Srping MVC 为 HandlerExceptionResolver 接口提供了多个不同的实现类,其中最常用的实现类如下。

  • DefaultHandlerExceptionResolver

  • ResponseStatusExceptionResolver

  • ExceptionHandlerExceptionResolver

  • SimpleMappingExceptionResolver

其中,ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver 是 Spring MVC 的默认异常处理器。

如果程序发生异常,Spring MVC 会按照 ExceptionHandlerExceptionResolver → ResponseStatusExceptionResolver → DefaultHandlerExceptionResolver 的顺序,依次使用这三个异常处理器对异常进行解析,直到完成对异常的解析工作为止。

DefaultHandlerExceptionResolver

DefaultHandlerExceptionResolver 是 HandlerExceptionResolver 接口的常用实现类之一,更是 Spring MVC 提供的默认异常处理器之一,Spring MVC 默认通过它对控制器处理请求时出现的异常进行处理。

DefaultHandlerExceptionResolver 提供了一个 doResolveException() 方法,其返回类型为 ModelAndView。该方法会在控制器方法出现指定异常时,生成一个新的包含了异常信息的 ModelAndView 对象替换控制器方法的 ModelAndView 对象,以达到跳转到指定的错误页面,展示异常信息的目的,其部分源码如下。

@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    try {
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
        }

         if (ex instanceof HttpMediaTypeNotSupportedException) {
            return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
        }

        if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
        }
        ……
    } catch (Exception var6) {
        if (this.logger.isWarnEnabled()) {
            this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", var6);
        }
    }
    return null;
}

从上面的代码可以看出,DefaultHandlerExceptionResolver 的 doResolveException() 方法可以将 Spring MVC 产生的各种异常转换为合适的状态码(code)。通过这些状态码,我们就可以进一步的确定发生异常的原因,以便于找到对应的问题。

下表中列举了 Spring MVC 中一些常见异常的默认状态码

异常

状态码

说明

HttpRequestMethodNotSupportedException

405(Method Not Allowed)

HTTP 请求方式不支持异常

HttpMediaTypeNotSupportedException

415(Unsupported Media Type)

HTTP 媒体类型不支持异常

HttpMediaTypeNotAcceptableException

406(Not Acceptable)

HTTP 媒体类型不可接受异常

BindException

400(Bad Request)

数据绑定异常

MissingServletRequestParameterException

400(Bad Request)

缺少参数异常

ConversionNotSupportedException

500(Internal Server Error)

数据类型转换异常

TypeMismatchException

400(Bad Request)

类型不匹配异常

HttpMessageNotReadableException

400(Bad Request)

HTTP 消息不可读异常

HttpMessageNotWritableException

500(Internal Server Error)

HTTP 消息不可写异常

ResponseStatusExceptionResolver

ResponseStatusExceptionResolver 也是 HandlerExceptionResolver 的实现类之一。与 DefaultHandlerExceptionResolver 一样,ResponseStatusExceptionResolver 也是 Spring MVC 提供的默认异常处理器之一,它被用来解析 @ResponseStatus 注解标注的自定义异常,并把异常的状态信息返回给客户端展示。

@ResponseStatus 注解

@ResponseStatus 注解主要用来标注在自定义的异常类上,示例代码如下。

package net.biancheng.c.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "自定义异常")
public class UserNotExistException extends RuntimeException {
}

如果程序运行时发生了这个自定义的异常,Spring MVC 就会通过 ResponseStatusExceptionResolver 对该异常进行解析,并将异常信息展示到错误页上。

@ResponseStatus 注解包含了三个属性,如下表。

属性

说明

code

设置异常的状态码。

code 为 @ResponseStatus 注解 value 属性的别名,与 value 属性完全等价。

value

设置异常的状态码。

value 为 @ResponseStatus 注解 code 属性的别名,与 code 属性完全等价。

reason

设置异常的原因或描述。

ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver 是 HandlerExceptionResolver 接口的实现类之一,它也是 Spring MVC 提供的默认异常处理器之一。

ExceptionHandlerExceptionResolver 可以在控制器方法出现异常时,调用相应的 @ExceptionHandler 方法(即使用了 @ExceptionHandler 注解的方法)对异常进行处理。

@ExceptionHandler 注解

Spring MVC 允许我们在控制器类(Controller 类)中通过 @ExceptionHandler 注解来定义一个处理异常的方法,以实现对控制器类内发生异常的处理。

package net.biancheng.c.controller;

import net.biancheng.c.exception.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController2 {

    //控制器方法
    @RequestMapping(value = "/testExceptionHandler")
    public String testExceptionHandler() {
        //发生 ArithmeticException 异常
        System.out.println(10 / 0);
        return "success";
    }

    //使用 @ExceptionHandler 注解定义一个异常处理方法
    @ExceptionHandler(ArithmeticException.class)
    public String handleException(ArithmeticException exception, Model model) {
        //将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
        model.addAttribute("ex", exception);
        //跳转到错误页
        return "error";
    }

}

@ExceptionHandler 注解中包含了一个 value 属性,我们可以通过该属性来声明一个指定的异常。如果在程序运行过程中,这个 Controller 类中的方法发生了这个指定的异常,那么 ExceptionHandlerExceptionResolver 就会调用这个 @ExceptionHandler 方法对异常进行处理。

@ExceptionHandler 方法的优先级

如果我们在同一个控制器类内使用 @ExceptionHandler 注解定义了多个异常处理的方法,那么我们就需要注意下 @ExceptionHandler 方法的优先级问题。

package net.biancheng.c.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {

    //控制器方法
    @RequestMapping(value = "/testExceptionHandler")
    public String testExceptionHandler() {
        //发生 ArithmeticException 异常
        System.out.println(10 / 0);
        return "success";
    }

    //使用 @ExceptionHandler 注解定义一个异常处理方法
    @ExceptionHandler(value = {Exception.class})
    public String handleException(Exception exception, Model model) {
        //将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
        model.addAttribute("ex", exception);
        //跳转到错误页
        return "error";
    }


    @ExceptionHandler(value = {RuntimeException.class})
    public String handleException2(Exception exception, Model model) {
        //将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
        model.addAttribute("ex", exception);
        //跳转到错误页
        return "error-2";
    }
}

从上面的代码可以看出,handleException 声明的异常为 Exception,handleException 声明的异常为 RuntimeException,且 Exception 是 RuntimeException 的父类。若此时控制器方法发生了 ArithmeticException 异常,那么 ExceptionHandlerExceptionResolver 会根据继承关系,调用继承深度最浅的异常处理方法(即 handleException2 方法),对异常进行处理。

注意,定义在某个控制器类中的 @ExceptionHandler 方法只在当前的控制器中有效,它只能处理其所在控制器类中发生的异常。

全局异常处理

我们还可以将 @ExceptionHandler 方法定义在一个使用了 @ControllerAdvice 注解的类中。使用 @ControllerAdvice 注解的类可以包含多个不同的带有 @ExceptionHandler 注解的方法,这些方法可以应用应用程序中所有带有 @RequestMapping 注解的控制器方法中,实现全局异常处理。

package net.biancheng.c.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ExceptionControllerAdvice {
    @ExceptionHandler
    public String exceptionAdvice(Exception exception, Model model) {
        System.out.println("ExceptionControllerAdvice>>>>>>>>>>>>>>>>>>>");
        model.addAttribute("ex", exception);
        return "error-2";
    }
}

RESTful(REST风格)是什么(熟悉)

RESTful(REST 风格)是一种当前比较流行的互联网软件架构模式,它充分并正确地利用 HTTP 协议的特性,为我们规定了一套统一的资源获取方式,以实现不同终端之间(客户端与服务端)的数据访问与交互。

什么是 REST

说到 REST,我们可能会想到英文单词 rest(意为:休息、放松等),但这里的 REST 实际上是 Resource Representational State Transfer 的缩写,翻译成中文就是“表现层资源表述状态转移”。

我们可以从以下 3 个角度来理解 REST。

1. Resource(资源)

当我们把 Web 工程部署到服务器(例如 Tomcat)中之后,那么这个工程中的所有内容在都可以被称为这个服务器中的资源。它可以是一个类、一个 HTML 文件、一个 CSS 文件、一个 JS 文件、数据库中的一张表、一段文本、一张图片、一段音频等等,它们都可以被称为资源。而服务器则可以看作是由许许多多离散的资源组成的。

这些资源都有一个共同的特征,那就是它们都可以通过一个 URI(统一资源标识符) 进行标识,任何对于该资源的操作都不能改变其 URI。想要获取这个资源,只要访问它的 URI 即可。

2. Representation(资源的表述)

资源的表述指的是资源在某个特定时刻的状态的描述,即资源的具体表现形式,它可以有多种格式,例如 HTML、XML、JSON、纯文本、图片、视频、音频等等。通常情况下,服务端与客户端资源的表述所有使用的格式往往是不同的,例如在服务端资源可能是数据库中的一段纯文本、一个 XML 文件、或者是数据库中的一张表,而客户端则可能是表现为 HTML 页面、JSON、甚至是音频和视频。

3.State Transfer(状态转移)

所谓的资源状态转移,简单点说就是,客户端与服务端进行交互时,资源从一种表现形式转换到另一种表现形式的过程。但是 HTTP 协议是一种无状态协议,它是无法保存任何状态的,因此如果客户端想要获取服务器上的某个资源,就必须通过某种手段让资源在服务器端发生“状态转化”,而这种状态转化又是建立在应用的表现层(UI)上的。这就是“表现层资源状态转移”的含义。

REST 实际上描述的是服务器与客户端的一种交互形式,REST 本身并不是一个实用的概念,真正实用的是如何设计 RESTFul(REST 风格)的接口,即我们到底通过什么样的手段让资源在服务器端发生状态转移。

RESTFul

在传统的项目开发中,我们通常都会将操作资源的动词写进 URL 中,而这些动词通常都是我们自行定义的,并没有一个统一的规范。莎士比亚说:一千个人眼中就有一个千个哈姆雷特,这句话应用在这里,再合适不过了。哪怕是对同一资源的相同操作,不同的人所定义的 URL 也是各不相同的。

例如,同样都是通过用户 ID 获取用户信息的请求,其 URL 可能是以下多种形式。

  • http://localhost:8080/biancheng/getUserById?id=1

  • http://localhost:8080/biancheng/user/getById?id=1

  • http://localhost:8080/biancheng/getUserInfo?id=1

  • http://localhost:8080/biancheng/a/b?id=1

RESTFul 提倡我们使用统一的风格来设计 URL,其规则如下。

1. URL 只用来标识和定位资源,不得包含任何与操作相关的动词。例如访问与用户(user)相关的资源时,其 URL 可以定义成以下形式。

http://localhost:8080/biancheng/user

2. 当请求中需要携带参数时,RESTFul 允许我们将参数通过斜杠(/)拼接到 URL 中,将其作为 URL 的一部分发送到服务器中,而不再像以前一样使用问号(?)拼接键值对的方式来携带参数,示例如下。

http://localhost:8080/biancheng/user/1

:我们在 URL 的末尾通过 “/1”的形式传递了一个取值为 1 的参数。

3. HTTP 协议中有四个表示操作方式的动词:GET、POST、PUT 和 DELETE,它们分别对应了四种与资源相关的基本操作: GET 用来获取资源, POST 用来新建资源, PUT 用来更新资源, DELETE 用来删除资源。客户端通过这四个动词,即可实现对服务器端资源状态转移的描述。

资源操作

传统方式 URL

RESTFul URL

HTTP 请求方式

获取资源(SELECT)

http://localhost:8080/biancheng/getUserById?id=1

http://localhost:8080/biancheng/user/1

GET

保存或新增资源(INSERT)

http://localhost:8080/biancheng/saveUser

http://localhost:8080/biancheng/user

POST

修改或更新资源(UPDATE)

http://localhost:8080/biancheng/updateUser

http://localhost:8080/biancheng/user

PUT

删除资源(DELETE)

http://localhost:8080/biancheng/deleteUser?id=1

http://localhost:8080/biancheng/user/1

DELETE

事实上,Spring 4.3 之后,为了更好的支持 RESTful 风格,增加了几个注解:@PutMapping、@GetMapping、@DeleteMapping、@PostMapping,从名字也能大概的看出,其实也就是将 method 属性的值与 @RequestMapping 进行了绑定而已

Spring MVC REST 风格(熟悉)

在 Spring MVC 中,我们可以通过 @RequestMapping +@PathVariable 注解的方式,来实现 RESTful 风格的请求。

1. 通过@RequestMapping 注解的路径设置

当请求中携带的参数是通过请求路径传递到服务器中时,我们就可以在 @RequestMapping 注解的 value 属性中通过占位符 {xxx} 来表示传递的参数,示例代码如下。

@RequestMapping("/testRest/{id}/{username}")

注意:value 属性中占位符的位置应当与请求 URL 中参数的位置保持一致,否则会出现传错参数的情况。

2. 通过 @PathVariable 注解绑定参数

我们可以在控制器方法的形参位置通过 @PathVariable 注解,将占位符 {xxx} 所表示的参数赋值给指定的形参。

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

3. 通过 HiddenHttpMethodFilter 对请求进行过滤

由于浏览器默认只支持发送 GET 和 POST 方法的请求,因此我们需要在 web.xml 中使用 Spring MVC 提供的 HiddenHttpMethodFilter 对请求进行过滤。这个过滤器可以帮助我们将 POST 请求转换为 PUT 或 DELETE 请求,其具体配置内容如下。

<!--来处理 PUT 和 DELETE 请求的过滤器-->
<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

HiddenHttpMethodFilter 处理 PUT 和 DELETE 请求时,必须满足以下 2 个条件:

  • 当前请求的请求方式必须为 POST;

  • 当前请求必须传输请求参数 _method。

在满足了以上条件后,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 _method 的值,即请求参数 _method 的值才是最终的请求方式,因此我们需要在 POST 请求中携带一个名为 _method 的参数,参数值为 DELETE 或 PUT。

注意:若 web.xml 中同时存在 CharacterEncodingFilter 和 HiddenHttpMethodFilter 两个过滤器,必须先注册 CharacterEncodingFilter,再注册 HiddenHttpMethodFilter。

代码演示

<!-- 获得get -->
<form action="stuManager/${stu.stuNo }.action" method="get">
    <input type="submit" value="查看">
</form>
 
<!-- 添加post -->
<form action="${ctxPath}/stuManager.action" method="post"> 
    <input type="submit" value="添加">
</form>
 
<!-- 修改put -->
<form action="${ctxPath}/stuManager.action" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="submit" value="修改">
</form>
 
<!-- 删除delete -->
<form action="stuManager/${stu.stuNo }.action" method="post">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="删除">
</form>
/**
 * 提交方式GET
 * 通过学生编号stuNo获得学生信息
 */
@RequestMapping(value="/stuManager/{stuNo}", method=RequestMethod.GET)
public String getStuInfo(@PathVariable("stuNo") String stuNo, Map<String,Object> map){
    map.put("stu", us.getStuInfo(stuNo));
    //实现Service层方法获得学生信息,并添加进map返回前台
    return "queStu";
}
 
/**
 * 提交方式POST
 * 添加学生信息
 */
@RequestMapping(value="/stuManager", method=RequestMethod.POST)
public String addStu(Student stu, Map<String,Object> map){
    us.addStu(stu);
    //实现Service层方法添加学生信息
    map.put("msg", "学生信息添加成功");
    return "addStu";
}
 
/**
 * 提交方式PUT
 * 修改学生信息
 */
@RequestMapping(value="/stuManager", method=RequestMethod.PUT)
public String updateStu(Student stu){
    us.updateStu(stu);
    //实现Service层方法更新学生信息
    return "redirect:/stuList";
}
 
/**
 * 提交方式DELETE
 * 通过学生编号stuNo删除学生信息
 */
@RequestMapping(value="/stuManager/{stuNo}", method=RequestMethod.DELETE)
public String delStu(@PathVariable("stuNo") String stuNo){
    us.delStu(stuNo);
    //实现Service层方法删除学生信息
    return "redirect:/stuList";
}

讲解:前端代码其实就是在表单里设置表单提交的方法为post,然后在里面加一个隐藏标签<input type="hidden" name="_method" value="put/delete"/>就可,后端代码可以在@RequestMapping中指定对应的方法为put或者delete或者用@PutMapping、@GetMapping、@DeleteMapping、@PostMapping注解

例如@RequestMapping(value="/stuManager", method=RequestMethod.PUT/DELETE)

Spring MVC 文件上传(熟悉)

在实际的项目开发中,文件的上传和下载可以说是最常用的功能之一,例如图片的上传与下载、邮件附件的上传和下载等。在 Spring MVC 中想要实现文件上传工作,需要的步骤如下。

1. 编写 form 表单

在 Spring MVC 项目中,大多数的文件上传功能都是通过 form 表单提交到后台服务器的。

form 表单想要具有文件上传功能,其必须满足以下 3 个条件。

  • form 表单的 method 属性必须设置为 post。

  • form 表单的 enctype 属性设置为 multipart/form-data。

  • 至少提供一个 type 属性为 file 的 input 输入框。

常见的文件上传表单示例代码如下。

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="fileName" multiple="multiple"/>
    <input type="submit" value="上传">
</form>

当 form 表单的 enctype 属性为 multipart/form-data 时,浏览器会以二进制流的方式对表单数据进行处理,由服务端对文件上传的请求进行解析和处理。

在上面的代码中,除了满足文件上传表单所必须具备的 3 个条件外,<input> 标签中还增加了一个 multiple 属性。该属性可以让我们同时选择对个文件进行上传,即实现多文件上传功能。

2. 配置文件解析器(MultipartResolver )

Spring MVC 提供了一个名为 MultipartResolver 的文件解析器,来实现文件上传功能。MultipartResolver 本身是一个接口,我们需要通过它的实现类来完成对它的实例化工作。

MultipartResolver 接口共有两个实现类,如下表。

实现类

说明

依赖

支持的 Servlet 版本

StandardServletMultipartResolver

它是 Servlet 内置的上传功能。

不需要第三方 JAR 包的支持。

仅支持 Servlet 3.0 及以上版本

CommonsMultipartResolver

借助 Apache 的 commons-fileupload 来完成具体的上传操作。

需要 Apache 的 commons-fileupload 等 JAR 包的支持。

不仅支持 Servlet 3.0 及以上版本,还可以在比较旧的 Servlet 版本中使用。

以上这两个 MultipartResolver 的实现类,无论使用哪一个都可以实现 Spring MVC 的文件上传功能。这里,我们以 CommonsMultipartResolver 为例进行讲解。

想要在 Spring MVC 中使用 CommonsMultipartResolver 对象实现文件上传,我们需要在 Spring MVC 的配置文件中对其进行以下配置。

<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"></property>
    <property name="maxUploadSize" value="1024000"></property>
</bean>

在以上配置中,除了定义了 CommonsMultipartResolver 的 Bean 外,还通过 <property> 标签对文件的编码格式和上传文件的大小进行了配置。

通过 <property> 可以对 CommonsMultipartResolver 的多个属性进行配置,其中常用的属性如下表。

属性

说明

defaultEncoding

上传文件的默认编码格式。

maxUploadSize

上传文件的最大长度(单位为字节)。

maxInMemorySize

读取文件到内存中的最大字节数。

resolveLazily

判断是否要延迟解析文件。

注意:当我们在 Spring MVC 的配置文件中对 CommonsMultipartResolver 的 Bean 进行定义时,必须指定这个 Bean 的 id 为 multipartResolver,否则就无法完成文件的解析和上传工作。

3. 引入 Jar 包

由于 CommonsMultipartResolver 是 Spring MVC 内部通过 Apache Commons FileUpload 技术实现的,因此我们还需要将 Apache Commons FileUpload 组件的相关依赖引入到项目中。

  • commons-fileupload-xx.xx.xx.jar

  • commons-io-x.x.x.jar

4. 编写控制器方法

在完成上面的所有步骤后,接下来,我们只需要在 Controller 中编写文件上传的方法即可实现文件的上传。

@Controller
public class FileUploadController {

    @RequestMapping("/uplaod")
    public String upload(MultipartFile file) {
        if (!file.isEmpty()) {
            return "success";
        }
        return "error";
    }
}

在该控制器方法中包含一个 org.springframework.web.multipart.MultipartFile 接口类型的形参,该参数用来封装被上传文件的信息。MultipartFile 接口是 InputStreamSource 的子接口,该接口中提供了多个不同的方法,如下表。

名称

作用

byte[] getBytes()

以字节数组的形式返回文件的内容。

String getContentType()

返回文件的内容类型。

InputStream getInputStream()

返回一个 input 流,从中读取文件的内容。

String getName()

返回请求参数的名称。

String getOriginalFillename()

返回客户端提交的原始文件名称。

long getSize()

返回文件的大小,单位为字节。

boolean isEmpty()

判断被上传文件是否为空。

void transferTo(File destination)

将上传文件保存到目标目录下。

代码演示:http://c.biancheng.net/spring_mvc/9683.html

Spring MVC 文件下载 (熟悉)

文件下载的含义十分简单,它指的就是将服务器中的文件下载到本机上。

下面就结合一个实例,来演示下如何在 Spring MVC 中实现文件的下载功能,可以分为以下步骤。

1. 在《Spring MVC文件上传》中创建的 springmvc-file-demo 的工程中,修改 success.html 的代码,在每个图片下面添加一个文件下载的超链接,代码如下。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>学生信息上传成功</h1>

<table>
    <tr>
        <td>学号:</td>
        <td th:text="${student.getStuId()}"></td>
    </tr>

    <tr>
        <td>姓名:</td>
        <td th:text="${student.getStuName()}"></td>
    </tr>
    <tr>
        <td>年龄:</td>
        <td th:text="${student.getAge()}"></td>
    </tr>
    <tr>
        <td>照片:</td>
        <td th:each="p:${student.getPath()}">
            <img th:src="${#servletContext.getContextPath()}+'/upload/'+${p}" width='200px' height='200px'/><br>
            <!--图片下载的超链接-->
            <a th:href="@{/downLoadFile(fileName=${p})}">点击下载图片</a>
        </td>
    </tr>
</table>
</body>
</html>

2. 在net.biancheng.c.controller 包下新建一个名为 DownLoadController 的控制器类,代码如下。

package net.biancheng.c.controller;

import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

@Controller
public class DownLoadController {

    /**
     * 文件下载
     *
     * @param request
     * @param fileName
     * @return
     * @throws IOException
     */
    @RequestMapping("/downLoadFile")
    public ResponseEntity<byte[]> downLoadFile(HttpServletRequest request, String fileName) throws IOException {
        //得到图片的实际路径
        String realPath = request.getServletContext().getRealPath("/upload/" + fileName);
        //创建该图片的对象
        File file = new File(realPath);
        //将图片数据读取到字节数组中
        byte[] bytes = FileUtils.readFileToByteArray(file);
        //创建 HttpHeaders 对象设置响应头信息
        HttpHeaders httpHeaders = new HttpHeaders();
        //设置图片下载的方式和文件名称
        httpHeaders.setContentDispositionFormData("attachment", toUTF8String(fileName));
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
    }


    /**
     * 下载保存时中文文件名的字符编码转换方法
     */
    public String toUTF8String(String str) {
        StringBuffer sb = new StringBuffer();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            // 取出字符中的每个字符
            char c = str.charAt(i);
            // Unicode码值为0~255时,不做处理
            if (c >= 0 && c <= 255) {
                sb.append(c);
            } else { // 转换 UTF-8 编码
                byte b[];
                try {
                    b = Character.toString(c).getBytes("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    b = null;
                }
                // 转换为%HH的字符串形式
                for (int j = 0; j < b.length; j++) {
                    int k = b[j];
                    if (k < 0) {
                        k &= 255;
                    }
                    sb.append("%" + Integer.toHexString(k).toUpperCase());
                }
            }
        }
        return sb.toString();
    }
}

在 DownLoadController 类中共包含以下 2 个方法:

  • downLoadFile() 方法:负责文件的下载工作,我们首先根据文件路径和文件名称创建一个 File 对象,然后对响应头中文件的打开方式和下载方式进行了设置,并通过 ResponseEntity 对下载结果对象进行封装。

  • toUTF8String() 方法:负责完成中文文件名的字符编码转换。

ResponseEntity 对象与前面的章节中介绍的 @ResponseBody 注解相似,它也是用来直接返回结果对象的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Linux运维总结:使用旧版redis-shake2.x进行redis集群间的数据同步【方案二】》

一、redis-shake简介 redis-shake是阿里云开源的用于Redis数据迁移和过滤的工具。 Github参考&#xff1a;redis-shake简介 1.1、迁移工具对比 redis-migrate-tool redis-migrate-tool是唯品会开源的一款Redis异构集群之间的数据实时迁移工具&#xff0c;不过已经有两年没有更…

如何对【javaSE】语法阶段复习

下面是我对学习java阶段的总复习&#xff0c;我愿称之为【复习宝典】 如果你对java的部分语法阶段的知识有所困惑&#xff0c;进来看看吧&#xff01; 文章目录 目录 文章目录 一、初始java 1.1java 的由来 1.2JDK安装 1.3main方法的介绍 二、数据类型和变量 2.1数据类型 三、运…

xxljob 里面 InheritableThreadLocal详解,XxlJobContext类的详解

目录 1 需求2 XxlJobContext类3 InheritableThreadLocal 类可以做什么1 需求 在xxljob 里面,有一个地方使用到了InheritableThreadLocal类 我们先说XxlJobContext类是干什么的,里面有什么东西 2 XxlJobContext类 这个类就是一个实体类,可以理解为实体类,里面有属性,有g…

Spring Boot读取配置文件内容的三种方式

系列文章目录 Spring Boot[概述、功能、快速入门]_心态还需努力呀的博客-CSDN博客 该系列文章持续更新中~ 目录 系列文章目录 前言 一、Value 二、Environment 2.1 注入对象 2.2 调用获取属性的方法 2.3 上述两种方法对比 三、ConfigurationProperties 3.1 创建一个实…

前端工程师leetcode算法面试必备-简单的二叉树

一、前言 本难度的题目主要考察二叉树的基本概念和操作。 1、基本概念 树是计算机科学中经常用到的一种非线性数据结构&#xff0c;以分层的形式存储数据。二叉树是一种特殊的树结构&#xff0c;每个节点最多有两个子树&#xff0c;通常子树被称作“左子树”和“右子树”。 …

在结构异质的云杉林中使用机载激光扫描仪数据进行单树分割

论文题目&#xff1a;Single Tree Segmentation Using Airborne Laser Scanner Data in a Structurally Heterogeneous Spruce Forest Abstract 在这项研究中&#xff0c;我们提出了一种基于机载激光扫描的树冠表面模型 (CSM) 及其相应点云的单树分割和表征的新方法。该方法包…

AI算法工程师 | 09机器学习-概率图模型(一)概率图模型概述

目录机器学习 - 概率图模型 之 概率图模型概述1、概率图模型学习的意义2、有向图和无向图3、生成式模型与判别式模型机器学习 - 概率图模型 之 概率图模型概述 本阶段将开启 概率图模型系列 的旅程。 1、概率图模型学习的意义 从自然语言处理 NLP 的角度看&#xff1a; 在自…

前端性能优化(四):资源优化

目录 一&#xff1a;资源的压缩与合并 1.1.为什么要压缩&合并 1.2.HTML压缩&#xff1a; 1.3.CSS压缩&#xff1a; 1.4.JS 压缩与混淆&#xff1a; 1.5.CSS JS 文件合并&#xff1a; 二&#xff1a;图片格式优化 2.1.JPEG/JPG&#xff1a; 2.2.PNG&#xff1a; 2…

linux之终端里sqlite数据库的使用

一. linux下安装数据库和创建一个数据库 1.安装命令&#xff1a; (1)sudo apt-get install sqlite (2) sudo apt-get install libsqlite3-dev 2.安装完后,创建一个数据库&#xff0c;终端下输入命令 【sqlite3 数据库名字 】数据库名字以.db 结尾格式 创建数据库student.db 【 …

node.js--vm沙箱逃逸初探

前言 前几天遇到一个考察vm沙箱逃逸的题目&#xff0c;由于这个点是第一次听说&#xff0c;所以就花时间了解了解什么是沙箱逃逸。此篇文章是对于自己初学vm沙箱逃逸的学习记录&#xff0c;若记录知识有误&#xff0c;欢迎师傅们指正。 什么是沙箱 就只针对于node.js而言&am…

有关于huggingface tokenizer的text_pair

tokenizer有一个名为text pair的参数&#xff0c;这个参数在做NLI任务的时候很有用&#xff0c;因为输入不是一个single sentence&#xff0c;而是sentence pair。 但是这个参数的类型让人非常confused&#xff0c;而且同时还有一个text参数&#xff0c;让人不知道传入的sente…

Java-集合(2)

List集合 List接口是Collection接口的子接口 List集合类的元素是有序的&#xff08;添加顺序和取出顺序是一致的&#xff09;且可重复List集合类的每个元素都有对应的索引(和数组索引是一样的) List集合的常用方法 add(int index Object ele)&#xff1a;在index索引位置插…

软件供应链安全中:攻击面远超想象

| 软件供应链攻击3年飙升742% | 引人注目的软件供应链攻击正在上升。欧盟网络安全机构ENISA报告称&#xff0c;自2020年初以来&#xff0c;有组织的软件供应链攻击数量增加了4倍。Gartner认为&#xff0c;这一趋势将持续下去。在《软件工程领导者如何降低软件供应链安全风险》…

dubbo学习笔记1(小d课堂)

常见的dubbo项目分层&#xff1a; 搭建springbootdubbo环境 我们首先用idea创建一个maven项目&#xff1a; 我们把src删除&#xff0c;在里面创建module&#xff1a; 然后接下来我们就要去用springboot去整合dubbo。 我们可以去github上去搜索dubbo&#xff0c;会找到dubbo-s…

心理应激微反应刑事侦查应用,社交行为、情绪行为、生物行为,说谎掩饰,单向表达不分析,情绪反应管理机制,惊讶,厌恶,愤怒,恐惧,悲伤,愉悦

心理应激微反应刑事侦查应用&#xff0c;社交行为、情绪行为、生物行为&#xff0c;说谎掩饰&#xff0c;单向表达不分析&#xff0c;情绪反应管理机制&#xff0c;惊讶&#xff0c;厌恶&#xff0c;愤怒&#xff0c;恐惧&#xff0c;悲伤&#xff0c;愉悦 提示&#xff1a;系列…

【uni-app学习之日历组件】(calendar 底部可展开 折叠)

链接 效果图 代码块 <template><tmt-calendar defaultDate"2021-11-03" :point-list"[2022-03-20,2022-04-01,2022-04-02,2022-04-05]" :show"true" changeDate"changeDate"></tmt-calendar> </template>参…

【王道操作系统】4.1.1 初识文件管理概念和功能

初识文件管理概念和功能 文章目录初识文件管理概念和功能1.文件的属性2.文件内部的数据如何组织起来3.文件之间应该如何组织起来4.操作系统应该向上提供哪些功能5.从上往下看&#xff0c;文件应该如何存放在外存6.其他需要由操作系统实现的文件管理功能1.文件的属性 2.文件内部…

STM32——TIM编码器接口

文章目录一、编码器接口简介二、正交编码器三、通用定时器框图四、编码器接口基本结构五、工作模式六、实例&#xff08;均不反相&#xff09;七、实例&#xff08;TI1反相&#xff09;八、编码器接口测速电路设计关键代码一、编码器接口简介 Encoder Interface 编码器接口编码…

pycharm配置详解

配置解释器File-->setting-->Project&#xff1a;somename-->Project InterpreterPycharm下Python环境没有块注释"""something"""是文档&#xff0c;不是块注释Python中就没有块注释Pycharm版本控制配置Pycharm中的快捷键pycharm中自定…

手把手教你用springboot实现jdk文档搜索引擎

目录 项目背景 项目描述 项目整体架构 项目流程 构建索引 项目背景 搜索引擎是我们经常会用到的工具&#xff0c;例如我们熟知的百度&#xff0c;谷歌等搜索引擎。除了网络搜索引擎&#xff0c;还有很多地方也有搜索引擎的身影&#xff0c;例如视频网站的搜索框&#xff0c;…