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";
}
主要作用
SSM框架构建起单体项目的技术栈需求!其中的SpringMVC负责表述层(控制层)实现简化!
SpringMVC的作用主要覆盖的是表述层,例如:
- 请求映射
- 数据输入
- 视图界面
- 请求分发
- 表单回显
- 会话控制
- 过滤拦截
- 异步交互
- 文件上传
- 文件下载
- 数据校验
- 类型转换
- 等等等
最终总结:
- 简化前端参数接收( 形参列表 )
- 简化后端数据响应(返回值)
- 以及其他......
核心组件和调用流程理解
Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet
DispatcherServlet
做整体请求处理调度!
除了DispatcherServlet
SpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。
SpringMVC处理请求流程:
SpringMVC涉及组件理解:
- DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
- HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
- HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]
- Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
- 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";}
}
测试
路径映射 - @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
注解可以用于类级别和方法级别,它们之间的区别如下:
- 设置到类级别:
@RequestMapping
注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。 - 设置到方法级别:
@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请求
-
请求行:包含请求方法(如GET、POST)、请求的URL和HTTP版本。例如:
GET /index.html HTTP/1.1
-
请求头:包含关于请求的元数据,例如:
Host
: 请求的主机名User-Agent
: 客户端软件的信息Accept
: 客户端可接受的内容类型
-
请求体(可选):在某些请求方法(如POST)中,可能包含要发送到服务器的数据。
HTTP响应
-
状态行:包含HTTP版本、状态码和状态消息。例如:
HTTP/1.1 200 OK
-
响应头:包含关于响应的元数据,例如:
Content-Type
: 响应内容的类型Content-Length
: 响应体的长度Set-Cookie
: 设置cookie
-
响应体:包含实际返回的数据,例如HTML文档、JSON数据等。
URL 携带大量数据,特别是GET请求,会把参数放在URL上
请求体 携带大量数据,特别是POST请求,会把参数放在请求体中
JSON 数据格式
JavaScript Object Notation(JavaScript 对象表示法)
JSON用于将结构化数据表示为 JavaScript 对象的标准格式,通常用于在网站上表示和传输数据
JSON 可以作为一个对象或者字符串存在
前者用于解读 JSON 中的数据,后者用于通过网络传输 JSON 数据。
JavaScript 提供一个全局的 可访问的 JSON 对象来对这两种数据进行转换。
JSON 是一种纯数据格式,它只包含属性,没有方法。
备注:将字符串转换为原生对象称为反序列化(deserialization),
而将原生对象转换为可以通过网络传输的字符串称为序列化(serialization)。
JSON数据格式图和访问方式
数据收集请求处理
直接接值
@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";
}
数据收集响应处理
@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);
}
企业响应数据方式
为了统一响应格式,企业通常会定义一个通用的响应类。这种响应类包含状态码、消息和数据等字段。
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风格访问资源时使用行动动作区分对资源进行了何种操作
@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.com
和sub.example.com
),也会引起跨域。- 不同的端口:即使协议和域名相同,如果端口不同(如
example.com:80
和example.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 执行
拦截器 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指定小数精度 |
验证字符串是否为邮箱地址格式 | |
@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
是一个接口,用于将视图名称解析为具体的视图对象。
运行流程图
运行流程图【简】