[SpringBoot3]Web服务

news2024/12/27 1:34:52

五、Web服务

  • 基于浏览器的B/S结构应用十分流行。SpringBoot非常适合Web应用开发,可以使用嵌入式Tomcat、Jetty、Undertow或Netty创建一个自包含的HTTP服务器。一个SpringBoot的Web应用能够自己独立运行,不依赖需要安装的Tomcat、Jetty等。
  • SpringBoot可以创建两种类型的Web应用
    • 基于Servlet体系的Spring Web MVC应用
    • 使用spring-boot-starter-webflux模块来构建响应式,非阻塞的Web应用程序
  • Spring WebFlux是单独一个体系的内容。Spring Web MVC又被称为SpringMVC,专注web应用开发。我们快速的创建控制器(Controller),接受来自浏览器或其他客户端的请求,并将业务代码的结果返回给请求方。

5.1高效构建Web应用

  • 创建Web应用,依赖选择spring-web(包含了Spring MVC、Tomcat)、Thymeleaf(视图技术,代替jsp)、Lombok。
  • 项目结构:

5.1.1html页面视图

1.Maven依赖

<dependencies>
    <!--Thymeleaf视图的依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!--SpringWeb依赖:包含了SpringMVC、Tomcat服务器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.创建Controller

@Controller
/**
 * @Controller:创建控制器对象,控制器能够浏览器接收请求,并响应结果给浏览器,控制器也叫处理器
 */
public class QuickController {
    @RequestMapping("/exam/quick")
    public String quick(Model model) {
        model.addAttribute("title", "Web开发");
        model.addAttribute("time", LocalDateTime.now());
        return "quick";
    }
}

3.创建视图

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div style="margin-left: 200px">
    <h3>视图技术</h3>
    <div th:text="${title}"></div>
    <div th:text="${time}"></div>
</div>
</body>
</html>

4.代码编写完成,启动运行类,在浏览器访问exam/quick地址

  • 编写SpringMVC的应用分成三步:
    • 编写请求页面(在浏览器直接模拟的请求)
    • 编写Controller
    • 编写视图技术

5.1.2JSON视图

  • 上面的例子以Html文件作为视图,可以编写复杂的交互的页面,CSS美化数据。除了带有页面的数据,还有一种只需要数据的视图,比如手机应用app,app的数据来自服务器应用处理结果。主流的方式是服务器返回json格式数据给手机app应用。我们可以通过原始的HttpServletResponse响应数据给请求方。借助SpringMVC能够无感知的处理json。

1.创建Controller

@Data
public class User {
    private String username;
    private Integer age;
}

@Controller
public class JSONViewController {

    //显示JSON视图,包含json数据
    @RequestMapping("/exam/json")
    public void responseJson(HttpServletResponse response) throws IOException {
        String json = "{\"name\":\"lisi\",\"age\":20}";
        //响应
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println(json);
        out.flush();
        out.close();
    }

    //SpringMVC支持控制器方法返回对象,由框架将对象使用jackson转为json并输出
    //User--Jackson工具库的ObjectMapper对象,---user转为json格式字符串--HttpServletResponse输出

    /**
     * 接收请求的注解
     *
     * @GetMapping:接收get请求,简化的@RequestMapping(method=RequestMethod.GET)
     * @PostMapping:接收post请求
     * @PutMapping:接收put请求
     * @DeleteMapping:接收delete请求
     */

    @ResponseBody
    //@RequestMapping("/exam/json2")
    @GetMapping("/exam/json2")
    public User getUserJson() {
        User user = new User();
        user.setUsername("张三");
        user.setAge(22);
        return user;
    }
}
  • 注意:从Spring6,SpringBoot3开始javax包名称,修改为jakarta。

2.浏览器测试两个地址

  • 构建前后端分离项目经常采用这种方式。

5.1.3给项目加favicon

  • 什么是favicon.ico?
    • favicon.ico是网站的缩略标志,可先显示浏览器标签、地址栏左边和收藏夹,是展示网站个性的logo标志。

  • 我们自己的网站定制 logo。首先找一个在线工具创建 favicon.ico。比如 https://quanxin.org/favicon , 用文字、图片生成我们需要的内容。生成的 logo 文件名称是 favicon.ico。

1.将生成的favicon.ico拷贝到项目的resources/或resources/static/目录。

2.在你的视图文件,加入对favicon.ico的引用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="icon" href="../favicon.ico" type="image/x-icon"/>
</head>
<body>
<div style="margin-left: 200px">
    <h3>视图技术</h3>
    <div th:text="${title}"></div>
    <div th:text="${time}"></div>
</div>
</body>
</html>
  • 注意:如果项目有过滤器,拦截器要放行对favicon.ico的访问。

5.2SpringMVC

  • SpringMVC是非常著名的Web应用框架,现在的大多数Web项目都采用SpringMVC。它与Spring有着紧密的联系,是Spring框架中的模块,专注于Web应用,能够使用Spring提供的强大功能,IoC、Aop等等。
  • SpringMVC框架底层是基于Servlet技术,遵循Servlet规范,Web组件Servlet、Filter、Listener在SpringMVC中都能使用。同时SpringMVC也是基于MVC架构的,职责分离,每个组件只负责自己的功能,组件解耦。
  • SpringBoot的自动配置、按约定编程极大简化、提供了Web应用的开发效率,

5.2.1控制器Controller

  • 控制器用来处理请求,接收浏览器发送过来的参数,将数据和视图应答给浏览器或者客户端app等。
  • 控制器是一个普通的Bean,使用@Controller或者@RestController注解。
    • @RestController包含了@Controller的功能,同时加入了@ResponseBody的注解,表示当前控制器类中的所有方法都会默认加上@ResponseBody的功能。
    • @Controller注解有@Component的功能,控制器类对象是Spring容器管理的。
  • 如何创建控制器对象?
    • 创建普通Java类,其中定义public方法,类上注解@Controller 或者@RestController。

5.2.1.1匹配请求路径到控制器方法
  • Spring支持多种策略,匹配请求路径到控制器方法。
  • SpringBoot3推荐使用PathPatternParser策略,比之前AntPathMatcher提高6-8倍吞吐量。
  • 吞吐量:吞吐量是指单位时间内系统能够完成的工作量,它衡量的是软件系统服务器的处理能力,就是在一秒中 统计所完成的工作量。 一个系统的吞度量(承压能力)与请求对CPU的消耗、外部接口、IO等等紧密关联。

  • 通配符:
    • ?:一个字符
    • *:0或多个字符
    • **:匹配0个或多个路径

示例

@GetMapping("/file/t?st.html")
//?匹配只有一个字符
//GET http://localhost:8080/file/test.html(对)
//GET http://localhost:8080/file/teest.html(错)

@GetMapping("/file/t?st.html")
public String path1(HttpServletRequest request){
	return "path 请求="+request.getRequestURI();
}
@GetMapping ("/images/*.gif")
*:匹配一个路径段中的 0 或多个字符
() GET http://localhost:8080/images/user.gif
() GET http://localhost:8080/images/cat.gif
() GET http://localhost:8080/images/.gif
() GET http://localhost:8080/images/gif/header.gif
() GET http://localhost:8080/images/dog.jpg

@GetMapping ("/images/*.gif")
public String path2(HttpServletRequest request){
	return "* 请求="+request.getRequestURI();
}
@GetMapping ("/pic/**")
** 匹配 0 或多段路径, 匹配/pic 开始的所有请求
() GET http://localhost:8080/pic/p1.gif
() GET http://localhost:8080/pic/20222/p1.gif
() GET http://localhost:8080/pic/user
() GET http://localhost:8080/pic/

@GetMapping ("/pic/**")
public String path3(HttpServletRequest request){
	return "/pic/**请求="+request.getRequestURI();
}

RESTFul

