当前位置: 首页 > article >正文

SpringBoot Web开发(SpringMVC)

SpringBoot Web开发(SpringMVC)

MVC 核心组件和调用流程


Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet DispatcherServlet 做整体请求处理调度!
.
除了DispatcherServletSpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。

SpringMVC 处理请求流程

在这里插入图片描述

SpringMVC 涉及的组件理解

  • DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]

  • HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]

  • HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handlerDispatcherServlet之间的适配器![经理]

  • Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]

  • ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

简单来讲

在这里插入图片描述

调用 Controller 层的方法,先通过 handlerMapping 里面缓存的 handler访问路径,和 handler(controller方法)识别到需要的请求参数, 通过 HandlerAdapter,提取对应的参数 k 对应的 v。然后才执行 handler,返回数据再经过 handlerAdapter 将 handler 返回的数据封装到 response 中,如果是静态资源返回一个字符。利用视图解析器规定的格式拼接访问静态资源

Web 场景原理

自动配置原理

  • 整合 web 场景依赖
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 引入了 autoconfigure 功能
  • @EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件
  • 加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置的所有组件
  • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中按需加载关于 web 自动配置类
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
    
====以下是响应式web场景和现在的没关系======
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
================以上没关系=================
    
    
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
  • 绑定了配置文件的一堆配置项
    • SpringMVC的所有配置 spring.mvc
    • Web场景通用配置 spring.web
    • 文件上传配置 spring.servlet.multipart
    • 服务器的配置 server: 比如:编码方式

自动配置效果

  • 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析

  • 默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问

  • 自动注册了 Converter,GenericConverter, Formatter组件,适配常见数据类型转换和格式化需求

  • 支持 HttpMessageConverters,可以方便返回 json 等数据类型

  • 注册 MessageCodesResolver,方便国际化及错误消息处理

  • 支持 静态 index.html

  • 自动使用 ConfigurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能

WebMvcConfigurer 接口!!!

  • External Libraries 中搜索 spring-boot-autoconfigure

  • 然后再进到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

  • 找到 WebMvcAutoConfiguration 。它是SpringMvC 自动配置类

  • 再找到 WebMvcAutoConfigurationAdapter

    • 它实现了 WebMvcConfigurer 接口
    • 并且有@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})配置前缀
    • 得出这个接口包含了所有 SpringMVC 组件的默认配置

.

通过 WebMvCAutoConfigurationAdapter 搜索以下方法可以找到响应组件的默认配置

在这里插入图片描述

最佳实践

WebMvcConfigurer 接口

当你需要对 Spring MVC 的一些常见功能进行配置和扩展时,会使用 WebMvcConfigurer。比如添加拦截器来实现权限验证、配置视图控制器来简化页面跳转、自定义消息转换器来处理特定格式的数据等。不会影响自动配置

WebMvcConfigurer 接口

当你需要对 Spring MVC 的底层请求处理逻辑进行深度定制时,会使用 WebMvcRegistrations。例如,你想要自定义 RequestMappingHandlerMapping 的请求映射规则,让它根据特定的条件来匹配请求;或者自定义 ExceptionHandlerExceptionResolver 的异常处理逻辑,实现更复杂的异常处理策略。不会影响自动配置

@EnableMVC 注解

当你在配置类上添加 @EnableWebMvc 注解时,它会全面接管 Spring MVC 的配置工作。这意味着 Spring Boot 默认提供的 MVC 自动配置会被完全覆盖,你需要手动配置所有与 Spring MVC 相关的组件,像视图解析器、消息转换器、拦截器、静态资源处理器等。

方式用法效果
全自动直接编写控制器逻辑全部使用自动配置默认效果
手自一体@Configuration + 配置WebMvcConfigurer+ 配置 WebMvcRegistrations不要标注 @EnableWebMvc保留自动配置效果。手动设置部分功能 ,定义MVC底层组件
全手动@Configuration + 配置WebMvcConfigurer标注 @EnableWebMvc禁用自动配置效果 全手动设置

SpringBoot 访问路径设置


路径注解 @RequestMapping

方法级别

直接在方法上加 @RequestMapping。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 @RequestMapping 注解进行更精细的映射。

@Controller                                             
public class UserController {

	//这里路径就是 /index 访问就会执行这个方法
	@RequestMapping("/index")
    public String index() {

        return null;
    }
}
类级别

在类上加 @RequestMapping,方法上还要再设置一次 @RequestMapping 设置了 @RequestMapping 的参数那就自动加上 类级别 的地址当前缀,如果没有参数就直接用 类地址 地址

@Controller
@RequestMapping("/user")
public class UserController {

	@RequestMapping //这里没有参数就直接用类级别的参数也就是 /user
    public String index() {

        return null;
    }

    @RequestMapping(value = "login") //这里有参数就是自动加上类级别的地址当前缀也就是 /user/login
    public String login() {

        return null;
    }

}
特定请求方式限制 —— 枚举方式

