Spring-MVC

news/2024/9/25 9:42:58

Spring-MVC

介绍

https://docs.spring.io/spring-framework/reference/web/webmvc.html

Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。

在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:

  • Spring 家族原生产品,与IOC容器等基础设施无缝对接
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

原生Servlet API开发代码片段

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  String userName = request.getParameter("userName");System.out.println("userName="+userName);
}

基于SpringMVC开发代码片段

@RequestMapping("/user/login")
public String login(@RequestParam("userName") String userName,Sting password){log.debug("userName="+userName);//调用业务即可return "result";
}

主要作用

image-20240903103956388

SSM框架构建起单体项目的技术栈需求!其中的SpringMVC负责表述层(控制层)实现简化!

SpringMVC的作用主要覆盖的是表述层,例如:

  • 请求映射
  • 数据输入
  • 视图界面
  • 请求分发
  • 表单回显
  • 会话控制
  • 过滤拦截
  • 异步交互
  • 文件上传
  • 文件下载
  • 数据校验
  • 类型转换
  • 等等等

最终总结:

  1. 简化前端参数接收( 形参列表 )
  2. 简化后端数据响应(返回值)
  3. 以及其他......

核心组件和调用流程理解

Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet DispatcherServlet 做整体请求处理调度!

除了DispatcherServletSpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。

SpringMVC处理请求流程:

SpringMVC涉及组件理解:

  1. DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
  2. HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
  3. HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]
  4. Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
  5. ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

快速体验

创建一个spring-mvc项目(引入依赖)

  <dependencies><!--引入spring-web依赖--><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>

Controller声明

@Controller
public class Hello{/*** hello就是controller内部的具体方法* @RequestMapping("/hello") 就是用来向handlerMapping中注册的方法注解!* @ResponseBody 代表向浏览器直接返回数据,不会默认跳转新页面*/@ResponseBody@RequestMapping("/hello")public String hello(){return "你好,spring-mvc";}
}

测试

image-20240903105100393

路径映射 - @RequestMapping

访问路径设置

@RequestMapping注解的作用就是将请求的 URL 地址处理请求的方式关联起来,建立映射关系。

路径位置通配符: 如果多个路径都能匹配上,那就精确优先

精准路径匹配

在@RequestMapping注解指定 URL 地址时,不使用任何通配符,按照请求地址进行精确匹配。

//@ResponseBody 
//@Controller
/**
这一个注解可以代替上面两个
*/
@RestController
public class UserController {/*** 精准设置访问地址 /user/login*/@RequestMapping(value = {"/user/login"})public String login(){System.out.println("UserController.login");return "login success!!";}
}

模糊路径匹配

在@RequestMapping注解指定 URL 地址时,通过使用通配符,匹配多个类似的地址。

@RestController
public class ProductController {/***  路径设置为 /product/*  *    /* 为单层任意字符串  /product/a  /product/aaa 可以访问此handler  *    /product/a/a 不可以**  路径设置为 /product/** *   /** 为任意层任意字符串  /product/a  /product/aaa 可以访问此handler  *   /product/a/a 也可以访问**  路径设置为 /product/hell?*  ? 为任意单个字符  /product/hello   /product/helle 可以方法此handler*  /product/hellaa 不可以*/@RequestMapping("/product/*")public String show(){System.out.println("ProductController.show");return "product show!";}
}

单层匹配和多层匹配:
/:只能匹配URL地址中的一层,如果想准确匹配两层,那么就写“//*”以此类推。
/**:可以匹配URL地址中的多层。

其中所谓的一层或多层是指一个URL地址字符串被“/”划分出来的各个层次

这个知识点虽然对于@RequestMapping注解来说实用性不大,但是将来配置拦截器的时候也遵循这个规则。

@RequestMapping写到 ==》类和方法上的区别

@RequestMapping 注解可以用于类级别和方法级别,它们之间的区别如下:

  1. 设置到类级别:@RequestMapping 注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。
  2. 设置到方法级别:@RequestMapping 注解也可以单独设置在控制器方法上,用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 @RequestMapping 注解进行更精细的映射。
//1.标记到handler方法
@RequestMapping("/user/login")
@RequestMapping("/user/register")
@RequestMapping("/user/logout")//2.优化标记类+handler方法
//类上
@RequestMapping("/user")//handler方法上
@RequestMapping("/login")
@RequestMapping("/register")
@RequestMapping("/logout")

@RequestMapping的属性

请求方式:method

//限定前端的请求方式
//method={RequestMethod.POST,RequestMethod.GET}
@RequestMapping(value = "/test01",method = RequestMethod.POST)
public String test01(){return "test01";
}

请求参数:params

/**
* 请求参数:params = {"username","age"}
* 1)、username:  表示请求必须包含username参数
* 2)、age=18:   表示请求参数中必须包含age=18的参数
* 3)、gender!=1:  表示请求参数中不能包含gender=1的参数
* @return
*/
@RequestMapping(value = "/test02",params = {"age=18","username","gender!=1"})
public String test02(){return "test02";
}