@GetMapping("/order/{*id}")
{*id} 匹配 /order 开始的所有请求, id 表示 order 后面直到路径末尾的所有内容。
id 自定义路径变量名称。与@PathVariable 一样使用
() GET http://localhost:8080/order/1001
() GET http://localhost:8080/order/1001/2023-02-16

@GetMapping("/order/{*id}")
public String path4(@PathVariable("id") String orderId, HttpServletRequest request){
	return "/order/{*id}请求="+request.getRequestURI() + ",id="+orderId;
}
注意:@GetMapping("/order/{*id}/{*date}")无效的, {*..}后面不能在有匹配规则了
@GetMapping("/pages/{fname:\\w+}.log")
:\\w+ 正则匹配, xxx.log
() GET http://localhost:8080/pages/req.log
() GET http://localhost:8080/pages/111.log
() GET http://localhost:8080/pages/req.txt
() GET http://localhost:8080/pages/###.log
() GET http://localhost:8080/pages/.log

@GetMapping("/pages/{fname:\\w+}.log")
public String path5(@PathVariable("fname") String filename, HttpServletRequest request){
	return "/pages/{fname:\\w}.log 请求="+request.getRequestURI() + ",filename="+filename;
}
5.2.1.2@RequestMapping
  • @RequestMapping:用于将web请求映射到控制器类的方法上。此方法处理请求,可用在类上或方法上。
  • 重要的属性:
    • value:别名path表示请求的uri,在类和方法同时使用value,方法上的继承类上的value。
    • method:请求方式,支持GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE。值为:RequestMethod[] method() , RequestMethod 是 enum 类型。
  • 快捷注解:
    • @GetMapping: 表示 get 请求方式的@RequestMapping
    • @PostMapping:表示 post 请求方式的@RequestMapping
    • @PutMapping:表示 put 请求方式的@RequestMapping
    • @DeleteMapping: 表示 delete 请求方式的@RequestMapping
5.2.1.3控制器方法参数类型与可用返回值类型
  • 参数类型

  • 返回值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3KvLeMym-1692712231665)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230815113230459.png)]

5.2.1.4接收请求参数
  • 用户在浏览器中点击按钮时,会发送一个请求给服务器,其中包含让服务器程序需要做什么的参数。这些参数发送给控制器方法,控制器方法的形参列表接收请求参数。

接收参数方式

  • 请求参数与形参一一对应,适合简单类型。形参可以有合适的数据类型,比如String、Integer、int等。
@RestController
public class ParameterController {
    //一一对应,适合接收简单类型数据String、int、long、double、float,适合接收参数数量少
    //http://localhost:8080/param/p1?name=lisi&age=20&sex=m
    @GetMapping("/param/p1")
    public String p1(String name, Integer age, String sex) {
        return "接收参数:" + name + "," + age + "," + sex;
    }
}
  • 对象类型,控制器方法形参是对象,请求的多个参数名与属性名相对应。
@Data
public class Person {
    private String name;
    private Integer age;
    private String sex;
}
//使用对象接收参数,要求对象的属性名称和请求中参数名不一样,属性有set方法,类有无参数构造方法
//http://localhost:8080/param/p2?name=张三&age=22&type=asd
@GetMapping("/param/p2")
public String p2(Person person, String type) {
    return "接收参数,使用对象:" + person.toString() + ",type=" + type;
}
  • HttpServletRequest 使用request对象接收参数
//http://localhost:8080/param/p3?name=张三&age=22&sex=男
@GetMapping("/param/p3")
public String p3(HttpServletRequest request) {
    String name = request.getParameter("name");
    String age = request.getParameter("age");
    String sex = request.getParameter("sex");
    return "name=" + name + ",age=" + age + ",sex=" + sex;
}
  • @RequestParam注解,将查询参数、form表单数据解析到方法参数
//http://localhost:8080/param/p4?name=张三&age=22
@GetMapping("/param/p4")
public String p4(@RequestParam(value = "name", required = true) String name,
                 @RequestParam(value = "age", required = false, defaultValue = "26") Integer age) {
    return "p4,name=" + name + ",age=" + age;
}
  • @RequestHeader从请求header中获取某项值
//http://localhost:8080/param/p5?name=张三&age=22
//Accept: application/json
@GetMapping("/param/p5")
public String p5(@RequestParam(value = "name", required = true) String name,
                 @RequestParam(value = "age", required = false, defaultValue = "26") Integer age,
                 @RequestHeader("Accept") String accept) {
    return "p5,name=" + name + ",age=" + age + ",Accept:" + accept;
}
5.2.1.4.1接收json
  • @RequestBody接收前端传递的json格式参数
@Data
public class User {
    private String username;
    private Integer age;
}

@PostMapping("/param/json")
public String p6(@RequestBody User user) {
    return "p6,json:" + user.toString();
}

IDEA Http Client测试:

POST http://localhost:8080/param/json
Content-Type: application/json

{
  "username": "lisi",
  "age": 22
}
5.2.1.4.2Reader-InputStream
  • Reader或InputStream读取请求体的数据,适合post请求体的各种数据。具有广泛性。