注意:违背请求方式,会出现405异常!!!

HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类:

public enum RequestMethod {
 GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
@Controller
public class UserController {

    /**
     * 精准设置访问地址 /user/login
     * method = RequestMethod.POST 可以指定单个或者多个请求方式!
     * 注意:违背请求方式会出现405异常!
     */
    @RequestMapping(value = {"/user/login"} , method = RequestMethod.POST)
    @ResponseBody
    public String login(){
        System.out.println("UserController.login");
        return "login success!!";
    }  
}
特定请求方式限制 —— 注解方式

注意:进阶注解只能添加到 handler 方法上,无法添加到类上!

还有 @RequestMapping 的 HTTP 方法特定快捷方式变体:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping
@RequestMapping(value="/login",method=RequestMethod.GET)
就等于
@GetMapping(value="/login")

精确路径匹配

SpringMVC@RequestMapping/ 可以省略
.
/ 就表示绝对路径 ,Tomcat 中表示 http://localhost:8080/,设置 Tomcat 上下文路径为 / 缺省就可以直接使用 / + 地址 访问对应的 Servlet

   @RequestMapping(value = "/login", method = RequestMethod.POST)     
   public String login() {return null;    
   }

模糊路径匹配

  • @RequestMapping 注解指定 URL 地址时,通过使用通配符,匹配多个类似的地址。
  • * 表示任意单层:/user/* 这种就是一层可以访问,/user/a/b 这就是两层访问不了
  • **表示任意层/user/** ,比如:/user/a/user/a/b 都可以
@Controller
public class ProductController {

    /**
     *  路径设置为 /product/*  
     *    /* 为单层任意字符串  /product/a  /product/aaa 可以访问此handler  
     *    /product/a/a 不可以
     * 
     *  路径设置为 /product/** 
     *   /** 为任意层任意字符串  /product/a  /product/aaa 可以访问此handler  
     *   /product/a/a 也可以访问
     */
    @RequestMapping("/product/*")
    @ResponseBody
    public String show(){
        System.out.println("ProductController.show");
        return "product show!";
    }
}

SpringBoot 接收参数

param 和 json 参数比较

在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比:

  • paramkey = value & key = value
  • json{ key : value, key : value }
    .
  1. 参数编码:
    param 类型的参数会被编码为 ASCII 码。例如,假设 name=john doe,则会被编码为 name=john%20doe。而 JSON 类型的参数会被编码为 UTF-8。
    .
  2. 参数顺序
    param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。
    .
  3. 数据类型
    param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。
  4. 嵌套性
    param 类型的参数不支持嵌套。但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。
    .
  5. 可读性
    param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。
    .

总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。

.
在实际开发中,
常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 JSON 类型的参数传递。

返回对象就是 json

简单类型接值

客户端请求

在这里插入图片描述
Handler 接收参数

  • 只要形参参数名和类型与传递的参数相同,即可自动接收!
  • 也可以不传递参数
@Controller
@RequestMapping("param")
public class ParamController {

    /**
     * 前端请求: http://localhost:8080/param/value?name=xx&age=18
     *
     * 可以利用形参列表,直接接收前端传递的param参数!
     *    要求: 参数名 = 形参名
     *          类型相同
     * 出现乱码正常,json接收具体解决!!
     * @return 返回前端数据
     */
    @GetMapping(value="/value")
    @ResponseBody
    public String setupForm(String name,int age){
        System.out.println("name = " + name + ", age = " + age);
        return name + age;
    }
}

简单类型接值【形参和请求参数不一致】

  • 指定绑定的请求参数名:@RequestParam(value="指定请求参数名") 【形参名和请求参数名一致可以省略】
  • 要求请求参数必须传递:required = false【前端是否必须传递此此参数,默认是必须,不传报400异常】
  • 为请求参数提供默认值: defaultValue = "1"【当非必须传递的时候, 可以设置默认值】)

浏览器请求

在这里插入图片描述
handler 接收参数

public class HelloController {


	//绑定请求参数为 myname 和 myage。myage 不必须传递。默认值为 0
    @RequestMapping(value = "/springmvc/hello")
    @ResponseBody
    public String hello
    (@RequestParam(value = "myname") String name, 		
    @RequestParam(value = "myage", required =false, defaultValue = "0") int age) {

        System.out.println("name:" + name + ",age:" + age);

        return name + ":" + age;
    }
}

数组类型接收

paramkey数组参数名 一致就会接收

客户端请求

在这里插入图片描述
handler 参数

@Controller
@RequestMapping("user")
public class testcontroller {


    @ResponseBody
    @RequestMapping
    //注意这里的 数组 参数名要和 param 的 key 一致
    public String test1(@RequestParam String[] names) {
        System.out.println(Arrays.toString(names));
        return Arrays.toString(names);
    }
}

