SpringMVC 框架学习4

简介: SpringMVC 框架学习4

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 用来删除资源。客户端通过这四个动词,即可实现对服务器端资源状态转移的描述。


1686480329330.png

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


Spring MVC REST 风格(熟悉)

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

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

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

注意: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 接口共有两个实现类,如下表。


1686480462806.png

以上这两个 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 的子接口,该接口中提供了多个不同的方法,如下表。


1686480513170.png

代码演示: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 注解相似,它也是用来直接返回结果对象的。


目录
相关文章
|
2月前
|
前端开发 Java Maven
SpringMVC之入门搭建框架
SpringMVC之入门搭建框架
|
2月前
|
前端开发 Java 应用服务中间件
SpringMVC基础篇:MVC基础知识
vSpringMVC基础篇:MVC基础知识
|
7月前
|
JSON 前端开发 Java
SpringMVC基础(上)
SpringMVC基础(上)
|
7月前
|
存储 JSON 前端开发
SpringMVC基础(下)
SpringMVC基础(下)
|
9月前
|
前端开发 Java 应用服务中间件
SpringMVC学习
SpringMVC学习
31 0
|
设计模式 前端开发 JavaScript
SpringMVC实战入门教程,四天快速搞定springmvc框架!
SpringMVC 也叫Spring web mvc。是Spring 框架的一部分,是在Spring3.0 后发布的。 这里对SpringMVC框架进行一个简单的介绍: • springmvc是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合。 • springmvc是一个基于mvc的web框架。 • springmvc 表现层:方便前后端数据的传输 • Spring MVC 拥有控制器,作用跟Struts类似,接收外部请求,解析参数传给服务层 MVC是指,C控制层,M模块层,V显示层这样的设计理念,而SSM框架里面SPRING MVC本身就是MVC框架,
256 0
|
设计模式 开发框架 前端开发
SpringMVC 框架学习1
SpringMVC 框架学习
76 0
|
前端开发 Java 应用服务中间件
SpringMVC 框架学习2
SpringMVC 框架学习2
59 0
|
XML JSON 前端开发
SpringMVC 框架学习3
SpringMVC 框架学习3
54 0
|
JSON 前端开发 NoSQL
【SSM】Spring MVC 程序开发(重点:SpringMVC 工作流程)
本文重点介绍SpringMVC 工作流程、Spring MVC的主要组件、Spring MVC 如何连接、如何获取参数、如何输出数据的。
103 0