@PostMapping("/param/json2")
public String p6(Reader reader) {
    StringBuffer content = new StringBuffer("");
    try (BufferedReader bin = new BufferedReader(reader)) {
        var line = "";
        while ((line = bin.readLine()) != null) {
            content.append(line);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return "p7,reader=" + content.toString();
}
IDEA Http Client 测试:
    
POST http://localhost:8080/param/json2

hello world !!!
5.2.1.4.3数组参数接收多个值
  • 数组作为形参,接收多个参数值。
@GetMapping("/param/vals")
public String getMultiVal(Integer [] id){
	List<Integer> idList = Arrays.stream(id).toList();
	return idList.toString();
}

测试请求:
GET http://localhost:8080/param/vals?id=11&id=12&id=13
GET http://localhost:8080/param/vals?id=11,12,13,14,15
都是成功的方式
5.2.1.5验证参数
  • 服务器端程序,Controller在方法接受了参数,这些参数都是由用户提供的,使用之前必须校验参数是我们需要的吗,值是否在允许的范围内,是否符合业务的要求。比如年龄不能是负数,姓名不能是空字符串,email必须有@符号,phone国内的11位才可以。
  • 验证参数方案
    • 编写代码,手工验证,主要是if语句、switch等等。
    • Java Bean Validation:JSR-303是JAVA EE 6中的一项子规范,叫做Bean Validation,是一个运行时的数据验证规范,为JavaBean验证定义了相应的元数据模型和API。
5.2.1.5.1 Java Bean Validation
  • Spring Boot使用Java Bean Validation验证域模型属性值是否符合预期,如果验证失败,立即返回错误信息。Java Bean Validation将验证规则从controller、service集中到Bean对象,一个地方控制所有的验证。
  • Bean的属性上,加入JSR-303的注解,实现验证规则的定义。
5.2.1.5.2JSR-303注解

  • Hibernate提供的部分注解

5.2.1.5.3快速上手
  • 验证Blog中的文章信息,用户提交文章给Controller,控制器使用Java Object接收参数,给Bean添加约束注解,验证文章数据。

1.添加Bean Validator Starter

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.创建文章数据类,添加约束注解

@Data
public class ArticleVo {
    private Integer id;
    @NotNull(message = "必须有作者")
    private Integer userId;

    @NotBlank(message = "文章必须有标题")
    //@Size 认为 null是有效值。
    @Size(min = 3,max = 30,message = "标题在3个字符以上")
    private String title;

    @NotBlank(message = "必须有副标题")
    @Size(min = 5,max = 60,message = "副标题在5个字以上")
    private String summary;

    @DecimalMin(value = "0",message = "阅读数量不能小于0")
    private Integer readCount;

    @Email(message = "邮箱不符合规则")
    private String email;
}

3.Controller使用Bean

@RestController
public class ArticleController {
    //发布新文章

    /**
     * @Validated:Spring中的注解,支持JSR 303规范,还能对group验证。可以在类、方法、参数上使用
     * BindingResult:包含Bean的验证结果
     */
    @PostMapping("/article/add")
    public Map<String, Object> addArticle(@Validated @RequestBody ArticleVo article, BindingResult br) {
        Map<String, Object> map = new HashMap<>();
        if (br.hasErrors()) {
            List<FieldError> fieldErrors = br.getFieldErrors();
            for (int i = 0, len = fieldErrors.size(); i < len; i++) {
                FieldError field = fieldErrors.get(i);
                map.put(i + "-" + field.getField(), field.getDefaultMessage());
            }
        }
        return map;
    }
}
5.2.1.5.4分组校验
  • 上面的ArticleVo用来新增文章,新的文章主键id是系统生成的。现在要修改文章,比如修改某个文章的title,summary, readCount,email 等。此时id必须有值,修改这个id的文章。新增和修改操作对id有不同的约束要求,通过group来区分是否验证。

1.添加group标志

@Data
public class ArticleVo {
    //组就是接口名
    public static interface AddArticleGroup {};
    public static interface EditArticleGroup {};

    @NotNull(message = "文章id必须有值", groups = {EditArticleGroup.class})
    @Min(value = 1, message = "id大于0", groups = {EditArticleGroup.class})
    private Integer id;
    @NotNull(message = "必须有作者", groups = {AddArticleGroup.class, EditArticleGroup.class})
    private Integer userId;

    @NotBlank(message = "文章必须有标题", groups = {AddArticleGroup.class, EditArticleGroup.class})
    //@Size 认为 null是有效值。
    @Size(min = 3, max = 30, message = "标题在3个字符以上", groups = {AddArticleGroup.class, EditArticleGroup.class})
    private String title;

    @NotBlank(message = "必须有副标题", groups = {AddArticleGroup.class, EditArticleGroup.class})
    @Size(min = 5, max = 60, message = "副标题在5个字以上", groups = {AddArticleGroup.class, EditArticleGroup.class})
    private String summary;

    @DecimalMin(value = "0", message = "阅读数量不能小于0", groups = {AddArticleGroup.class, EditArticleGroup.class})
    private Integer readCount;

    @Email(message = "邮箱不符合规则", groups = {AddArticleGroup.class, EditArticleGroup.class})
    private String email;
}

2.修改Controller,不同方法增加group标志

@RestController
public class ArticleController {
    @PostMapping("/article/add")
    public Map<String, Object> addArticle(@Validated(ArticleVo.AddArticleGroup.class) @RequestBody ArticleVo article, BindingResult br) {
        Map<String, Object> map = new HashMap<>();
        if (br.hasErrors()) {
            List<FieldError> fieldErrors = br.getFieldErrors();
            for (int i = 0, len = fieldErrors.size(); i < len; i++) {
                FieldError field = fieldErrors.get(i);
                map.put(i + "-" + field.getField(), field.getDefaultMessage());
            }
        }
        return map;
    }
    @PostMapping("/article/edit")
    public Map<String, Object> editArticle(@Validated(ArticleVo.EditArticleGroup.class) @RequestBody ArticleVo article, BindingResult br) {
        Map<String, Object> map = new HashMap<>();
        if (br.hasErrors()) {
            List<FieldError> fieldErrors = br.getFieldErrors();
            for (int i = 0, len = fieldErrors.size(); i < len; i++) {
                FieldError field = fieldErrors.get(i);
                map.put(i + "-" + field.getField(), field.getDefaultMessage());
            }
        }
        return map;
    }
}

3.测试代码

POST http://localhost:8080/article/edit
Content-Type: application/json

{
  "id": 10,
  "userId": 10,
  "title": "云原生",
  "summary": "云原生SpringClod,Linux",
  "readCount": 10,
  "email": "abc@163.com"
}

5.2.2模型Model

  • 在许多实际项目需求中,后台要从控制器直接返回前端所需的数据,这时Model大家族就派上用场了。
  • Model指的是Spring MVC中的”M“,用来传输数据。从控制器直接返回数据给前端,配置jsp,模板技术都能够展示M中存储的数据。
  • Model简单理解就是给前端浏览器的数据,放在Model中,ModelAndView里的任意值,还有json格式的字符串都是Model。
@RequestMapping("/exam/quick")
public String quick(Model model) {
    model.addAttribute("title", "Web开发");
    model.addAttribute("time", LocalDateTime.now());
    return "quick";
}

5.2.3视图View

  • SpringMVC中的View(视图)用来展示数据的。无论使用thymleaf、jsp还是其他技术,classpath有jar就能使用视图了。程序员主要就是配置更改,Spring Boot3不推荐FreeMarker、jsp这些了。页面的视图技术Thymeleaf、Groovy Templates。
  • org.springframework.web.servlet.View 视图的接口,实现此接口的都是视图类,视图类作为Bean被Spring管理。
5.2.3.1页面视图
  • Thymeleaf作为代替jsp的视图技术,可以编写页面,排列数据。

1.创建Controller,控制器方法返回ModelAndView

@GetMapping("/hello")
public ModelAndView hello() {
    //ModelAndView表示数据和视图
    ModelAndView mv = new ModelAndView();
    mv.addObject("name", "李四");
    mv.addObject("age", 20);
    mv.setViewName("hello");
    return mv;
}

2.创建视图:在resources/templates创建hello.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div th:text="${name}"></div> <br/>
  <div th:text="${age}"></div> <br/>
</body>
</html>
  • application.properties默认thymeleaf的设置
#前缀 视图文件存放位置
spring.thymeleaf.prefix=classpath:/templates/
#后缀 视图文件扩展名
spring.thymeleaf.suffix=.html

3.测试,浏览器访问

5.2.3.2JSON视图
  • 在一个类中定义其他多个引用类型或集合类型,构成复杂json

1.编写实体类

@Data
public class User {
    private String username;
    private Integer age;
    private RoleVO roleVO;
}

@Data
public class RoleVO {
  private Integer id;
  private String roleName;
  private String memo;
}

2.增加控制器方法

@GetMapping("/json")
@ResponseBody
public User returnJson() {
    User user = new User();
    user.setUsername("张三");
    user.setAge(20);

    RoleVO roleVO = new RoleVO();
    roleVO.setRoleName("管理员");
    roleVO.setMemo("具有较高的权限");
    roleVO.setId(10);
    user.setRoleVO(roleVO);
    return user;
}

3.浏览器访问

5.2.3.3ResponseEntity
  • ResponseEntity包含HttpStatus Code和应答数据的结合体。因为有Http Code能表达标准的语义,200成功,404没有发现等。

1.ResponseEntity做控制器方法返回值

@GetMapping("/json3")
@ResponseBody
public ResponseEntity<User> returnEntity() {
    User user = new User();
    user.setUsername("张三");
    user.setAge(20);

    RoleVO roleVO = new RoleVO();
    roleVO.setRoleName("管理员");
    roleVO.setMemo("具有较高的权限");
    roleVO.setId(10);
    user.setRoleVO(roleVO);

    ResponseEntity<User> response = new ResponseEntity<>(user, HttpStatus.OK);
    return response;
}

2.测试

5.2.3.4Map作为返回值
  • Map作为返回值是数据,能够自动转为json

1.创建Map返回值的方法

@GetMapping("/map")
@ResponseBody
public Map<String, Object> returnMap() {
    Map<String, Object> map = new HashMap<>();
    map.put("name", "lisi");
    map.put("age", 20);
    return map;
}

2.测试

5.3SpringMVC请求流程

  • SpringMVC框架是基于Servlet技术的。以请求为驱动,围绕Servlet设计的。SpringMVC处理用户请求与访问一个Servlet是类似的,请求发送给Servlet,执行doService方法,最后响应结果给浏览器完成一次请求处理。

  • DispatcherServlet是核心对象,称为中央调度器。负责接收所有对Controller的请求,调用开发者的Controller处理业务逻辑,将Controller方法的返回值经过视图处理响应给浏览器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LiBI69Cc-1692712231667)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230818161315789.png)]

  • 红色的DispatcherServlet是框架创建的核心对象。
  • 蓝色的部分框架已经提供多个对象,开发任意可自定义,替换默认的对象。
  • 绿色的部分是开发人员自己创建的对象,控制器Conroller和视图对象。