集合类型接收

一个名字对应多个值

  • 多选框,提交的数据的时候一个key对应多个值,我们可以使用集合进行接收!集合用 @RequestParam 声明

.
注意:param 的 key 要和集合名字一样

客户端请求

在这里插入图片描述

handler 接收参数

@Controller
@RequestMapping("user")
public class testcontroller {


    @ResponseBody
    @RequestMapping
    //注意这里的 List 参数名要和 param 的 key 一致
    public String test1(@RequestParam List<String> names) {
        System.out.println(names);
        return names.toString();
    }

}

实体类型接收

要通过对象接收参数值,只需创建一个实体类,为每个属性配备 get 和 set 方法,客户端传递的 param key 要和 实体类的属性名一致。接收参数时,在形参列表中声明实体类对象即可。
.
如果是实体类对象的属性还是个对象那就用 address.provice 这样来做 param 的 key
.
注意点:实体类必须有 get 和 set

POJO

@Data
public class User {
    private int age;
    private String name;
    private Address address;
}
@Data
public class Address {
    private String province;
    private String city;
 }

客户端

在这里插入图片描述
handler

@Controller
@RequestMapping("user")
public class testcontroller {


    @ResponseBody
    @RequestMapping
    public String test1(User user) {
        System.out.println(user);
        return user.toString();
    }

}

日期参数接收

param 的 key 要和 日期类型的参数名一样。并且用 @DateTimeFormat 指定格式

客户端

在这里插入图片描述

handler

@Controller
@RequestMapping("user")
public class testcontroller {


    @ResponseBody
    @RequestMapping
    public String test1(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDate updateTime) {
        System.out.println(updateTime);
        return updateTime.toString();
    }

}

路径参数接收

{} 声明的路径原理就是 *

动态路径参数:http://localhost:8080/path/key/root 这里的 key/root 假如是动态路径。那它可以当参数传递给 handler

  • 接收动态路径参数必须要用 @PathVariable 声明
  • 如果 handler 形参名和路径的名字一样。那就直接 @PathVariable 就行。不一样就手动指定 @PathVariable 的 name

客户端

在这里插入图片描述

handler

@Controller
//这里 key 和 password 可以传递给下面的 handler
@RequestMapping("path/{key}/{password}")
public class testcontroller {


    @ResponseBody
    @RequestMapping
    public String 
    //第一个参数因为形参名和路径名不一样所以要设置 name 参数
    test1(@PathVariable(name= "key") String mykey, @PathVariable String password) {
        System.out.println(mykey + ":" + password);
        return mykey + ":" + password;
    }

}

路径匹配默认规则 【路径参数】

Ant 风格路径用法

Ant 风格的路径模式语法具有以下规则:

  • *:表示任意数量的字符。**
  • ?:表示任意一个字符。**
  • **:表示任意数量的目录
  • {}:表示一个命名的模式占位符
  • []:表示字符集合,例如 [a-z] 表示小写字母。

举例

  • *.html 匹配任意名称,扩展名为.html的文件。

  • /folder1/*/*.java 匹配在folder1目录下的任意两级目录下的.java文件。

  • /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。

  • /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。

注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:

  • 要匹配文件路径中的星号,则需要转义为\\*
  • 要匹配文件路径中的问号,则需要转义为\\?
新规则和旧规则改变

AntPathMatcherPathPatternParser

  • PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
  • PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
  • PathPatternParser \** 多段匹配的支持仅允许在模式末尾使用
    @GetMapping("/a*/b?/{p1:[a-f]+}")
    public String hello(HttpServletRequest request, 
                        @PathVariable("p1") String path) {

        log.info("路径变量p1: {}", path);
        //获取请求路径
        String uri = request.getRequestURI();
        return uri;
    }
总结
  • 使用默认的路径匹配规则,是由 PathPatternParser 提供的
  • 如果路径中间需要有 \**,替换成 ant 风格路径
# 改变路径匹配策略:
# ant_path_matcher 老版策略;
# path_pattern_parser 新版策略;
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

JSON 参数接收

前端发送 JSON 数据时,Spring MVC 框架可通过 @RequestBody 注解将其转为 Java 对象。此注解表示方法参数值从请求体获取,Springboot 中直接添加该注解就行,无需额外操作,且不用指定 value 属性,它会自动映射到相应的参数上。

  • 第一步:给前端来的 JSON 存储的 POJO 类
@Data
public class Person {

    private String name;
    private int age;
    private String gender;


}
  • 第二步:接收 JSON
@RequestMapping("/json")
@Controller
@ResponseBody
public class JsonController {

    //data -> 请求体 post {name,age,gender}
    //前端 传了一个 json 报 415
    //原因: java原生的api 只支持路径参数和 param 参数 不支持 json
    // json 本身就是前端的格式
    //解决: 1. 导入 json 处理的依赖 2.handlerAdapter 配置 json 转化器
    @PostMapping("data")
    public String data(@RequestBody Person person) {
        System.out.println("person = " + person);
        return person.toString();
    }
}