请求头:headers

/*** 请求头:headers = {"haha"}* 1)、haha:  表示请求中必须包含名为haha的请求头* 2)、hehe!=1:  表示请求头中 的 hehe 不能是1;(hehe=0,不带hehe)* @return*/
@RequestMapping(value = "/test03",headers = "haha")
public String test03(){return "test03";
}

请求内容类型:consumes

/**
* 请求内容类型:consumes = {"application/json"}; 消费什么数据;
* Media Type:媒体类型
* 1)、application/json:  表示浏览器必须携带 json 格式的数据。
* @return
*/
@RequestMapping(value = "/test04",consumes = "application/json")
public String test04(){return "test04";
}

响应内容类型:produces

/*** 响应内容类型:produces = {"text/plain;charset=utf-8"}; 生产什么数据;* @return*/
@RequestMapping(value = "/test05",produces = "text/html;charset=utf-8")
public String test05(){return "<h1>你好,张三</h1>";
}

参数处理 - HTTP请求与响应

HTTP请求

  1. 请求行:包含请求方法(如GET、POST)、请求的URL和HTTP版本。例如:

    GET /index.html HTTP/1.1  
    
  2. 请求头:包含关于请求的元数据,例如:

    • Host: 请求的主机名
    • User-Agent: 客户端软件的信息
    • Accept: 客户端可接受的内容类型
  3. 请求体(可选):在某些请求方法(如POST)中,可能包含要发送到服务器的数据。

HTTP响应

  1. 状态行:包含HTTP版本、状态码和状态消息。例如:

    HTTP/1.1 200 OK  
    
  2. 响应头:包含关于响应的元数据,例如:

    • Content-Type: 响应内容的类型
    • Content-Length: 响应体的长度
    • Set-Cookie: 设置cookie
  3. 响应体:包含实际返回的数据,例如HTML文档、JSON数据等。

URL 携带大量数据,特别是GET请求,会把参数放在URL上

image-20240903155820307

请求体 携带大量数据,特别是POST请求,会把参数放在请求体中

image-20240903160000691

JSON 数据格式

JavaScript Object Notation(JavaScript 对象表示法)

JSON用于将结构化数据表示为 JavaScript 对象的标准格式,通常用于在网站上表示和传输数据

JSON 可以作为一个对象或者字符串存在

​ 前者用于解读 JSON 中的数据,后者用于通过网络传输 JSON 数据。

​ JavaScript 提供一个全局的 可访问的 JSON 对象来对这两种数据进行转换。

JSON 是一种纯数据格式,它只包含属性,没有方法。

备注:将字符串转换为原生对象称为反序列化(deserialization),

而将原生对象转换为可以通过网络传输的字符串称为序列化(serialization)。

JSON数据格式图和访问方式

image-20240903160353674

数据收集请求处理

直接接值

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

@RequestParam注解

可以使用 @RequestParam 注释将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。

@RequestParam使用场景:

  • 指定绑定的请求参数名
  • 要求请求参数必须传递

​ required = false:非必须携带;

  • 为请求参数提供默认值

​ defaultValue = "123456":默认值,参数可以不带;

@RequestMapping("/handle02")
public String handle02(@RequestParam("username") String name,@RequestParam(value = "password",defaultValue = "123456") String pwd,@RequestParam("cellphone") String phone,@RequestParam(value = "agreement",required = false) boolean ok){System.out.println(name);System.out.println(pwd);System.out.println(phone);System.out.println(ok);return "ok";
}

实体类接值

如果目标方法参数是一个 实体类;SpringMVC 会自动把请求参数 和 实体类属性进行匹配

@Data
class Person{String username;String password;String cellphone;boolean agreement;
}
//1、pojo的所有属性值都是来自于请求参数
//2、如果请求参数没带,封装为null;
@RequestMapping("/handle03")public String handle03(Person person){System.out.println(person);return "ok";
}

@RequestHeader注解

获取请求头信息

@RequestMapping("/handle04")
public String handle04(@RequestHeader(value = "host",defaultValue = "127.0.0.1") String host,@RequestHeader("user-agent") String ua){System.out.println(host);System.out.println(ua);return "ok~"+host;
}

@CookieValue注解

获取cookie值

@RequestMapping("/handle05")
public String handle05(@CookieValue("haha") String haha){return "ok:cookie是:" + haha;
}

@RequestBody注解[JSON数据接收]

@RequestBody: 获取请求体json字符串数据,然后自动转为person对象

前端发送 JSON 数据的示例:(使用postman测试)

{"name": "张三","age": 18,"gender": "男"
}

定义一个用于接收 JSON 数据的 Java 类

public class Person {private String name;private int age;private String gender;// getter 和 setter 略
}
//@RequestBody Person person//1、拿到请求体中的json字符串//2、把json字符串转为person对象
@RequestMapping("/handle06")
public String handle07(@RequestBody Person person){System.out.println(person);return "ok";
}//获取JSON字符串
@RequestMapping("/handle06")
public String handle07(@RequestBody String person){System.out.println(person);return "ok";
}