5.4SpringMVC自动配置

  • SpringBoot检测classpath上存在的类,从而判断当前应用使用的是哪个Servlet Web服务器,从而决定定义相应的工厂组件,也就是Web服务器。
  • 配置类:ServerProperties.class,配置web server服务器
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
}
  • application文件配置服务器,现在使用tomcat服务器
#配置服务器
#服务器端口号
server.port=8081
#上下文访问路径
server.servlet.context-path=/api
#request、response字符编码
server.servlet.encoding.charset=UTF-8
#强制request、response设置charset字符编码
server.servlet.encoding.force=true

#配置tomcat服务器的日志
#日志路径
server.tomcat.accesslog.directory=D:/log
#启用访问日志
server.tomcat.accesslog.enabled=true
#日志文件名前缀
server.tomcat.accesslog.prefix=access_log
#日志文件日期时间
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd
#日志文件名称后缀
server.tomcat.accesslog.suffix=.log
#post 请求内容最大值,默认2M
server.tomcat.max-http-form-post-size=2000000
#服务器最大连接数
server.tomcat.max-connections=8192
  • SpringMVC配置
#配置DispatcherServlet
spring.mvc.servlet.path=/course
#Servlet的加载顺序,越小越先加载
spring.mvc.servlet.load-on-startup=0
#时间格式,可以接受请求参数使用
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss

 	//测试日期参数
    @GetMapping("/param/date")
    @ResponseBody
    public String paramDate(LocalDateTime date) {
        return "日期:" + date;
    }
http://localhost:8081/api/course/param/date?date=2203-02-02 12:10:10

5.5Servlets、Filters、Listeners

  • web应用还会用到Servlet、Filter或Listener。这些对象能够作为Spring Bean注册到嵌入式的Tomcat中。ServletRegistrationBean、FilterRegistrationBean 和ServletListenerRegistrationBean 控制 Servlet,Filter,Listener。@Order或Ordered接口控制对象的先后顺序。
  • Servlet现在完全支持注解的使用方式,@WebServlet。

5.5.1Servlets

5.5.1.1@WebServlet使用Servlet

1.创建Servlet

@WebServlet(urlPatterns = "/helloServlet",name = "HelloServlet")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("这是一个SpringBoot中的Servlet");
        out.flush();
        out.close();
    }
}

2.扫描Servlet注解

//扫描@WebServlet注解
@ServletComponentScan(basePackages = "com.hhb.web")
@SpringBootApplication
public class Springboot13ServletFilterApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot13ServletFilterApplication.class, args);
    }

}

3.测试

5.5.1.2ServletRegistrationBean

1.创建Servlet,不需要注解

public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("这是一个登录的Servlet");
        out.flush();
        out.close();
    }
}

2.配置Servlet

@Configuration
public class WebAppConfig {
    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        //创建ServletRegistrationBean登录一个或多个Servlet
        ServletRegistrationBean bean = new ServletRegistrationBean();
        bean.setServlet(new LoginServlet());
        bean.addUrlMappings("/user/login");
        bean.setLoadOnStartup(1);
        return bean;
    }
}

3.测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tMowdVMT-1692712231668)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230819101553421.png)]

5.5.2创建Filter

  • Filter对象使用频率比较高,比如记录日志、权限验证、敏感字符过滤等等。Web框架中包含内置的Filter,SpringMVC中也包含较多的内置Filter。
5.5.2.1@WebFilter注解
  • @WebFilter创建Filter对象,使用方式同@WebServlet

1.创建过滤器

@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String uri = ((HttpServletRequest) request).getRequestURI().toString();
        System.out.println("***LogFilter过滤器执行了,uri:" + uri);
        chain.doFilter(request, response);
    }
}

2.扫描包

@ServletComponentScan(basePackages = "com.hhb.web")
@SpringBootApplication
public class Springboot13ServletFilterApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot13ServletFilterApplication.class, args);
    }
}

3.浏览器访问测试

5.5.2.2FilterRegistrationBean
  • FilterRegistrationBean 与 ServletRegistrationBean 使用方式类似,无需注解。

注册Filter

@Bean
public FilterRegistrationBean filterRegistrationBean() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new LogFilter());
    bean.addUrlPatterns("/*");
    return bean;
}
5.5.2.3Filter排序
  • 多个Filter对象如果要排序,有两种途径:
    • 过滤器类名称,按字典顺序排列,AuthFilter->LogFilter
    • FilterRegistrationBean登记Filter,设置order顺序,数值越小越先执行。

1.创建两个Filter,使用之前的AuthFilter、LogFilter

public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String uri = ((HttpServletRequest) request).getRequestURI().toString();
        System.out.println("LogFilter过滤器执行了,uri:" + uri);
        chain.doFilter(request, response);
    }
}

public class AuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String uri = ((HttpServletRequest) request).getRequestURI().toString();
        System.out.println("AuthFilter过滤器执行了,uri:" + uri);
        chain.doFilter(request, response);
    }
}

2.创建配置类,登记Filter

@Bean
public FilterRegistrationBean logFilter() {
    FilterRegistrationBean log = new FilterRegistrationBean();
    log.setFilter(new LogFilter());
    log.setOrder(1);
    log.addUrlPatterns("/*");
    return log;
}

@Bean
public FilterRegistrationBean authFilter() {
    FilterRegistrationBean auth = new FilterRegistrationBean();
    auth.setFilter(new AuthFilter());
    auth.setOrder(2);
    auth.addUrlPatterns("/*");
    return auth;
}

3.测试Filter

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ebZErCeB-1692712231668)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230819110825309.png)]

5.5.2.4使用框架中的Filter
  • SpringBoot中有许多已经定义好的Filter,这些Filter实现类一些功能,如果我们需要使用他们,可以像自己的Filter一样,通过FilterRegistrationBean注册Filter对象。
  • 现在我们想记录每个请求的日志。CommonsRequestLoggingFilter 就能完成简单的请求记录。

1.登记CommonsRequestLoggingFilter

@Bean
public FilterRegistrationBean addOtherFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    //创建Filter对象
    CommonsRequestLoggingFilter commonLog = new CommonsRequestLoggingFilter();
    //包含请求uri
    commonLog.setIncludeQueryString(true);
    //登记Filter
    filterRegistrationBean.setFilter(commonLog);
    //拦截所有地址
    filterRegistrationBean.addUrlPatterns("/*");
    return filterRegistrationBean;
}

2.设置日志级别为debug,修改application.properties

logging.level.web=debug

3.测试访问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kgFe1l4-1692712231669)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230819112417433.png)]

5.5.3Listener

创建监听器

@WebListener("Listener 的描述说明")
public class MySessionListener implements HttpSessionListener {
	@Override
	public void sessionCreated(HttpSessionEvent se) {
	HttpSessionListener.super.sessionCreated(se);
	}
}

5.6WebMvcConfigurer

  • WebMvcConfigurer作为配置类是采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,就是SpringMVC XML配置文件的JavaConfig(编码)实现方式。
  • WebMvcConfigurer是一个接口,需要自定义某个对象,实现接口并覆盖某个方法。主要方法功能介绍一下:
public interface WebMvcConfigurer {
	//帮助配置 HandlerMapping
	default void configurePathMatch(PathMatchConfigurer configurer) {}
	//处理内容协商
	default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
	//异步请求
	default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
	//配置默认 servlet
	default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
	//配置内容转换器
	default void addFormatters(FormatterRegistry registry) {}
	//配置拦截器
	default void addInterceptors(InterceptorRegistry registry) {}
	//处理静态资源
	default void addResourceHandlers(ResourceHandlerRegistry registry) {}
	//配置全局跨域
	default void addCorsMappings(CorsRegistry registry) {}
	//配置视图页面跳转
	default void addViewControllers(ViewControllerRegistry registry) {}
	//配置视图解析器
	default void configureViewResolvers(ViewResolverRegistry registry) {}
	//自定义参数解析器,处理请求参数
	default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
	//自定义控制器方法返回值处理器
	default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
	//配置 HttpMessageConverters
	default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
	//配置 HttpMessageConverters
	default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
	//配置异常处理器
	default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
	//扩展异常处理器
	default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
	//JSR303 的自定义验证器
	default Validator getValidator() {
	return null;
	}
	//消息处理对象
	default MessageCodesResolver getMessageCodesResolver() {
	return null;
	}
}

5.6.1页面跳转控制器

  • SpringBoot中使用页面视图,比如Thymeleaf。要跳转显示某个页面,必须通过Controller对象。也就是我们需要创建一个Controller,转发到一个视图才行。如果我们现在需要显示页面,可以无需这个Controller。addViewControllers()完成从请求到视图跳转。

  • 项目代码结构:

1.创建视图

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>欢迎!!!</h3>
</body>
</html>

2.创建SpringMVC配置类

//SpringMVC配置:使用JavaConfig的方式配置SpringMVC,代替原来的xml配置文件
@Configuration
public class MvcSettings implements WebMvcConfigurer {
    //页面跳转控制器,从请求直达视图页面(无需controller)
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //配置页面的控制:addViewController("请求uri").setViewName("目标视图")
        registry.addViewController("/welcome").setViewName("index");
    }
}

3.测试功能

5.6.2数据格式化

  • Formatter是数据转换接口,将一种数据类型转换为另一种数据类型。与Formatter功能类似的还有Converter<S,T>。本章研究Formatter接口。

  • Formatter只能将String类型转换为其他数据类型,这点在Web应用适用更广,因为Web请求的所有参数都是String,我们需要把String转为Integer、Long、Date等等。

  • Spring中内置了一些Formatter:

    • DateFormatter:String和Date之间的解析与格式化
    • InetAddressFormatter:String和InetAddress之间的解析与格式化
    • PercentStyleFormatter:String与Number之间的解析与格式化,带货币符号
    • NumberFormat:String和Number之间的解析与格式化
  • 接口原型:

    public interface Formatter<T> extends Printer<T>, Parser<T> {}
    
    • Formatter是一个组合接口,没有自己的方法。内容来自Printer和 Parser
    • Printer:将T类型转换为String,格式化输出
    • Parser:将String类型转为期望的T对象
  • 需求:将“1111;2222;333,NF;4;561”接收,代码中用DeviceInfo存储参数值

1.创建DeviceInfo数据类

@Data
public class DeviceInfo {
    private String item1;
    private String item2;
    private String item3;
    private String item4;
    private String item5;
}

2.自定义Formatter

//将请求参数String转为DeviceInfo
public class DeviceFormatter implements Formatter<DeviceInfo> {
    //text表示请求参数的值
    @Override
    public DeviceInfo parse(String text, Locale locale) throws ParseException {
        DeviceInfo info = null;
        if (StringUtils.hasLength(text)) {
            String[] items = text.split(";");
            info  = new DeviceInfo();
            info.setItem1(items[0]);
            info.setItem2(items[1]);
            info.setItem3(items[2]);
            info.setItem4(items[3]);
            info.setItem5(items[4]);
        }
        return info;
    }

    @Override
    public String print(DeviceInfo object, Locale locale) {
        StringJoiner joiner = new StringJoiner("#");
        joiner.add(object.getItem1()).add(object.getItem2())
                .add(object.getItem3()).add(object.getItem4())
                .add(object.getItem5());
        return joiner.toString();
    }
}

3.登记自定义的DeviceFormatter

//转换器
@Override
public void addFormatters(FormatterRegistry registry) {
    registry.addFormatter(new DeviceFormatter());
}

4.新建Controller接收请求设备数据

@RestController
public class DeviceController {

    @PostMapping("/device/add")
    public String addDeviceInfo(@RequestParam("device") DeviceInfo info){
        return "接收的设备信息:"+info.toString();
    }
}

5.单元测试

POST http://localhost:8080/device/add
Content-Type: application/x-www-form-urlencoded

device=1111;2222;333,NF;4;561

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tVwVXCTR-1692712231669)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230819145817001.png)]

5.6.3拦截器

  • HandlerInterceptor接口和它的实现类称为拦截器,是SpringMVC的一种对象。拦截器是SpringMVC框架的对象,与Servlet无关。拦截器能够预先处理发给Controller的请求,可以决定请求是否被Controller处理。用户请求是先由DispatcherServlet接收后,在Controller之前执行的拦截器对象。
  • 一个项目中有众多的拦截器:框架中预定义的拦截器,自定义拦截器。下面我们说说自定义拦截器的应用。
  • 拦截器定义步骤:
    • 声明类实现HandlerInterceptor接口,重写三个方法(需要哪个重写哪个)
    • 登记拦截器
5.6.3.1一个拦截器
  • 需求:zhangsan操作员用户,只能查看文章,不能修改删除。

1.创建文章的Controller

@RestController
public class ArticleController {
    @PostMapping("/article/add")
    public String addArticle() {
        return "发布新的文章";
    }

    @PostMapping("/article/edit")
    public String editArticle() {
        return "修改文章";
    }

    @DeleteMapping("/article/remove")
    public String removeArticle() {
        return "删除文章";
    }

    @GetMapping("/article/query")
    public String queryArticle() {
        return "查询文章";
    }
}

2.创建有关的权限拦截器

public class AuthInterceptor implements HandlerInterceptor {
    public static final String COMMON_USER = "zhangsan";

    //判断登录的用户,能否执行相应的动作
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("AuthInterceptor权限拦截器");
        //登录的用户
        String loginUser = request.getParameter("loginUser");
        //获取请求的uri地址
        String requestURI = request.getRequestURI();

        //判断用户和操作
        if (COMMON_USER.equals(loginUser) && (
                requestURI.startsWith("/article/add") ||
                requestURI.startsWith("/article/edit") ||
                requestURI.startsWith("/article/remove")
        )) {
            return false;
        }
        return true;
    }
}

3.登记拦截器

//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    //权限拦截器
    AuthInterceptor authInterceptor = new AuthInterceptor();
    registry.addInterceptor(authInterceptor)
            .addPathPatterns("/article/**")//拦截以article开头的所有请求
            .excludePathPatterns("/article/query");//不拦截的地址
}

4.测试拦截器

###
POST http://localhost:8080/article/add
Content-Type: application/x-www-form-urlencoded

loginUser=zhangsan&title=Vue


###
GET http://localhost:8080/article/query
Content-Type: application/x-www-form-urlencoded

loginUser=zhangsan&title=Vue
5.6.3.2多个拦截器
  • 增加一个验证登录用户的拦截器,只有zhangsan,lisi,admin能够登录系统。其他用户不可以。两个拦截器登录的拦截器先执行,权限拦截器后执行,order()方法设置顺序,整数值越小,先执行。

1.创建登录拦截器

public class LoginInterceptor implements HandlerInterceptor {

    private List<String> permitUser = new ArrayList<>();

    public LoginInterceptor() {
        this.permitUser = Arrays.asList("zhangsan", "lisi", "admin");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("==========LoginInterceptor执行了==========");
        //获取登录的用户名称
        String loginUser = request.getParameter("loginUser");

        //只有三个用户能够访问系统
        if (StringUtils.hasText(loginUser) && permitUser.contains(loginUser)) {
            return true;
        }
        return false;
    }
}

2.登记拦截器,设置顺序order