Cookie 数据接收

在这里插入图片描述

可以使用 @CookieValue 注解将 HTTP Cookie 的值绑定到的方法参数。

@Controller
@ResponseBody
@RequestMapping("/test")
public class testcontroller {

    @RequestMapping("/cooie")
    //形参名和 cookie  key 一样就不用给 @CookieValue 指定 name
    public String addPerson(@CookieValue String cookieName){

        System.out.println("value = " + cookieName);
        return cookieName;
    }

    //创建 Cookie
    @GetMapping("save")
    public String save(HttpServletResponse response) {
        Cookie cookie = new Cookie("cookieName", "root");
        response.addCookie(cookie);
        return "ok";
    }

}

请求头信息接收

在这里插入图片描述

  • 第一种:根据参数接收*

@RequestHeader("请求头的 key")

@GetMapping("/demo")
public void handle(
    @RequestHeader("Accept-Encoding") String encoding, 
    @RequestHeader("Keep-Alive") long keepAlive) { 
 	...
}
  • 第二种:根据形参名自动匹配*
//获取 token
//这种是根据根据参数名自动匹配
@GetMapping("/demo")
public void handle(@RequestHeader String token) { 
 
}

SpringBoot 响应数据


模板引擎

模板引擎就是类似 vue 的东西

SpringBoot 包含以下模板引擎的自动配置

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache
整合 Thymeleaf
  • 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • templatehtml 文件

在这里插入图片描述

  • controller
@Controller
public class WelcomeController {

    /**
     * 利用模板引擎跳转到指定页面
     * @return
     */
    @GetMapping("/well")
    public String hello(@RequestParam("name") String name, Model model) {

        //模板的逻辑视图名
        //物理视图 = 前缀 + 逻辑视图名 + 后缀
        //真是地址 = classpath:/templates/welcome.html

        //把需要给页面共享的数据放到 model 中
        model.addAttribute("msg", name);
        return "welcome";
    }

}

返回 JSON 数据

基本使用
  • 创建 pojo
@Data
public class User {

    private String name;

    private int age;

}
  • 响应数据
@RequestMapping("json")
@RestController //@Controller + @ResponseBody
public class JsonController {


    @GetMapping("data2")
    public List<User> data1() {
        User user = new User();
        user.setName("two dogs!");
        user.setAge(3);

        List<User> users = new ArrayList<>();
        users.add(user);
        return users;
    }
}
@ResponseBody 注解
  • 方法上使用 @ResponseBody

在前后端分离项目里,@ResponseBody 注解加在方法上,它会把方法返回的对象序列化成 JSON 或 XML 格式的数据,直接发给客户端。这意味着返回值 不会走视图解析器渲染这一步,而是 直接作为数据响应。

@RequestMapping(value = "/user/detail", method = RequestMethod.POST)
@ResponseBody
public User getUser(@RequestBody User userParam) {
    System.out.println("userParam = " + userParam);
    User user = new User();
    user.setAge(18);
    user.setName("John");
    //返回的对象,会使用jackson的序列化工具,转成json返回给前端!
    return user;
}
  • 在类上使用 @ResponseBody

如果类中每个方法上都标记了 @ResponseBody 注解,那么这些注解就可以提取到类上。

@ResponseBody  //responseBody可以添加到类上,代表默认类中的所有方法都生效!
@Controller
@RequestMapping("param")
public class ParamController {
@RestController 注解

类上的 @RestponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解。

内容协商 [ 返回 JSON 原理 ]

@ResponseBody 默认规则

基于请求头内容协商:(默认开启)

  • 客户端向服务端发送请求,携带 HTTP 标准的 Accept 请求头
  • Accept: application/jsontext/xmltext/yaml
  • 服务端根据客户端请求头期望的数据类型进行动态返回

.
.
基于请求参数内容协商:(需要手动开启)

  • 发送请求 GET /projects/spring-boot?format=json
    • format=... 的形式进行内容协商
  • 匹配到 @GetMapping("/projects/spring-boot")
  • 根据参数协商,优先返回 json 类型数据
  • 如果发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据
# 开启基于请求参数的内容协商
spring.mvc.contentnegotiation.favor-parameter=true  
#自定义参数名,默认为format
spring.mvc.contentnegotiation.parameter-name=myparam 

在这里插入图片描述

@ResponseBody 原理

@ResponseBodyHttpMessageConverter处理

  • HttpMessageConverter 会先进行内容协商

  • 默认MessageConverter有以下