@RequestPart/@RequestParam【文件上传】

上传文件前端form要求:【method=post、enctype="multipart/form-data"】

文件上传:@RequestPart/@RequestParam 取出文件项,封装为MultipartFile,就可以拿到文件内容

@RequestMapping("/handle07")
public String handle08(@RequestParam("headerImg") MultipartFile headerImgFile,@RequestPart("lifeImg") MultipartFile[] lifeImgFiles) throws IOException {//1、获取原始文件名String originalFilename = headerImgFile.getOriginalFilename();//2、文件大小long size = headerImgFile.getSize();//3、获取文件流InputStream inputStream = headerImgFile.getInputStream();System.out.println(originalFilename + " ==> " + size);//4、返回文件的内容类型headerImgFile.getContentType();//5、文件保存==transferToheaderImgFile.transferTo(new File("D:\\img\\" + originalFilename));if (lifeImgFiles.length > 0) {for (MultipartFile imgFile : lifeImgFiles) {imgFile.transferTo(new File("D:\\img\\" + imgFile.getOriginalFilename()));}}return "ok!!!";
}

注意:SpringMVC对上传文件有大小限制(默认单文件最大:1MB;整个请求最大:10MB)

设置自定义大小

spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=1000MB

HttpEntity<>类:封装请求原始数据

HttpEntity<>:封装请求头、请求体; 把整个请求拿过来。
泛型::请求体类型; 可以自动转化

@RequestMapping("/handle08")
public String handle09(HttpEntity<Person> entity){//1、拿到所有请求头HttpHeaders headers = entity.getHeaders();System.out.println("请求头:"+headers);//2、拿到请求体Person body = entity.getBody();System.out.println("请求体:"+body);return "Ok";
}

接受原生 API

下表描述了支持的控制器方法参数

Controller method argument 控制器方法参数 Description
jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse 请求/响应对象
jakarta.servlet.http.HttpSession 强制存在会话。因此,这样的参数永远不会为 null
java.io.InputStream, java.io.Reader 用于访问由 Servlet API 公开的原始请求正文。
java.io.OutputStream, java.io.Writer 用于访问由 Servlet API 公开的原始响应正文。
@PathVariable 接收路径参数注解
@RequestParam 用于访问 Servlet 请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。
@RequestHeader 用于访问请求标头。标头值将转换为声明的方法参数类型。
@CookieValue 用于访问Cookie。Cookie 值将转换为声明的方法参数类型。
@RequestBody 用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap 共享域对象,并在视图呈现过程中向模板公开。
Errors, BindingResult 验证和数据绑定中的错误信息获取对象!
/*** 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!* 注意: 接收原生对象,并不影响参数接收!*/
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,HttpServletResponse response){String method = request.getMethod();System.out.println("method = " + method);return "api";
}

image-20240903184849208

数据收集响应处理

@ResponseBody[对象序列化为 JSON]

可以在方法上使用 @ResponseBody注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。

//会自动的把返回的对象转为json格式
//@ResponseBody //把返回的内容。写到响应体中
@RequestMapping("/resp01")
public Person resp01() {Person person = new Person();person.setUsername("张三");person.setAge(21);person.setSex("男");return person;
}

ResponseEntity<>类

HttpEntity:拿到整个请求数据
ResponseEntity:拿到整个响应数据(响应头、响应体、状态码)

通过ResponseEntity<>类实现文件下载

@RequestMapping("/download")
public ResponseEntity<InputStreamResource> download() throws IOException {FileInputStream inputStream = new FileInputStream("c:/img/a.jpg");//1、文件名中文会乱码:解决:String encode = URLEncoder.encode("哈哈美女.jpg", "UTF-8");//2、文件太大会oom(内存溢出)InputStreamResource resource = new InputStreamResource(inputStream);return ResponseEntity.ok()//内容类型:流.contentType(MediaType.APPLICATION_OCTET_STREAM)//内容大小.contentLength(inputStream.available())//  Content-Disposition :内容处理方式.header("Content-Disposition", "attachment;filename="+encode).body(resource);
}

image-20240903194935983

企业响应数据方式

为了统一响应格式,企业通常会定义一个通用的响应类。这种响应类包含状态码、消息和数据等字段。

public class Result<T> {  private String code;  private String message;  private T data;  public static<T> Result<T> success(String code, String msg, T data){Result<T> result = new Result<>();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}//省略......
}  // 控制器示例  
@GetMapping("/api/users")  
public Result<List<User>> getUsers() {  List<User> users = userService.getAllUsers();  return new Result<>("200", "成功", users);  
}

REST 风格

REST(Representational State Transfer 表现层状态转移)是一种软件架构风格;

REST 简介 按照REST风格访问资源时使用行动动作区分对资源进行了何种操作

image-20240904114934199

@PathVariable-路径变量

用于接收路径的可变参数。 路径上使用 { 参数名称} 描述路径可变参数