//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    //权限拦截器
    AuthInterceptor authInterceptor = new AuthInterceptor();
    registry.addInterceptor(authInterceptor)
            .order(2)
            .addPathPatterns("/article/**")//拦截以article开头的所有请求
            .excludePathPatterns("/article/query");//不拦截的地址

    //登录拦截器
    LoginInterceptor loginInterceptor = new LoginInterceptor();
    registry.addInterceptor(loginInterceptor)
            .order(1)//顺序,整数值,越小越先执行
            .addPathPatterns("/**")//拦截所有对controller的请求
            .excludePathPatterns("/article/query");//排除
}

5.7文件上传

  • SpringBoot上传文件现在变得非常简单,提供了封装好的处理上传文件的接口MultipartResolver,用于解析上传文件的请求。
  • MultipartFile表示上传的文件,提供了方便的方法保存文件到磁盘。

MultipartFile API

方法作用
getName()参数名称
getOriginalFilename()上传文件原始名称
isEmpty()上传文件是否为空
getSize()上传的文件字节大小
getInputStream()文件的InputStream,可用于读取部件的内容
transferTo(File dest)保存上传文件到目标dest

5.7.1MultipartResolver

1.服务器创建目录存放上传后的文件

  • 例如在 D:/upload

2.创建index.html作为上传后的显示页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>文件上传成功!!!</h3>
</body>
</html>

3.创建上传文件页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div style="margin-left: 200px">
    <h3>文件上传</h3>
    <form action="uploadFile" enctype="multipart/form-data" method="post">
        选择文件:<input type="file" name="upfile"><br>
        <input type="submit" value="上传">
    </form>
</div>
</body>
</html>
  • 要求:
    • enctype=“multipart/form-data”
    • method=“post”
    • 表示一个上传文件,upfile自定义上传文件参数名称

4.创建Controller

package com.hhb.upload.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Controller
public class UploadFileController {
    //上传文件
    @PostMapping("/uploadFile")
    public String uploadFile(@RequestParam("upfile") MultipartFile multipartFile) throws IOException {
        System.out.println("开始处理上传文件");
        Map<String, Object> info = new HashMap<>();
        if (!multipartFile.isEmpty()) {
            info.put("上传文件的参数名称", multipartFile.getName());
            info.put("内容类型", multipartFile.getContentType());
            var ext = "unknown";
            //原始文件名称,例如a.gif
            var filename = multipartFile.getOriginalFilename();
            //indexOf(".") 判断"."第一次出现的位置
            if (filename.indexOf(".") > 0) {
                //截取"."之后的字符
                ext = filename.substring(filename.indexOf(".") + 1);
            }
            //生成服务器使用的文件名称
            var newFileName = UUID.randomUUID().toString() + "." + ext;
            //存储服务器的文件
            var path = "D://upload//" + newFileName;
            //把文件保存到path目录
            multipartFile.transferTo(new File(path));
            info.put("文件名称", newFileName);
        }
        System.out.println("info = " + info);
        //重定向到index页面,防止刷新,避免重复上传
        return "redirect:/index.html";
    }
}

5.测试

  • 浏览器访问 http://localhost:8080/upload.html

  • 文件上传,查看 D:/upload 目录上传的文件

  • SpringBoot默认单个文件最大支持1MB,一次请求最大10MB,改变默认值,需要application修改配置项。

#一次请求最大支持5MB
spring.servlet.multipart.max-request-size=5MB
#单个文件最大支持100KB
spring.servlet.multipart.max-file-size=100KB
#超过指定大小,直接写文件到磁盘,不再内存处理
spring.servlet.multipart.file-size-threshold=0KB
  • 配置错误页面**(规定格式)**

  • resources/static/error/5xx.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>文件上传错误</h3>
</body>
</html>

5.7.2Servlet规范

  • Servlet3.0规范中,定义了jakarta.servlet.http.Part接口处理multipart/form-data POST请求中接收到表单数据。
  • 有了Part对象,其write()方法将上传文件保存到服务器本地磁盘目录。
  • 在HttpServletRequest接口中引入的新方法:
    • getParts():返回Part对象的集合。
    • getPart(字符串名称):检索具有给定名称的单个Part对象。

原生的Servlet规范的文件上传

@Controller
public class UploadAction {
    @PostMapping("/files")
    public String upload(HttpServletRequest request) throws ServletException, IOException {
        for (Part part : request.getParts()) {
            String filename = extractFileName(part);
            //将文件写入服务器的目录
            part.write(filename);
        }
        return "redirect:/index.html";
    }

    private String extractFileName(Part part) {
        String contentDisp = part.getHeader("content-disposition");
        System.out.println("contentDisp=" + contentDisp);
        String[] items = contentDisp.split(";");
        for (String s : items) {
            if (s.trim().startsWith("filename")) {
                return s.substring(s.indexOf("=") + 2, s.length() - 1);
            }
        }
        return "";
    }
}

application文件,配置服务器存储文件位置

spring.servlet.multipart.location=D://upload

5.7.3多文件上传

  • 多文件上传,在接收文件参数部分有所改变MultiPartFile [] files。循环遍历数组解析每个上传的文件。

前端请求页面

<!DOCTYPE html>
<html lang="en">
<body>
<div style="margin-left: 200px">
    <h3>文件上传</h3>
    <form action="file" enctype="multipart/form-data" method="post">
        选择文件-1:<input type="file" name="upfile"> <br/> <br/>
        选择文件-2:<input type="file" name="upfile"> <br/> <br/>
        选择文件-3:<input type="file" name="upfile"> <br/> <br/>
        <input type="submit" value="上传">
    </form>
</div>
</body>
</html>

5.8全局异常处理

  • 在Controller处理请求过程中发生了异常,DispatcherServlet将异常处理委托给异常处理器(处理异常的类),实现HandlerExceptionResolver接口的都是异常处理类。
  • 项目中的异常一般都是集中处理,定义全局异常处理器。再结合框架提供的注解,例如:@ExceptionHandler、@ControllerAdvice、@RestControllerAdvice一起完成异常的处理。
  • @ControllerAdvice与@RestControllerAdvice区别在于:@RestControllerAdvice加入了@ResponseBody。

5.8.1全局异常处理器

1.创建输入数字的页面

  • 在static目录下创建input.html,static目录下的资源浏览器可以直接访问。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="divide" method="post">&nbsp;&nbsp;数:<input type="text" name="n1"><br>
    被除数:<input type="text" name="n2"><br>
    <input type="submit" value="计算">
</form>
</body>
</html>

2.创建控制器,计算两个整数相除

@RestController
public class NumberController {
    @PostMapping("/divide")
    public String some(Integer n1, Integer n2) {
        int res = n1 / n2;
        return "n1/n2=" + res;
    }
}

3.浏览器访问input.html,计算两个数字相除

  • 显示默认错误页面

4.创建自定义异常处理器

/**
 * 1.在类的上面加@ControllerAdvice、@RestControllerAdvice,灵活组合
 * 2.在类中自定义方法,处理各种异常。方法定义同Controller类中的方法的定义
 */
//控制器增强,给Controller增加异常处理功能,类似AOP的思想
@ControllerAdvice
public class GlobalExceptionHandler {
    //定义方法处理数学异常

    /**
     * @ExceptionHandler:指定处理异常的方法 位置:在方法的上面
     * 属性:是异常类的class数组,如果你的系统抛出的异常类型与@ExceptionHandler声明的相同,由当前方法处理异常
     */
    /*@ExceptionHandler({ArithmeticException.class})
    public String handlerArithmeticException(ArithmeticException e, Model model) {
        String error = e.getMessage();
        model.addAttribute("error", error);
        return "exp";//视图
    }*/
    //不带视图,直接返回数据
    @ExceptionHandler({ArithmeticException.class})
    @ResponseBody
    public Map<String, String> handlerReturnDataException(ArithmeticException e) {
        Map<String, String> error = new HashMap<>();
        error.put("msg", e.getMessage());
        error.put("tips", "被除数不能为0");
        return error;
    }
}

5.创建错误提示页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>显示异常的信息:
    <div th:text="${error}"></div>
</h3>
</body>
</html>
  • 再测试显示,提示页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhmSxwbg-1692712231670)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230821142843877.png)]