    • ByteArrayHttpMessageConverter: 支持字节数据读写
    • StringHttpMessageConverter: 支持字符串读写
    • ResourceHttpMessageConverter:支持资源读写
    • ResourceRegionHttpMessageConverter: 支持分区资源写出
    • AllEncompassingFormHttpMessageConverter:支持表单 xml/json 读写
    • MappingJackson2HttpMessageConverter: 支持请求体响应体 Json 读写
  • 系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的HttpMessageConverter

静态资源

静态资源路径映射
  • 访问 /** 默认就来以下路径寻找静态资源
classpath:/META-INF/resources/

classpath:/resources/

classpath:/static/

classpath:/public/

注意:

  • 欢迎页 index.html 也是默认在以上路径查找。没有就在 templates下找 index.html项目启动默认访问
  • favicon.ico(浏览器窗口栏那个图标) 也是默认在以上路径查找
静态资源缓存规则

cachePeriod 【大概配置】

  • cachePeriod 是指资源被缓存的时间长度。在此时间范围内,资源会被浏览器或代理服务器缓存,不需要再次从服务器获取。
  • 默认值:如果没有设置 cachePeriod,则默认没有缓存周期,每次请求都会向服务器发送请求以获取最新的资源。
  • 单位:cachePeriod 的单位通常是秒(s)。例如,如果设置为 3600,则表示资源会被缓存1小时。
  • 作用:设置 cachePeriod 可以减少服务器的负载和网络延迟,提高资源的加载速度,特别是对于不经常变化的资源(如图片、CSS、JavaScript文件)。

.
cacheControl 【精确配置】

  • cacheControl 是HTTP协议中的一个头部字段,用于指定在HTTP请求 / 响应链中,资源应该如何被缓存。
  • 默认:没有 cacheControl
  • 详细说明:cacheControl 可以设置多个指令,例如:
    • no-cache:告诉浏览器或代理服务器在重新验证资源之前不能使用缓存资源。
    • no-store:告诉浏览器或代理服务器不存储这次请求或响应的任何部分。
    • max-age=<seconds>:指定资源被缓存的最大时间,单位为秒。
    • public:表明响应可以被任何缓存所存储。
    • private:表明响应只能被单个用户缓存,不能被共享缓存所存储。
  • 作用:通过 cacheControl,可以更精细地控制资源的缓存行为,确保用户能够获取到最新的资源,或者提高资源的加载速度。

.
userLastModified

  • 定义useLastModified 是一个标志,表示是否在HTTP响应中包含 Last-Modified 头部。

  • 默认::false

  • 详细定义:userLastModifiedtrue会带上一个Last-Modified(该资源的最后修改时间)。下次请求改资源会获取 If-Modified-Since。这个 If-Modified-Since 的值就是 Last-Modified(该资源的最后修改时间) 。要是资源从 If-Modified-Since开始就没动过。就直接返回 304 。直接使用缓存中的资源

  • 通过使用 Last-Modified 头部,可以减少不必要的数据传输,因为如果资源没有变化,服务器不需要重新发送资源的内容,只需发送一个状态码即可。

有了精确配置就不用大概配置了

自定义静态资源规则
配置方式

翻阅 WebMvConfigurer 接口笔记。一直往下进到 WebMvcAutoConfigurationAdapter。找到 addResourceHandlers 方法。这里就是静态资源规则源码

在这里插入图片描述

  • 静态资源访问路径默认规则
    • 照着 getStaticPathPattern() 一直往下点
    • 可以知道怎么利用 配置文件 自定义这个规则

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

# 重新设置静态资源访问路径
# 从以上图片可以看出前缀是 spring.mvc 后缀是 staticPathPattern
#会覆盖默认规则
spring.mvc.static-path-patteren=/static/**
  • 访问静态资源默认到哪个目录去找的默认规则
    • 照着 getStaticPathPattern() 一直往下点
    • 可以知道怎么利用 配置文件 自定义这个规则

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

# 重新设置态资源默认到哪个目录去找的规则

# 从以上图片看出 前缀是 spring.web 然后后缀是静态类中的 Resources 中的 staticLocations 属性。然后配置规则是 classpath

#会覆盖默认规则
spring.web.resources.static-locations=classpath:/a/, classpath:/b/
  • 缓存相关
##大概设置
#设置缓存时间
spring.web.resources.cache.period=3600

##详细设置,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存。只要是这个客户端。就谁都可以用这个缓存数据
spring.web.resources.cache.cachecontrol.cache-public=true

#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true

总结:

  • spring.mvc 前缀
    • 设置静态资源访问路径
  • spring.web前缀
    • 设置访问静态资源要找哪个路径
    • 设置静态资源缓存策略
代码方式

因为 WebMvcConfigurer接口包含了所有 SpringMVC 组件的默认配置。所以我们可以自己写一个配置了实现 WebMvcConfigurer接口。自己制定规则

  • 第一种方式
@Configuration
public class MyConfig implements WebMvcConfigurer  {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //保留以前的配置
        //没有 @EnableWebMvc 禁用boot 默认配置。就算没这句话默认配置也还在
        WebMvcConfigurer.super.addResourceHandlers(registry);

        //自己加一些配置
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/a/", "classpath:/b/")
                .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS))
    }
}
  • 第二种方式

为什么容器中放一个WebMvcConfigurer就能配置底层行为

  • **WebMvcAutoConfiguration 是一个自动配置类,它里面有一个EnableWebMvcConfiguration静态类 **

  • EnableWebMvcConfiguration继承于 DelegatingWebMvcConfiguration,这两个都生效

  • DelegatingWebMvcConfiguration利用 DI 把容器中 所有 WebMvcConfigurer 注入进来

  • 别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它调用所有 WebMvcConfigurer的配置底层方法。

@Configuration //这是一个配置类, 容器中放一个 webMMvcConfigurer组件, 就能自定义底层
public class MyConfig{

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/static/**")
                        .addResourceLocations("classpath:/static/")
                        .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
            }
        };
    }
}

SpringBoot 异常处理


单独写一个异常类。发生异常就会走此类下的 handler 方法

  • ControllerAdvice: 走字符串拼接网址那一套。前后端不分离的
  • RestControllerAdvice:直接返回数据。不拼接。前后端分离

只要发生异常就进入这里寻找对应的异常处理,并且要注意在配置类扫描这个全局异常类。注意要让主程序扫描到这个类

//全局异常发生会走此类下的 handler 方法
//@ControllerAdvice //可以返回逻辑视图 转发和重定向
@RestControllerAdvice 
public class GlobalExceptionHandler {

    //发生异常 -> 进入 @ControllerAdvice 注解的类型 -> 根据@ExceptionHandler(指定的异常) 去处理
    //指定的异常 可以精准查找 或者查找父异常

    @ExceptionHandler(ArithmeticException.class)
    public Object ArithmeticException(ArithmeticException e) {
        String message = e.getMessage();
        System.out.println("message = " + message);
        return message;
    }

    //如果没有 ArithmeticException 就走 Exception
    @ExceptionHandler(Exception.class)
    public Object Exception(Exception e) {
        String message = e.getMessage();
        System.out.println("message = " + message);
        return message;
    }


}

SpringBoot 登录校验


登录校验会话技术方案

概述

在这里插入图片描述

会话跟踪方案对比

在这里插入图片描述

JWT 令牌技术

概念
  • Token

令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。
.
简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验

在这里插入图片描述

  • JWT
  • jwt工作流程

    • 用户提供其凭据(通常是用户名和密码)进行身份验证。

    • 服务器对这些凭据进行验证,并在验证成功后创建一个JWT

    • 服务器将JWT发送给客户端,并客户端在后续的请求中将JWT附加在请求头或参数中。

    • 服务器接收到请求后,验证JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作

使用语法
  • 导入依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

<!-- JDK9以上需要这个依赖-->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>
  • 生成 JWT 令牌*
    • 签名算法可以去 JWT 官网查找,密钥自己设置
public class JWTTest {

    @Test
    public void jwtTest1() {

        //自己的内容
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", 1);
        claims.put("name", "com");

        String jwt =  Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "mangfu")//设置签名算法 和 密钥
                .setClaims(claims) //自定义内容(载荷)[自己的内容]
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置 jwt 有效期为 1 h (当前时间往后推)
                .compact(); //拿到字符串类型返回值

        System.out.println(jwt);
    }

注意事项

在这里插入图片描述

实战:登录成功,下发令牌

登录成功。令牌发到浏览器。后面浏览器每次访问服务端就携带令牌。到服务端进行登录校验

  • JWT 令牌工具类
    • 下发 JWT 令牌
    • 解析 JWT 令牌
public class JwtUtils {
	//设置密钥
	private static String signKey = "mangfu"
	//设置 JWT 令牌生效时间 12 小时
	private static Long expire = 43200000L;
	
	public static String generateJwt(Map<String, Object> claims) {
		String jwt = JWTs.builder()
				.addClaims(claims)
				.signWith(SignatureAlgoritth.HS256, signKey)
				.setExpiration(new Date(System.currentTimeMillis() + expire))
				.compact()
		return jwx;		
	}
    
    public static Claims parseJWT(String jwt) {
		Claims claims = JWTs.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();;

		return claims;		
	}
	
}

Filter 过滤器

概念

在这里插入图片描述

基本使用

在这里插入图片描述

  • 注意是 servlet 包下的 filter
  • initdestory 提供了默认实现可以不重写
详细使用细节
  • 使用流程

在这里插入图片描述

  • 拦截器路径设置

在这里插入图片描述

  • 过滤器链

在这里插入图片描述

实战:登录校验,令牌校验

用户登录成功后,会下发 JWT 令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端, 请求头名称为 token,值为登录时下发的 JWT 令牌

注意

在这里插入图片描述

校验流程

在这里插入图片描述

@WebServlet(urlPatterns = "/*")
public class DemoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        //1. 获取请求 url
        String url = req.getRequestURI().toString();
        System.out.println("请求的 url: " + url);

        //2.判断请求 url 中是否包含 login, 如果包含, 说明是登录操作, 放行
        if (url.contains("login")) {
            System.out.println("登录操作, 放行...");
            filterChain.doFilter(req, resp);
            return;
        }

        //3. 获取请求头中的令牌 token
        String jwt = req.getHeader("token");

        //4. 判断令牌是否存在, 如果不存在, 返回错误结果 (未登录)
        /*
             public static boolean hasLength(@Nullable String str) {
                return str != null && !str.isEmpty();
             }
         */
        if (StringUtils.hasLength(jwt)) {
            System.out.println("请求头 token 为空, 返回未登录的信息");

            /*
                Result error = Result.error("NOT_LOGIN");
                //把错误信息转成 JSON 发给前端
                String notLogin = JSONObject.toJSONString(error)
                resp.getWriter().write(notLogin);
             */

            return;
        }