@GetMapping("/employee/{id}") //{id} 前端可以传来任意
public R getEmployeeById(@PathVariable("id") Long id){  //业务代码
}

请求参数放在路径上,以?k=v的形式

@RequestParam 用于接收 url 地址传参或表单传参

@RequestBody 用于接收 json 数据

请求参数直接放在路径上,而不是: ?k=v

@PathVariable 用于接收路径的可变参数。 路径上使用 { 参数名称} 描述路径可变参数

后期开发中,发送请求参数超过 1 个时,以 json 格式为主,@RequestBody应用较广。

如果发送非 json格式数据,选用 @RequestParam 接收请求参数。

采用 RESTful 进行开发,当参数数量较少时,例如 1 个,可以采用 @PathVariable 接收请求路径变量参数,通常用于传递 id 值

RESTful快速开发

@RestController 等同于 @Controller 与 @ResponseBody 两个注解组合功能

@GetMapping @PostMapping @PutMapping @DeleteMapping

设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,

请求方式 作用
GET 查询某个员工
POST 新增某个员工
PUT 修改某个员工
DELETE 删除某个员工
GET 查询所有员工
GET 查询所有员工

CORS(跨域资源共享)

跨域问题主要是由于浏览器的同源策略(Same-Origin Policy)引起的。

同源策略要求,只有当网页和其请求的资源来自同一个源(即协议、域名和端口号都相同)时,浏览器才允许网页访问这些资源。如果网页试图访问一个不同源的资源,就会遇到跨域问题。

  • 不同的协议:如果一个网站使用 HTTPS,而另一个网站使用 HTTP,它们被视为不同的源。
  • 不同的域名:即使两个网站在同一协议下,如果它们的域名不同(如 example.comsub.example.com),也会引起跨域。
  • 不同的端口:即使协议和域名相同,如果端口不同(如 example.com:80example.com:8080),也会被视为不同的源。
  • AJAX 请求:当使用 AJAX 请求从一个源(如 example.com)访问另一个源(如 api.example.com)时,浏览器会阻止这种请求,除非目标服务器允许跨域请求。
  • iframe 嵌套:如果一个页面通过 iframe 嵌套了来自不同源的页面,可能会引发跨域问题,尤其是在尝试访问嵌套页面的 DOM 时。

解决办法

CORS(跨域资源共享):服务器通过设置 Access-Control-Allow-Origin 等 HTTP 头来允许来自不同源的请求。

@RestController
@CrossOrigin //设置==>允许不同源的请求
@RequestMapping("/api/v1")
public class EmployeeController {//业务代码
}

JSONP:通过 <script> 标签加载数据,这种方式不受同源策略限制,但存在安全隐患。

代理服务器:通过在同一源的服务器上设置代理,将请求转发到目标服务器,从而绕过跨域限制。

拦截器(HandlerInterceptor接口)

在 Spring MVC 中,拦截器(Interceptor)是用于处理请求的对象,类似于 Servlet 的过滤器。拦截器可以在请求到达控制器之前和响应返回之前进行处理,常用于日志、权限检查、请求修改等功能。实现了 HandlerInterceptor 接口的类可以作为拦截器。

使用步骤

实现 HandlerInterceptor 接口的组件即可成为拦截器

注意:这样拦截器并不会起作用。需创建一个配置类,实现WebMvcConfigurer接口重写addInterceptors方法配置和添加拦截器。

import org.springframework.stereotype.Component;  
import org.springframework.web.servlet.HandlerInterceptor;  import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  @Component //也要成为组件 
public class MyInterceptor implements HandlerInterceptor {  @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  // 在请求到达控制器之前执行  System.out.println("preHandle: " + request.getRequestURI());  return true; // 返回true表示继续处理请求,返回false则中断请求  }  @Override  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  // 在控制器执行后、视图渲染前执行  System.out.println("postHandle: " + request.getRequestURI());  }  @Override  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  // 在整个请求完成后执行  System.out.println("afterCompletion: " + request.getRequestURI());  if (ex != null) {  System.out.println("Exception: " + ex.getMessage());  }  }  }
拦截器参数:
前置处理: Request:请求对象	,response: 响应对象 handler: 被调用的处理器对象,本质上是一个方法对象,对反射技术中的 Method 对象进行了再包装。后置处理modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。完成后处理Exception :如果处理器执行中·出现异常对象,可以针对异常情况进行单独处理

创建 WebMvcConfigurer 组件,并配置拦截器的拦截路径

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;  
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  @Configuration  
public class WebConfig implements WebMvcConfigurer {  @Autowired  private MyInterceptor myInterceptor;  @Override  public void addInterceptors(InterceptorRegistry registry) {  registry.addInterceptor(myInterceptor)  .addPathPatterns("/**") // 拦截所有请求,可以根据需要修改  .excludePathPatterns("/public/**"); // 排除某些路径  }  
}

执行顺序效果:preHandle => 目标方法 => postHandle => afterCompletion

多个拦截器

拦截器执行顺序:顺序preHandle => 目标方法 => 倒序postHandle => 渲染 => 倒序afterCompletion