5.8.2BeanValidator异常处理

  • 使用JSR-303验证参数时,我们是在Controller方法声明BindingResult对象获取校验结果。Controller的方法很多,每个方法都加入BindingResult处理检验参数比较繁琐。校验参数失败抛出异常给框架,异常处理器能够捕获到MethodArgumentNotValidException,它是BindException的子类。
  • BindException异常实现了BindingResult接口,异常类能够得到BindingResult对象,进一步获取JSR303校验的异常信息。

1.添加JSR-303依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.创建Bean对象,属性加入JSR-303注解

@Data
public class OrderVO {
    @NotBlank(message = "订单名称不能为空")
    private String name;

    @NotNull(message = "商品必须有数量")
    @Range(min = 1, max = 99, message = "一个订单的商品数量在{min}--{max}")
    private Integer amount;

    @NotNull(message = "用户不能为空")
    @Min(value = 1, message = "从1开始")
    private Integer userId;
}

3.Controller接收请求

@RestController
public class OrderController {
    @PostMapping("/order/new")
    public String createOrder(@Validated @RequestBody OrderVO orderVO) {
        return "订单信息:" + orderVO.toString();
    }
}

4.创建异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {
    //处理JSR303 验证参数的异常
    //@ExceptionHandler({BindException.class})
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseBody
    public Map<String, Object> handlerJSR303Exception(MethodArgumentNotValidException e) {
        System.out.println("**********JSR303**********");
        Map<String, Object> map = new HashMap<>();
        BindingResult result = e.getBindingResult();
        if (result.hasErrors()) {
            List<FieldError> errors = result.getFieldErrors();
            for (int i = 0, len = errors.size(); i < len; i++) {
                FieldError field = errors.get(i);
                map.put(field.getField(), field.getDefaultMessage());
            }
        }
        return map;
    }
}

5.测试

POST http://localhost:8080/order/new
Content-Type: application/json

{
  "name": "",
  "amount": 0,
  "userId": 10
}

5.8.3ProblemDetail[SpringBoot3]

  • 一直以来SpringBoot默认的异常反馈内容比较单一,包含Http Status Code、时间、异常信息。但具体异常原因没有体现,这次SpringBoot3对错误信息增强了。
5.8.3.1 RFC 7807

”Problem Details“ 的 JSON 应答格式

{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/transactions/abc"
}
  • ”Problem Details“ 包含内容:

标准字段描述是否必须
type标识错误类型的URI。在浏览器中加载这个URI应该转向这个错误的文档。默认为 about:blank可认为是
title问题类型的简单描述
datail错误信息详细描述
instance标识该特定故障实例的URI,它可以作为发生的这个错误的ID
status错误使用的HTTP状态代码,它必须与实际状态匹配

5.8.3.2MediaType
  • RFC 7807增加了两种媒体类型:application/problem+jsonapplication/problem+xml。返回错误的HTTP响应应在其Content-Type响应标头中包含适当的内容类型,并且客户端可以检查该标头以确认格式。
5.8.3.3Spring支持Problem Detail

5.8.3.4自定义异常处理器ProblemDetail
  • 需求:我们示例查询某个isbn的图书。在application.yml中配置图书的初始数据,用户访问一个api地址,查询某个isbn的图书,查询不到抛出自定义异常BootNotFoundException,自定义异常处理器捕获异常。ProblemDeatil作为应答结果,支持RFC 7807。

1.新建图书的Record(普通的POJO类都是可以的)

public record BookRecord(String isbn,String name,String author) {
}

2.创建存储多本图书的容器类

@Setter
@Getter
@ConfigurationProperties(prefix = "product")
//将数据存入到BookContainer
public class BookContainer {
    private List<BookRecord> books;
}

3.application.yml配置图书基础数据

#指定初始的book数据,代替数据库
product:
  books:
    - isbn: B001
      name: java
      author: lisi
    - isbn: B002
      name: tomcat
      author: zhangsan
    - isbn: B003
      name: jvm
      author: wangwu

#访问路径
server:
  servlet:
    context-path: /api

4.新建自定义异常类

public class BookNotFoundException extends RuntimeException {
    public BookNotFoundException() {
        super();
    }

    public BookNotFoundException(String message) {
        super(message);
    }
}

5.新建控制器类

@RestController
public class BookController {
    //注入BookContainer
    @Autowired
    private BookContainer bookContainer;

    //根据isbn查询图书,如果没有查到,抛出异常
    @GetMapping("/book")
    public BookRecord getBook(String isbn) {
        Optional<BookRecord> bookRecord = bookContainer.getBooks().stream().filter(el ->
                el.isbn().equals(isbn)
        ).findFirst();
        if (bookRecord.isEmpty()) {
            throw new BookNotFoundException(" isbn :" + isbn + "->没有此图书");
        }
        return bookRecord.get();
    }
}

6.新建异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {

    //定义的ProblemDetail
    @ExceptionHandler({BookNotFoundException.class})
    public ProblemDetail handlerBookNotFoundException(BookNotFoundException e) {
        //ProblemDetail
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
        //type:异常类型,是一个uri,uri找到解决问题的途径
        problemDetail.setType(URI.create("/api/probs/not-found"));
        problemDetail.setTitle("图书异常");

        //增加自定义的字段
        problemDetail.setProperty("时间", Instant.now());
        problemDetail.setProperty("客服", "362619368@qq.com");
        return problemDetail;
    }
}

7.测试接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNtkaSWh-1692712231670)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230822202408704.png)]

5.8.3.5ErrorResponse
  • SpringBoot识别ErrorResponse类型作为异常的应答结果。可以直接使用ErrorResponse作为异常处理方法的返回值。

修改异常处理器GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {
	//返回ErrorResponse
    @ExceptionHandler({BookNotFoundException.class})
    public ErrorResponse handlerException(BookNotFoundException e) {
        ErrorResponseException error = new ErrorResponseException(HttpStatus.NOT_FOUND, e);
        return error;
    }
}

测试接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H12MsOqz-1692712231671)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230822203000908.png)]

5.8.3.6扩展ErrorResponseException
  • 自定义异常可以扩展ErrorResponseException,SpringMVC将处理异常以符合RFC7807的格式返回错误响应。

1.创建新的异常类继承ErrorResponseException

//自定义异常类,让SpringMVC的异常处理器使用
public class IsbnNotFoundException extends ErrorResponseException {
    public IsbnNotFoundException(HttpStatus httpStatus, String detail) {
        super(httpStatus, createProblemDetail(httpStatus, detail), null);
    }

    private static ProblemDetail createProblemDetail(HttpStatus status, String detail) {
        //封装RFC7807字段
        ProblemDetail problemDetail = ProblemDetail.forStatus(status);
        problemDetail.setType(URI.create("/api/probs/not-found"));
        problemDetail.setTitle("图书异常");
        problemDetail.setDetail(detail);
        //自定义字段
        problemDetail.setProperty("严重程度", "低");
        problemDetail.setProperty("客服邮箱", "36261968@qq.com");
        return problemDetail;
    }
}

2.抛出IsbnNotFoundException

@RestController
public class BookController {
    //注入BookContainer
    @Autowired
    private BookContainer bookContainer;

    //根据isbn查询图书,如果没有查到,抛出异常
    @GetMapping("/book")
    public BookRecord getBook(String isbn) {
        Optional<BookRecord> bookRecord = bookContainer.getBooks().stream().filter(el ->
                el.isbn().equals(isbn)
        ).findFirst();
        if (bookRecord.isEmpty()) {
            //throw new BookNotFoundException(" isbn :" + isbn + "->没有此图书");
            throw new IsbnNotFoundException(HttpStatus.NOT_FOUND, " isbn :" + isbn + "->没有此图书");
        }
        return bookRecord.get();
    }
}

3.启动RFC7807支持,修改application.yml,增加配置

spring:
  mvc:
    problemdetails:
      enabled: true