        //5. 如果令牌存在, 解析 token, 如果解析失败, 返回错误结果 (未登录)
        //解析不报错说明成功, 报错说明失败
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("解析令牌失败, 返回未登录错误信息");

             /*
                Result error = Result.error("NOT_LOGIN");
                //把错误信息转成 JSON 发给前端
                String notLogin = JSONObject.toJSONString(error)
                resp.getWriter().write(notLogin);
             */
        }

        //6. 放行
        System.out.println("令牌合法, 放行");
        filterChain.doFilter(req, resp);

    }
}

Interceptor 拦截器

概念

在这里插入图片描述
在这里插入图片描述

拦截器使用

在这里插入图片描述

就是自定义一个拦截器类实现 HandlerInterceptor ,然后配置类实现 WebMvcConfigurer 扫描拦截器类。配置拦截路径

  • 实现 HandlerInterceptor 实现其所有方法
    在这里插入图片描述
  • 配置类实现 WebMvcConfigurer 添加拦截器
@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.atguigu.controller","com.atguigu.exceptionhandler"}) 
public class SpringMvcConfig implements WebMvcConfigurer {

  
     //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置方案1 拦截全部请求
        registry.addInterceptor(new MyInterceptor());

        //配置方案2 指定地址拦截
        // * 任意一层字符串 ** 任意多层字符串
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/user/data");