  • 只有执行成功的 preHandle 才会倒序执行 afterCompletion

  • postHandle 、afterCompletion 从哪里炸(异常报错),倒序链路从哪里结束

  • postHandle 失败不会影响 afterCompletion 执行

image-20240904154104344

拦截器 vs 过滤器

拦截器 过滤器
接口 HandlerInterceptor Filter
定义 Spring 框架 Servlet 规范
放行 preHandle 返回 true 放行请求 chain.doFilter() 放行请求
整合性 可以直接整合Spring容器的所有组件 不受Spring容器管理,无法直接使用容器中组件 需要把它放在容器中,才可以继续使用
拦截范围 拦截 SpringMVC 能处理的请求 拦截Web应用所有请求
总结 SpringMVC的应用中,推荐使用拦截器

WebMvcConfigurer组件

WebMvcConfigurer 是 Spring Framework 中的一个接口,用于配置 Spring MVC 的各种设置。

它允许开发者通过实现该接口来定制 Spring MVC 的行为,通过实现 WebMvcConfigurer,可以灵活地添加自定义的配置,比如视图解析、拦截器、消息转换器等。

import org.springframework.context.annotation.Configuration;  
import org.springframework.web.servlet.config.annotation.EnableWebMvc;  
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  @Configuration  
@EnableWebMvc  
public class WebConfig implements WebMvcConfigurer {  // 自定义配置方法 
}

添加拦截器

@Override  
public void addInterceptors(InterceptorRegistry registry) {  registry.addInterceptor(new MyInterceptor());  
}  

配置视图解析器

@Override  
public void configureViewResolvers(ViewResolverRegistry registry) {  registry.jsp().prefix("/WEB-INF/views/").suffix(".jsp");  
}  

添加格式化和转换器

@Override  
public void addFormatters(FormatterRegistry registry) {  registry.addConverter(new MyConverter()); registry.addConverterFactory(new MyconverterFactory());
}  

静态资源配置

@Override  
public void addResourceHandlers(ResourceHandlerRegistry registry) {  registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");  
}  

跨域请求配置

@Override  
public void addCorsMappings(CorsRegistry registry) {  registry.addMapping("/**").allowedOrigins("http://example.com");  
}  

异常处理(@ExceptionHandler)

对于异常的处理,一般分为两种方式:

  • 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
  • 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 @Throws@ExceptionHandler),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。

编程式异常处理:try - catch、throw

声明式异常处理:SpringMVC 提供了 @ExceptionHandler、@ControllerAdvice 等便捷的声明式注解来进行快速的异常处理