4.测试接口

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

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

相关文章

如何在Windows、Mac和Linux操作系统上安装Protocol Buffers(protobuf)编译器

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

TLSv1.2抓包解密分析过程之RSA_WITH_AES_128_CBC_SHA

RSA_WITH_AES_128_CBC_SHA是TLSv1.2中最简单的加密协议了&#xff0c;非常适合用于学习TLSv1.2的握手过程。 RSA_WITH_AES_128_CBC_SHA表示 使用RSA非对称加密进行密钥协商使用AES128 CBC模式加密Hash算法使用SHA-1 参考文档&#xff1a;rfc5246 rfc5077 rfc7627 1. t…

Kyligence Copilot 登陆海外,斩获 Product Hunt 日榜 TOP 2

8月14日&#xff0c;AI 数智助理 Kyligence Copilot 在全球知名科技产品平台 Product Hunt 上线&#xff0c;其以出色的产品创新实力&#xff0c;在激烈的竞争中脱颖而出&#xff0c;仅仅在 24 小时内收获了超过 400 个投票和近 200 条支持评论&#xff0c;荣登当日产品榜排名第…

Microsoft正在将Python引入Excel

Excel和Python这两个世界正在碰撞&#xff0c;这要归功于Microsoft的新集成&#xff0c;以促进数据分析和可视化 Microsoft正在将流行的编程语言Python引入Excel。该功能的公共预览版现已推出&#xff0c;允许Excel用户操作和分析来自Python的数据。 “您可以使用 Python 绘图…

远程端口转发 实践 如何将物理机某一端口的服务转发到vps上,使得外网能访问到

以本机1470端口&#xff08;我的sqli-labs&#xff09;与vps的9023端口为例。 SSH基本的连接命令是&#xff1a; ssh usernamehostname这里牵扯到了两台主机&#xff0c;一是执行命令、运行SSH客户端的主机&#xff0c;我们称为本地主机A【Host A】&#xff1b;二是接收连接请…

记录某一次演讲

大家好&#xff0c;我是繁依&#xff0c;是一名软件工程的学生。很高兴能站在这里&#xff0c;与大家分享一下自己日常数据分析学习的经验。首先&#xff0c;我先来介绍一下自己常用的学习资源和工具&#xff0c;学习平台及资源有哔哩哔哩、掘金小册、和鲸社区、飞桨社区等 常…

ECharts配合Node.js爬虫实现数据可视化

数据可视化简介 可视化技术是将数据和信息以图形化的方式展示出来&#xff0c;以便更好地理解和分析。可视化技术通常使用各种图表、图形、动画和交互式效果来呈现数据。可视化技术有以下几个基本概念&#xff1a; 数据&#xff1a;可视化技术的基础是数据。数据可以是数字、文…

使用Kind搭建本地k8s集群环境

目录 1.前提条件 2.安装Kind 3.使用Kind创建一个K8s集群 3.1.创建一个双节点集群&#xff08;一个Master节点&#xff0c;一个Worker节点&#xff09; 3.2.验证一下新创建的集群信息 3.3.删除刚刚新建的集群 4.安装集群客户端 4.1.安装kubectl 4.1.1.验证kubectl 4.2.安…

工法到底是什么?

关于工法许多人都是感到陌生的&#xff0c;第一次接触会想&#xff0c;工法是建筑工法还是农业工法呢&#xff1f;其实都不是的。百度百科给的解释是&#xff1a;工法一词来自日本&#xff0c;日本《国语大辞典》将工法释为工艺方法和工程方法。在中国&#xff0c;工法是指以工…

ElementUI Table 翻页缓存数据

Element UI Table 翻页保存之前的数据,网上找了一些,大部分都是用**:row-key** 和 reserve-selection,但是我觉得有bug,我明明翻页了…但是全选的的个框还是勾着的(可能是使用方法不对,要是有好使的…请cute我一下…感谢) 所以自己写了一个… 思路: 手动勾选的时候,将数据保存…

JDK 核心jar之 rt.jar

一、JDK目录展示 二、rt.jar 简介 2.1.JAR释义 在软件领域&#xff0c;JAR文件&#xff08;Java归档&#xff0c;英语&#xff1a;Java Archive&#xff09;是一种软件包文件格式&#xff0c;通常用于聚合大量的Java类文件、相关的元数据和资源&#xff08;文本、图片等&…

通过python在unity里调用C#接口

log: 背景 最近在做虚拟人底层驱动sdk测试&#xff0c;因为后端使用的是C#,我个人更倾向于python编程辅助测试工作&#xff0c;测试sdk需要通过开发提供的接口方法文档&#xff0c;通过传测试场景参数调用方法进行单元测试 技术&工具 项目语言 C# 项目工具 unity 测试…

Aspose.Tasks for .NET V23Crack

Aspose.Tasks for .NET V23Crack 改进了大型项目的内存占用。 添加了API&#xff0c;允许您在应用程序无法访问系统字体文件夹时指定用户的字体文件夹。 Aspose.Tasksfor.NET是处理MicrosoftProject文件的可靠的项目管理API。API支持在不依赖Microsoft Project的情况下读取、写…

CAM实现的流程--基于Pytorch实现

CAM实现的流程 CAM类激活映射CAM是什么CAM与CNN CAM类激活映射 CAM是什么 可视化CNN的工具&#xff0c; CAM解释网络特征变化&#xff0c;CAM使得弱监督学习发展成为可能&#xff0c;可以慢慢减少对人工标注的依赖&#xff0c;能降低网络训练的成本。通过可视化&#xff0c;就…

HTML 和 CSS 来实现毛玻璃效果(Glassmorphism)

毛玻璃效果简介 它的主要特征就是半透明的背景&#xff0c;以及阴影和边框。 同时还要为背景加上模糊效果&#xff0c;使得背景之后的元素根据自身内容产生漂亮的“变形”效果&#xff0c;示例&#xff1a; 代码实现 首先&#xff0c;创建一个 HTML 文件&#xff0c;写入如下…

cuda编程day001

一、环境&#xff1a; ①、linux cuda-11.3 opecv4.8.0 不知道头文件和库文件路径&#xff0c;用命令查找&#xff1a; # find /usr/local -name cuda.h 2>/dev/null # 查询cuda头文件路径 /usr/local/cuda-11.3/targets/x86_64-linux/include/cuda.h # find /usr/…

【AUTOSAR应用层详细介绍|应知应会】

AUTOSAR应用层详细介绍 文章目录 AUTOSAR应用层详细介绍前言一、软件组件1.1 软件组件的分类二、数据类型三、端口与端口接口3.1 端口3.2 端口接口四、软件组件的内部行为五、AUTOSAR虚拟功能总线 VFB前言 AUTOSAR 包含一个分层的架构,其中最顶层抽象程度最高的是应用层,应用…

LeetCode42.接雨水

这道题呢可以按列来累加&#xff0c;就是先算第1列的水的高度然后再加上第2列水的高度……一直加到最后就是能加的水的高度&#xff0c;我想到了这里然后就想第i列的水其实就是第i-1列和i1列中最小的高度减去第i列的高度&#xff0c;但是其实并不是&#xff0c;比如示例中的第5…

记录Taro巨坑,找不到sass类型定义文件

问题 taronutuisassts项目里tsconfig.json一直报红报错。 找不到“sass”的类型定义文件。 程序包含该文件是因为: 隐式类型库 “sass” 的入口点 其实正常人想的肯定是装上 types/sass试试。开始我试过了&#xff0c;没用。删了依赖重装也没用。后面在issue中找到答案了 解决…

错题整理——2022小米测开

1. 算法的五个基本特性是:输入、输出、有穷性、确定性和可行性。 2. 网络端口范围与最大tcp连接数和系统允许打开的最大文件数&#xff0c;用户允许打开的最大文件数&#xff0c;TCP网络连接可用的端口范围有关&#xff0c;取上述的最小值&#xff1b; 端口范围是指操作系统支…