        //配置方案3 排除拦截 
        // addPathPatterns 需要拦截的路径
        // excludePathPatterns 需要拦截的路径中有哪些不拦截
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/user/**").excludePathPatterns("/user/data1");
    
    }
}




多个拦截器情况
  • 如果有多个拦截器,执行顺序
    • preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
    • postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
    • afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
实战:登录校验,令牌校验

在这里插入图片描述

@Component
public class DemoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1. 获取请求 url
        String url = request.getRequestURI().toString();
        System.out.println("请求的 url: " + url);

        //2.判断请求 url 中是否包含 login, 如果包含, 说明是登录操作, 放行
        if (url.contains("login")) {
            System.out.println("登录操作, 放行...");
            return true;
        }

        //3. 获取请求头中的令牌 token
        String jwt = request.getHeader("token");

        //4. 判断令牌是否存在, 如果不存在, 返回错误结果 (未登录)
        /*
             public static boolean hasLength(@Nullable String str) {
                return str != null && !str.isEmpty();
             }
         */
        if (StringUtils.hasLength(jwt)) {
            System.out.println("请求头 token 为空, 返回未登录的信息");

            /*
                Result error = Result.error("NOT_LOGIN");
                //把错误信息转成 JSON 发给前端
                String notLogin = JSONObject.toJSONString(error)
                resp.getWriter().write(notLogin);
             */

            return false;
        }

        //5. 如果令牌存在, 解析 token, 如果解析失败, 返回错误结果 (未登录)
        //解析不报错说明成功, 报错说明失败
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("解析令牌失败, 返回未登录错误信息");

             /*
                Result error = Result.error("NOT_LOGIN");
                //把错误信息转成 JSON 发给前端
                String notLogin = JSONObject.toJSONString(error)
                resp.getWriter().write(notLogin);
             */
        }

        //6. 放行
        System.out.println("令牌合法, 放行");
        return true;

    }
}

拦截器和过滤器的区别

在这里插入图片描述

RESTFul 风格设计规范

HTTP 协议请求方式要求

REST 风格主张在项目设计、开发过程中,具体的操作符合HTTP协议定义的请求方式的语义。

操作请求方式
查询操作GET
保存操作POST
删除操作DELETE
更新操作PUT

URL 风格要求