  • @ExceptionHandler:可以处理指定类型异常
  • @ControllerAdvice:可以集中处理所有Controller的异常
  • @ExceptionHandler + @ControllerAdvice: 可以完成全局统一异常处理

控制器级别的异常处理

在控制器中,可以使用 @ExceptionHandler 注解来处理特定的异常。该注解可以应用于控制器方法,指定该方法处理特定类型的异常。

import org.springframework.http.HttpStatus;  
import org.springframework.http.ResponseEntity;  
import org.springframework.web.bind.annotation.ControllerAdvice;  
import org.springframework.web.bind.annotation.ExceptionHandler;  
import org.springframework.web.bind.annotation.RestController;  @RestController  
public class MyController {  @GetMapping("/example")  public String example() {  throw new RuntimeException("An error occurred");  }  @ExceptionHandler(RuntimeException.class) //统一处理运行时异常public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {  // ex中包括了错误信息return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);  }  
}

全局异常处理

使用 @ControllerAdvice 注解可以创建一个全局异常处理类,该类中的方法可以处理来自所有控制器的异常。这种方式使得异常处理逻辑集中化,便于管理和维护。

@RestControllerAdvice 可以代替 @ControllerAdvice和@ResponseBody

import org.springframework.http.HttpStatus;  
import org.springframework.http.ResponseEntity;  
import org.springframework.web.bind.annotation.ControllerAdvice;  
import org.springframework.web.bind.annotation.ExceptionHandler;  //@ControllerAdvice
//@ResponseBody //返回数据以JSON形式返回
@RestControllerAdvice
public class GlobalExceptionHandler {  @ExceptionHandler(Throwable.class)  // 表示处理所有错误public ResponseEntity<String> handleGenericException(Exception ex) {  return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);  }  }

企业-异常处理方式

自定义异常类

// 自定义运行时异常  
public class ResourceNotFoundException extends RuntimeException {  private String code;  private String message;public ResourceNotFoundException(ErrorCode errorCode) {this.code= myExecptionEnum.getCode();this.message= myExecptionEnum.getMessage();}public String getCode() {return code;}public String getMessage() {return message;}
}// 自定义检查异常  
public class BusinessException extends Exception {  private String code;  private String message;public BusinessException(ErrorCode errorCode) {this.code= myExecptionEnum.getCode();this.message= myExecptionEnum.getMessage();}public String getCode() {return code;}public String getMessage() {return message;}}

自定义异常枚举类

public enum ErrorCode {  USER_NOT_FOUND("1001", "用户未找到"),  INVALID_INPUT("1002", "无效输入"),  DATABASE_ERROR("1003", "数据库错误"),  UNAUTHORIZED_ACCESS("1004", "未授权访问");  private final String code;  private final String message;  ErrorCode(String code, String message) {  this.code = code;  this.message = message;  }  public String getCode() {  return code;  }  public String getMessage() {  return message;  }  
}

使用全局异常处理器捕获自定义异常生成统一的响应==>这种响应类包含状态码、消息和数据等字段。

import org.springframework.http.HttpStatus;  
import org.springframework.http.ResponseEntity;  
import org.springframework.web.bind.annotation.ControllerAdvice;  
import org.springframework.web.bind.annotation.ExceptionHandler;  @ControllerAdvice  
public class GlobalExceptionHandler {  @ExceptionHandler(CustomException.class)  public Result<String> handleCustomException(CustomException e) {  return Result.error(ex.getCode(),ex.getMessage());}   
}

在服务层等地方检查业务规则,并在需要时抛出自定义异常。

@RestController
public class UserService {  @GetMapping("/user/{id}")public User findUserById(Long userId) {  User user = userRepository.findById(userId);  if (user == null) {  throw new CustomException(ErrorCode.USER_NOT_FOUND);  }  return user;  } 
}

扩展 - SpringBoot底层异常处理默认行为

SpringBoot 依然 使用 SpringMVC 的异常处理机制

不过 SpringBoot 编写了一些默认的处理配置

默认行为==>自适应的异常处理:

浏览器发的请求,出现异常返回默认错误页面

移动端发的请求,出现异常返回默认json错误数据;项目开发的时候错误模型需要按照项目的标准走

数据校验[validation]

Spring项目通常会集成Hibernate Validator作为数据校验的实现。你可以通过Java Bean Validation(JSR 380)注解来定义校验规则。

<!--添加依赖-->
<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-validation</artifactId>  
</dependency>  
校验注解 作用
@AssertFalse 验证Boolean类型字段是否为false
@AssertTrue 验证Boolean类型字段是否为true
@DecimalMax 验证字符串表示的数字是否小于等于指定的最大值
@DecimalMin 验证字符串表示的数字是否大于等于指定的最小值
@Digits(integer, fraction) 验证数值是否符合指定的格式,integer指定整数精度,fraction指定小数精度
@Email 验证字符串是否为邮箱地址格式
@Future 验证日期是否在当前时间之后
@Past 验证日期是否在当前时间之前
@Min(value) 验证数字是否大于等于指定的最小值
@Max(value) 验证数字是否小于等于指定的最大值
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 与@Null相反(a!=null)
@NotEmpty 验证字符串是否非空(a!=null && a!=“”)
@NotBlank 验证字符串是否非空白字符(a!=null && a.trim().length > 0)
@Size(max=, min=) 验证字符串、集合、Map、数组的大小是否在指定范围内
@Pattern(regex=, flag=) 验证字符串是否符合指定的正则表达式

给Bean的字段标注校验注解,并指定校验错误消息提示

import javax.validation.constraints.Email;  
import javax.validation.constraints.NotBlank;  public class UserDTO {  @NotBlank(message = "用户名不能为空")  private String username;  @NotBlank(message = "密码不能为空")  private String password;  @Email(message = "邮箱格式不正确")  private String email;  // Getter和Setter省略  
}

使用【@Valid】或@Validated(不用)开启校验

import org.springframework.http.ResponseEntity;  
import org.springframework.validation.annotation.Validated;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestBody;  
import org.springframework.web.bind.annotation.RestController;  import javax.validation.Valid;  @RestController  
public class UserController {  @PostMapping("/api/users")  public Result<String> createUser(@RequestBody @Valid UserDTO userDTO ,BindingResult bindingResult) {  //使用 BindingResult 封装校验结果// 创建用户逻辑  return Result.ok("用户创建成功");  }  
}

如果校验不通过,目标方法不执行

自定义校验注解

创建自定义注解

import javax.validation.Constraint;  
import javax.validation.Payload;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  //校验器去真正完成校验功能
@Constraint(validatedBy = {GenderValidator.class})   //设置指定限定类CustomValidator
@Target({ ElementType.FIELD })  
@Retention(RUNTIME)
public @interface Gender {  String message() default "值不合法";  Class<?>[] groups() default {};  Class<? extends Payload>[] payload() default {};  
}

创建一个实现ConstraintValidator的类,定义实际的校验逻辑。

import javax.validation.ConstraintValidator;  
import javax.validation.ConstraintValidatorContext;  public class GenderValidator implements ConstraintValidator<Gender, String> {/**** @param value 前端提交来的准备让我们进行校验的属性值* @param context 校验上下文** @return*/@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {return "男".equals(value) || "女".equals(value);}
}

创建message.proerties 可以使校验错误消息动态化

gender.message=性别只能为: 男,女

使用自定义注解

@Gender(message = "{gender.message}") 	//message = "{}" 占位符
private String gender;

全局异常处理

在Spring Boot中,使用@ControllerAdvice进行全局异常处理,可以捕获数据校验失败的情况,并生成统一格式的错误响应。

import org.springframework.http.HttpStatus;  
import org.springframework.http.ResponseEntity;  
import org.springframework.validation.BindException;  
import org.springframework.web.bind.annotation.ControllerAdvice;  
import org.springframework.web.bind.annotation.ExceptionHandler;  
import org.springframework.web.context.request.WebRequest;  
import org.springframework.web.bind.MethodArgumentNotValidException;  @ControllerAdvice  
public class GlobalExceptionHandler {  @ExceptionHandler(value = MethodArgumentNotValidException.class)public Result<> methodArgumentNotValidException(MethodArgumentNotValidException ex) {//1、result 中封装了所有错误信息BindingResult result = ex.getBindingResult();List<FieldError> errors = result.getFieldErrors();Map<String, String> map = new HashMap<>();for (FieldError error : errors) {String field = error.getField();String message = error.getDefaultMessage();map.put(field, message);}return Result.error(500, "参数错误", map);}
}

接口文档[knife4j]

接口文档是描述 API 接口的详细文档,通常用于沟通开发、测试和使用 API 的团队。一个好的接口文档不仅能够让开发人员明确接口的使用方法,还可以帮助使用者理解 API 的功能和预期行为。

Knife4j 使用,参考:https://doc.xiaominfo.com/docs/quick-start

swagger标准常用注解;

访问 http://ip:port/doc.html 即可查看接口文档

注解 标注位置 作用
@Tag controller 类 描述 controller 作用
@Parameter 参数 标识参数作用
@Parameters 参数 参数多重说明
@Schema model 层的 JavaBean 描述模型作用及每个属性
@Operation 方法 描述方法作用
@ApiResponse 方法 描述响应状态码等

添加依赖

<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.4.0</version>
</dependency>

创建application.yaml文件

# springdoc-openapi项目配置
springdoc:swagger-ui:path: /swagger-ui.htmltags-sorter: alphaoperations-sorter: alphaapi-docs:path: /v3/api-docsgroup-configs:- group: 'default'paths-to-match: '/**'packages-to-scan: com.chs.controller #规定扫描那个包下的所有controller并生成文档
# knife4j的增强配置,不需要增强可以不配
knife4j:enable: truesetting:language: zh_cn

使用OpenAPI3的规范注解,注释各个Spring的REST接口

@RestController
@Tag(name = "名")  //本controller类的介绍
public class BodyController {@Operation(summary = "普通body请求") //方法的介绍@PostMapping("/body")public ResponseEntity<> body(@RequestBody FileResp fileResp){return ResponseEntity.ok();}@Parameters({@Parameter(name = "id", description = "员工id", in = ParameterIn.PATH,required = true) //参数的介绍})@Operation(summary="按照id查询员工信息")@GetMapping("/employee/{id}")public R get(@PathVariable("id") Long id){//进行脱敏以后返回给前端return R.ok(respVo);}
}
@Schema(description = "员工修改提交的数据")  //属性的描述
@Data
public class EmployeeUpdateVo {@Schema(description = "员工id")@NotNull(message = "id不能为空")private Long id;
}

JavaBean也要分层,各种xxO

  • Pojo:普通java类
  • Dao:Database Access Object : 专门用来访问数据库的对象
  • DTO:Data Transfer Object: 专门用来传输数据的对象;
  • TO:transfer Object: 专门用来传输数据的对象;
  • BO:Business Object: 业务对象(Service),专门用来封装业务逻辑的对象;
  • VO:View/Value Object: 值对象,视图对象(专门用来封装前端数据的对象)

@JsonFormat:日期处理

@JsonFormat 是 Jackson 提供的一个注解,用于自定义序列化和反序列化 JSON 数据时的日期格式。通过使用这个注解,开发者可以指定日期字段在 JSON 中的表现形式,以满足特定的需求。

import com.fasterxml.jackson.annotation.JsonFormat;  
import java.util.Date;  public class User {  private String username;// shape: 指定数据的形状,通常使用 JsonFormat.Shape.STRING 来表示日期为字符串。// pattern: 指定日期的格式模式,例如 yyyy-MM-dd、MM/dd/yyyy 等。// timezone: 指定时区,默认为系统时区。可以使用 @JsonFormat(timezone = "GMT+8") 来设置为东八区。@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")  private Date birthDate;  // Getter 和 Setter 方法省略  
}

@JsonFormat 注解是处理日期格式化的重要工具,可以帮助开发者轻松地控制日期在 JSON 中的表现形式。

[源码]DispatcherServlet 请求处理流程

DispatcherServlet 是 Spring MVC 中不可或缺的组成部分,它的存在使得请求处理逻辑更加清晰和解耦,促进了控制器之间的协作,并通过灵活的配置支持为开发者提供了强大的功能和扩展性。

九大组件

MultipartResolver(*)

MultipartResolver 接口用于处理多部分请求(multipart requests),这通常是文件上传的场景。它负责解析上传的文件和其他表单数据,并将其转换为可处理的格式。

LocaleResolver

LocaleResolver 用于确定和存储与当前请求关联的地区信息(Locale)。

ThemeResolver

ThemeResolver 允许应用支持主题(theme)功能,主题可以改变界面的外观和样式。

List<HandlerMapping>(*)

HandlerMapping 是一个接口,用于将 HTTP 请求映射到处理该请求的处理器(Controller)上。DispatcherServlet 会包含一个或多个 HandlerMapping 的实现,以便根据请求的不同类型选择合适的控制器来处理请求。

List<HandlerAdapter>(*)

HandlerAdapter 是一个接口,用于支持调用不同类型的处理器(Controller)对象。因为处理器的实现可能不同,HandlerAdapter 提供了一个适配器模式,帮助 DispatcherServlet 调用这些处理器。通常,开发者可以根据需要实现自己的 HandlerAdapter。

List<HandlerExceptionResolver>(*)

HandlerExceptionResolver 接口用于处理控制器执行过程中抛出的异常。通过在 DispatcherServlet 中注册一个或多个实现,开发者可以定义全局的异常处理逻辑。

RequestToViewNameTranslator

RequestToViewNameTranslator 是一个接口,用于将请求信息转换为视图名称。它对于基于请求的视图解析器非常有用,可以帮助根据当前请求上下文动态生成视图名称。

FlashMapManager

FlashMapManager 用于管理 Flash 属性,这是一种在重定向请求之间传递临时属性的机制。Flash 属性通常用于传递消息(如成功或错误消息)到下一请求的视图中,一般适用于 POST/Redirect/GET 模式。

List<ViewResolver>

ViewResolver 是一个接口,用于将视图名称解析为具体的视图对象。

运行流程图

mvc

运行流程图【简】

image-20240906095002435

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

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

相关文章

【泛微E9】查询部门的部门层级以及所有上级部门

效果图如下:field1:一级部门 field2:二级部门 field3:三级部门 field4:四级部门 field5:五级部门 field6:六级部门 创建视图,view_bmcjpath 视图定义如下: WITH RECURSIVE department_tree (id, DEPARTMENTMARK, supdepid, depth, path) AS ( -- 初始化查询(非递归部…

Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载

Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载 基于 ARM 的 Windows 10 请访问原文链接:https://sysin.org/blog/windows-10-arm/,查看最新版…

macOS 15 Blank OVF - macOS Sequoia 虚拟化解决方案

macOS 15 Blank OVF - macOS Sequoia 虚拟化解决方案macOS 15 Blank OVF - macOS Sequoia 虚拟化解决方案 适用于 VMware ESXi 和 VMware Workstation 的 macOS Sequoia 虚拟化模板 请访问原文链接:https://sysin.org/blog/macos-15-ovf/,查看最新版。原创作品,转载请保留出…

ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发

上一篇我们分析了argo-workflow 中的 archive,包括 流水线GC、流水线归档、日志归档等功能。本篇主要分析 Workflow 中的几种触发方式,包括手动触发、定时触发、Event 事件触发等。1. 概述 Argo Workflows 的流水线有多种触发方式:手动触发:手动提交一个 Workflow,就会触发…

从零开始学机器学习——了解回归

在本文中,我们探讨了回归分析在统计学和数据分析中的重要性和应用。线性回归和逻辑回归作为两种主要的回归分析方法,分别适用于不同类型的数据建模和预测需求。通过数学建模,它们能够揭示变量之间的关系,并且在实际应用中展现了强大的预测能力。首先给大家介绍一个很好用的…

智慧工地安全帽智能识别系统

智慧工地安全帽智能识别系统通过opencv深度学习技术,智慧工地安全帽智能识别系统可自动检测识别作业现场人员有没有戴安全帽,当智慧工地安全帽智能识别系统检测出现场施工作业人员没有按照要求戴安全帽时,立即抓拍存档并同步回传违规数据到后台监控大数据平台,并提醒后台人…

煤矿人员工服着装智能识别监测系统

煤矿人员工服着装智能识别监测系统在摄像头监控画面中自动检测作业人员是否正确着装,煤矿人员工服着装智能识别监测系统若发现有现场作业人员没有按要求正确佩戴安全帽、穿着工服,煤矿人员工服着装智能识别监测系统会立即抓拍存档回传后台大数据监控管理平台,现场同时进行语…

Ai检测人员穿衣规范系统

Ai检测人员穿衣规范系统可以通过yolo深度学习技术对现场画面中人员穿衣自动检测,Ai检测人员穿衣规范系统发现现场作业人员未正确按要求穿衣服如穿戴安全带、手套、安全帽、胶鞋、反光衣,Ai检测人员穿衣规范系统会进行语音播报提醒,并将违规行为截图回传给后台大数据平台进行…