RESTful 风格的 API 设计里,URL 路径一般用名词命名,用来表示资源。资源可以是用户、订单这类实体,也能是搜索、计算等服务。设计 URL 路径时,强调用名词标识资源,而非用动词描述操作。比如,把/editEmp(动词)改成/Emp(名词) 。
.
例如:

  • GET /users:检索用户列表
  • POST /users:创建新用户
  • PUT /users/123:更新ID为123的用户
  • DELETE /users/123:删除ID为123的用户
操作传统风格REST 风格
保存/CRUD/saveEmp URL 地址:/CRUD/emp 请求方式:POST
删除/CRUD/removeEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:DELETE
更新/CRUD/updateEmp URL 地址:/CRUD/emp 请求方式:PUT
查询/CRUD/editEmp?empId=2URL 地址:/CRUD/emp/2 请求方式:GET

传递参数设计要求

  • 获取数据 GET ,删除数据 DELTETE

    • 参数是 id 标识。使用路径 方式 /url/id
    • 参数是 是 范围参数。使用 param 方式 /url?page=1&size=10
  • 保存数据 POST,修改数据 PUT

    • 全部使用请求体传递 JSON 方式

其他原则

  • 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。
  • 对于敏感信息,最好使用 POST 采用请求体来传递参数。
  • 如果地址冲突 (请求方式 和 路径都一样) :那就在后面加个动词区分一下,比如 GET /user GET /user/search

实战举例

接口设计

功能接口和请求方式请求参数返回值
分页查询GET /userpage=1&size=10{ 响应数据 }
用户添加POST /user{ user 数据 }{响应数据}
用户详情GET /user/1路径参数{响应数据}
用户更新PUT /user{ user 更新数据}{响应数据}
用户删除DELETE /user/1路径参数{响应数据}
条件模糊GET /user/searchpage=1&size=10&keywork=关键字{响应数据}
  • 用户 pojo
package com.atguigu.pojo;

/**
 * projectName: com.atguigu.pojo
 * 用户实体类
 */
@Data
public class User {

    private Integer id;
    private String name;

    private Integer age;
}

  • controller
/**
 * projectName: com.atguigu.controller
 *
 * description: 用户模块的控制器
 */
@RequestMapping("user")
@RestController
public class UserController {

    /**
     * 模拟分页查询业务接口
     */
    @GetMapping
    public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
                            @RequestParam(name = "size",required = false,defaultValue = "10")int size){
        System.out.println("page = " + page + ", size = " + size);
        System.out.println("分页查询业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟用户保存业务接口
     */
    @PostMapping
    public Object saveUser(@RequestBody User user){
        System.out.println("user = " + user);
        System.out.println("用户保存业务!");
        return "{'status':'ok'}";
    }

    /**
     * 模拟用户详情业务接口
     */
    @PostMapping("/{id}")
    public Object detailUser(@PathVariable Integer id){
        System.out.println("id = " + id);
        System.out.println("用户详情业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟用户更新业务接口
     */
    @PutMapping
    public Object updateUser(@RequestBody User user){
        System.out.println("user = " + user);
        System.out.println("用户更新业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟条件分页查询业务接口
     */
    @GetMapping("search")
    public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
                            @RequestParam(name = "size",required = false,defaultValue = "10")int size,
                            @RequestParam(name = "keyword",required= false)String keyword){
        System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);
        System.out.println("条件分页查询业务!");
        return "{'status':'ok'}";
    }
}

http://www.kler.cn/a/525828.html

相关文章:

  • 我是如何写作的?
  • 一文掌握ADB的安装及使用
  • 回顾:Maven的环境搭建
  • 为大模型提供webui界面的利器:Open WebUI 完全本地离线部署deepseek r1
  • 程序地址空间
  • 《多阶段渐进式图像修复》学习笔记
  • CF EDU ROUND 172
  • unity学习24:场景scene相关生成,加载,卸载,加载进度,异步加载场景等
  • 前端进阶:深度剖析预解析机制
  • 电梯系统的UML文档13
  • 跟李沐学AI:视频生成类论文精读(Movie Gen、HunyuanVideo)
  • python学opencv|读取图像(五十一)使用修改图像像素点上BGR值实现图像覆盖效果
  • java求职学习day19
  • AI协助探索AI新构型的自动化创新概念
  • 8641 冒泡排序
  • 【教学类-89-03】20250113新年篇03——福字贴(PPTX艺术字-空心字)
  • 我的求职面经:(2)C++中空指针请使用nullptr不要使用NULL
  • Haproxy介绍及学习
  • 代码随想录_栈与队列
  • 1/30每日一题
  • 课题推荐:基于matlab,适用于自适应粒子滤波的应用
  • [权限提升] Windows 提权 — 系统内核溢出漏洞提权
  • 三路排序算法
  • 拼车(1094)
  • 在汇编语言中,ASSUME 是一个用于告诉汇编器如何将段寄存器与特定段名称关联的指令
  • AutoDL 云服务器:xfce4 远程桌面 终端乱码 + 谷歌浏览器