文章目录
- [SpringMVC 概念和基本使用](#SpringMVC 概念和基本使用)
-
- [SpringMVC 简介](#SpringMVC 简介)
- [SpringMVC 核心组件和调用流程](#SpringMVC 核心组件和调用流程)
- [SpringMVC 基本使用](#SpringMVC 基本使用)
-
- 第一步:导入依赖
- [第二步:Controller 层开发](#第二步:Controller 层开发)
- [第三步:SpringMVC 配置类配置核心组件](#第三步:SpringMVC 配置类配置核心组件)
- [第四步:SpringMVC 环境搭建](#第四步:SpringMVC 环境搭建)
- [第五步:部署 Tomcat 并运行测试](#第五步:部署 Tomcat 并运行测试)
- [SpringMVC 访问路径设置](#SpringMVC 访问路径设置)
- [SpringMVC 接收参数](#SpringMVC 接收参数)
- [原生 API 参数调用](#原生 API 参数调用)
-
- 共享域对象操作
-
- [request 级别共享域](#request 级别共享域)
- [session 级别共享域](#session 级别共享域)
- [Application 级别共享地域](#Application 级别共享地域)
- [SpringMVC 响应数据](#SpringMVC 响应数据)
- [RESTFul 风格设计规范](#RESTFul 风格设计规范)
- [SpringMVC 声明式全局异常处理](#SpringMVC 声明式全局异常处理)
- [SpringMVC 拦截器](#SpringMVC 拦截器)
- [SpringMVC 参数校验](#SpringMVC 参数校验)
SpringMVC 概念和基本使用
SpringMVC 简介
Spring Web MVC
是基于Servlet API
构建的原始Web框架,从一开始就包含在Spring Framework
中。正式名称"Spring Web MVC"
来自其源模块的名称(spring-webmvc
),但它通常被称为"Spring MVC"。
SpringMVC 核心组件和调用流程
调用 Controller
层方法
请求映射查找
- 通过
DispatcherServlet
把request
对象传递给HandlerMapping
。然后HandlerMapping
基于这个request
对象中的请求路径等信息。从其缓存中查找匹配的hander(Controller 方法)
,对应路径等内容。然后把匹配到的信息返回给DispatcherServlet
.
请求处理与响应
DispatcherServlet
拿到匹配的信息后。会来到HandlerAdpter
,并将request
和response
传递过来。HandlerAdpter
借助request
进行请求参数绑定。然后才执行handler(Controller 方法)
。返回的数据封装到response
中让HandlerAdapter
处理。然后response
返回给客户端.
动态视图资源处理
handler(Controller 方法)
返回的是一个字符串就到视图解析器。通过视图解析器前缀和后缀拼接访问视图
SpringMVC 基本使用
第一步:导入依赖
xml
<properties>
<spring.version>6.0.6</spring.version>
<servlet.api>9.1.0</servlet.api>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- springioc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- web相关依赖 -->
<!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
<!--
在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用
Jakarta EE 提供的相应类库和命名空间。错误信息 "'org.springframework.web.servlet.DispatcherServlet'
is not assignable to 'javax.servlet.Servlet,jakarta.servlet.Servlet'" 表明你使用了旧版本的
Servlet API,没有更新到 Jakarta EE 规范。
-->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>${servlet.api}</version>
<scope>provided</scope>
</dependency>
<!-- springwebmvc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
第二步:Controller 层开发
@RequestMapping
:对外访问的地址@ResponseBody
:告诉Spring框架,被注解的方法的返回值应该直接写入HTTP响应体(而不是视图名称),并且不需要进行视图解析。
java
public class HelloController {
@RequestMapping("/springmvc/hello")
@ResponseBody
public String hello() {
System.out.println("Hello springmvc");
return "hello springmvc";
}
}
第三步:SpringMVC 配置类配置核心组件
需要把
HandlerAdapter
和HandlerMapping
放入 IOC 容器 并扫描组件。其实用@EnableWebMvc
也可以自动配置。
java
@Configuration
@ComponentScan(basePackages = "com.mangfu.controller")
public class SpringMvcConfig {
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
return new RequestMappingHandlerAdapter();
}
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
}
第四步:SpringMVC 环境搭建
作用:可以被 web 项目加载,会初始化 ioc 容器, 会设置 dispatcherServlet 的地址
java
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
//指定 springmvc 的配置类。会自动加载配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//设置dispatcherServlet的处理路径!
//一般情况下为 / 代表处理所有请求!
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
第五步:部署 Tomcat 并运行测试
SpringMVC 访问路径设置
路径注解 @RequestMapping
方法级别
直接在方法上加
@RequestMapping
。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 @RequestMapping 注解进行更精细的映射。
java
@Controller
public class UserController {
//这里路径就是 /index 访问就会执行这个方法
@RequestMapping("/index")
public String index() {
return null;
}
}
类级别
在类上加
@RequestMapping
,方法上还要再设置一次@RequestMapping
设置了ReqquestMapping
的参数那就自动加上 类级别的地址当前缀,如果没有参数就直接用 类地址地址
java
@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 中封装到了下面这个枚举类:
java
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
java
@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
java
@RequestMapping(value="/login",method=RequestMethod.GET)
就等于
@GetMapping(value="/login")
精确路径匹配
SpringMVC
中@RequestMapping
的/
可以省略.
/
就表示绝对路径 ,Tomcat 中表示http://localhost:8080/
,设置Tomcat
上下文路径为/
缺省就可以直接使用/
+地址
访问对应的Servlet
java
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login() {
return null;
}
模糊路径匹配
- 在
@RequestMapping
注解指定URL
地址时,通过使用通配符,匹配多个类似的地址。*
表示任意单层:/user/*
这种就是一层可以访问,/user/a/b
这就是两层访问不了**
表示任意层 :/user/**
,比如:/user/a
,/user/a/b
都可以
java
@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!";
}
}
SpringMVC 接收参数
param 和 json 参数比较
在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比:
param
:key = value & key = value
json
:{ key : value, key : value }
.
- 参数编码:
param 类型的参数会被编码为 ASCII 码。例如,假设name=john doe
,则会被编码为name=john%20doe
。而 JSON 类型的参数会被编码为 UTF-8。
.- 参数顺序 :
param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。
.- 数据类型 :
param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。- 嵌套性 :
param 类型的参数不支持嵌套。但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。
.- 可读性 :
param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。
.总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。在实际开发中,常见的做法是:在
GET
请求中采用param
类型的参数,而在POST
请求中采用JSON
类型的参数传递。
返回对象就是 json
SpringMVC 各种数据接收
简单类型接值
-
客户端请求
-
Hander 接收参数
- 只要形参参数名和类型与传递的参数相同,即可自动接收!
- 也可以不传递参数
java
@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 接收参数
java
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;
}
}
数组类型接收
param 的 key 和 数组参数名一致就会接收
- 客户端请求
- handler 接收参数
java
@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 接收参数
java
@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
java
@Data
public class User {
private int age;
private String name;
private Address address;
}
java
@Data
public class Address {
private String province;
private String city;
}
- 客户端
- handler
java
@Controller
@RequestMapping("user")
public class testcontroller {
@ResponseBody
@RequestMapping
public String test1(User user) {
System.out.println(user);
return user.toString();
}
}
日期参数接收
param 的 key 要和 日期类型的参数名一样。并且用
@DateTimeFormat
指定格式
- 客户端
- handler
java
@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
java
@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;
}
}
JSON 参数接收
前端发送 JSON 数据时,
Spring MVC
框架可通过@RequestBody
注解将其转为 Java 对象。此注解表示方法参数值从请求体获取,Springboot
中直接添加该注解就行,无需额外操作,且不用指定 value 属性,它会自动映射到相应的参数上。
- 第一步:导入 jackson 依赖
xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
- 第二步:在配置类上添加
@EnableWebMvc
注解
- 不做一二步会报 415 错误,因为原生
java api
不支持JSON
- 这一步会自动加上
RequestMappingHandlerMapping
和RequestMappingHandlerAdapter
到ioc
容器 并且给RequestMappingHandlerAdapter
配置json
转换器
java
@EnableWebMvc //给 handlerAdapter 配置了 json 转化器
@Configuration
@ComponentScan("com.atguigu.json")
public class MvcConfig {
}
- 第三步:给前端来的JSON存储的 POJO 类
java
@Data
public class Person {
private String name;
private int age;
private String gender;
}
- 第四步:接收 JSON
java
@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
的值绑定到的方法参数。
java
@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")
java
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
...
}
- 第二种:根据形参名自动匹配
java
//获取 token
//这种是根据根据参数名自动匹配
@GetMapping("/demo")
public void handle(@RequestHeader String token) {
}
原生 API 参数调用
直接在形参传就行
java
/**
* 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
* 注意: 接收原生对象,并不影响参数接收!
*/
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,
HttpServletResponse response){
String method = request.getMethod();
System.out.println("method = " + method);
return "api";
}
共享域对象操作
1.
ServletContext
共享域:ServletContext
对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在ServletContext
中保存的数据是线程安全的。.
2.HttpSession
共享域:HttpSession
对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在HttpSession
中,让用户在多个页面间保持登录状态。.
3.HttpServletRequest
共享域:HttpServletRequest
对象可以在同一个请求的多个handler
方法之间共享数据。比如,可以将请求的参数和属性存储在HttpServletRequest
中,让处理器方法之间可以访问这些数据。
API | 功能 |
---|---|
void setAttribute(String name,String value) |
向域对象中添加/修改数据 |
Object getAttribute(String name); |
从域对象中获取数据 |
removeAttribute(String name); |
移除域对象中的数据 |
request 级别共享域
java
@RequestMapping("/attr/request/original")
@ResponseBody
// 拿到原生对象,就可以调用原生方法执行各种操作
public String testAttrOriginalRequest(HttpServletRequest request) {
request.setAttribute("requestScopeMessageOriginal", "i am very happy[original]");
return "target";
}
session 级别共享域
- 获取 Session 对象:
HttpSession session = req.getSession();
- 获取 Session 的 ID:
String jSessionId = session.getId();
- 判断session是不是新创建的session:
boolean isNew = session.isNew();
- 向session对象中存入数据:
session.setAttribute("username",username);
java
@RequestMapping("/attr/session")
@ResponseBody
public String testAttrSession(HttpSession session) {
//直接对session对象操作,即对会话范围操作!
return "target";
}
Application 级别共享地域
springmvc 会在初始化容器的时候,将 servletContext对象存储到 ioc 容器中!
java
/自动装配
@Autowired
private ServletContext servletContext;
@RequestMapping("/attr/application")
@ResponseBody
public String attrApplication() {
servletContext.setAttribute("appScopeMsg", "i am hungry...");
return "target";
}
java
//使用 request 或者 seesion 获取
public void data(HttpServletResponse response, HttpServletRequest request, HttpSession session) {
//正常使用原生对象就可以
ServletContext servletContext = request.getServletContext();
ServletContext servletContext1 = session.getServletContext();
}
SpringMVC 响应数据
开发模式介绍
- 前后端不分离: controller 通过共享域对象,让动态页面从共享域取数据然后返回装配好的 html 文件。然后把 html 文件返回给浏览器
页面跳转
快速返回模板视图
- 第一步:导入 jsp 依赖
xml
<!-- jsp需要依赖! jstl-->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
- 第二步:创建 jsp 页面
建议位置:/WEB-INF/下,避免外部直接访问!
位置:/WEB-INF/views/home.jsp
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!-- 可以获取共享域的数据,动态展示! jsp== 后台vue -->
${msg}
</body>
</html>
- 第三步:配置 jsp 视图解析器
java
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//registry 可以快速添加前后缀
registry.jsp("/WEB-INF/views/",".jsp");
}
}
- 第五步:handler 返回视图
java
@Controller
@RequestMapping("jsp")
public class JspController {
/**
* 快速查找视图
* 1.方法的返回值是字符串类型
* 2.不能添加 @ResponseBody
* 3.通过返回值拼接视图名称
* @return
*/
@GetMapping("index")
public String index(HttpServletRequest request) {
//request 数据发送给 jsp
request.setAttribute("data", "hello");
//和前后缀拼接
return "index";
}
}
请求转发和响应重定向
- 将方法的返回值,设置String类型
- 转发使用
forward
关键字,重定向使用redirect
关键字- 总之就是
fordwrad/redirect: /+路径
- 注意:如果是项目下的资源,转发和重定向都一样都是项目下路径!都不需要添加项目根路径!
请求转发
- 客户端只发送了一次请求, 客户端地址栏不变
- 请求转发可以转发给
WEB-INF
下受保护的资源- 请求转发可以转发给其他
Servlet
动态资源,也可以转发给一些静态资源以实现页面跳转
java
@Controller
@RequestMapping("jsp")
public class JspController {
@GetMapping("index")
public String index(HttpServletRequest request) {
//request 数据发送给 jsp
request.setAttribute("data", "hello");
//和前后缀拼接
return "index";
}
/**
* 转发: 只能是项目下的资源
* 1.方法的返回值写成字符串
* 2.不能添加 responseBody 注解
* 3. 返回的字符串前 forward: /转发地址
*
*/
@GetMapping("forward")
public String forward() {
/*
/ 表示 http://localhost:8080/
jsp/index 表示要转发的地址
*/
return "forward:/jsp/index"
}
}
响应重定向
- 客户端至少发送了两次请求, 客户端地址栏是要变化的
- 重定向可以是其他
Servlet
动态资源,也可以是一些静态资源以实现页面跳转- 重定向不可以到给
WEB-INF
下受保护的资源
java
@Controller
@RequestMapping("jsp")
public class JspController {
@GetMapping("index")
public String index(HttpServletRequest request) {
//request 数据发送给 jsp
request.setAttribute("data", "hello");
//和前后缀拼接
return "index";
}
/**
* 转发: 只能是项目下的资源
* 1.方法的返回值写成字符串
* 2.不能添加 responseBody 注解
* 3. 返回的字符串前 forward: /转发地址
*
*/
@GetMapping("redirect")
public String redirect() {
/*
/ 表示 http://localhost:8080/
jsp/index 表示要重定向的地址
*/
return "redirect:/jsp/index"
}
}
返回 JSON 数据
基本使用
- 导入 json 依赖
xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
- 添加
@EnableWebMvc
注解
java
//TODO: SpringMVC对应组件的配置类 [声明SpringMVC需要的组件信息]
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器。还会自动导入 handlerMapping 和 handlerAdapter
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
}
- 创建 pojo
java
@Data
public class User {
private String name;
private int age;
}
- 响应数据
java
@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 格式的数据,直接发给客户端。这意味着返回值 不会走视图解析器渲染这一步,而是 直接作为数据响应。]
java
@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 注解,那么这些注解就可以提取到类上。
java
@ResponseBody //responseBody可以添加到类上,代表默认类中的所有方法都生效!
@Controller
@RequestMapping("param")
public class ParamController {
@RestController
注解
类上的
@RestponseBody
注解可以和@Controller
注解合并为@RestController
注解。所以使用了@RestController
注解就相当于给类中的每个方法都加了@ResponseBody
注解。
返回静态资源
资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。典型的静态资源包括:
- 纯HTML文件
- 图片
- CSS文件
- JavaScript文件
- ......
-
在 web 应用中加入静态资源
-
手动构建确保编译
-
改造配置类
重写
configureDefaultServletHandling
开启经他i资源处理
java
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
//这里是前缀和后缀。等下会把 controller 中发过来的字符串进行拼接访问
registry.jsp("/WEB-INF/views/",".jsp");
}
//开启静态资源处理 <mvc:default-servlet-handler/>
//dispatcherServlet -> handlerMMapping 找有没有对应的 handler(controller) -> 没有再去找 -> 有没有静态资源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
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=2 |
URL 地址:/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 /user | page=1&size=10 | { 响应数据 } |
用户添加 | POST /user | { user 数据 } | {响应数据} |
用户详情 | GET /user/1 | 路径参数 | {响应数据} |
用户更新 | PUT /user | { user 更新数据} | {响应数据} |
用户删除 | DELETE /user/1 | 路径参数 | {响应数据} |
条件模糊 | GET /user/search | page=1&size=10&keywork=关键字 | {响应数据} |
- 用户 pojo
java
package com.atguigu.pojo;
/**
* projectName: com.atguigu.pojo
* 用户实体类
*/
@Data
public class User {
private Integer id;
private String name;
private Integer age;
}
- controller
java
/**
* 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'}";
}
}
SpringMVC 声明式全局异常处理
单独写一个异常类。发生异常就会走此类下的 handler 方法
ControllerAdvice
: 走字符串拼接网址那一套。前后端不分离的RestControllerAdvice
:直接返回数据。不拼接。前后端分离
只要发生异常就进入这里寻找对应的异常处理,并且要注意在配置类扫描这个全局异常类
java
//全局异常发生会走此类下的 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;
}
}
java
<!-- 扫描controller对应的包,将handler加入到ioc-->
@ComponentScan(basePackages = {"com.atguigu.controller",
"com.atguigu.exceptionhandler"})
SpringMVC 拦截器
拦截器方法拦截位置
拦截器使用
就是自定义一个拦截器类实现
HandlerInterceptor
,然后配置类实现WebMvcConfigurer
扫描拦截器类。配置拦截路径
- 实现
HandlerInterceptor
实现其所有方法
- 配置类实现
WebMvcConfigurer
添加拦截器
java
@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() 方法。
SpringMVC 参数校验
为什么使用参数校验
本来是要在 Service 写校验的。现在我们直接在 pojo 类加校验注解就行
校验注解详解
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
注解 | 规则 |
---|---|
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
配置
@EnableWebMvc
后,SpringMVC 会默认装配好一个LocalValidatorFactoryBean
,通过在处理方法的入参上标注@Validated
注解即可让SpringMVC
在完成数据绑定后执行数据校验的工作。
参数校验使用
- 第一步:导入依赖
xml
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
- POJO类
java
public class User {
@NotNull
private String name;
@Email
private String email;
@Size(min = 6, max = 20)
private String password;
// 省略 getter 和 setter
}
- Controller 类
java
@RestController
public class UserController {
//User 类用 JSR 303 注解校验属性。
//UserController 的 addUser 方法加 @Validated 触发 User 对象校验,请求 /user 时,不符规则会返回错误信息。
@PostMapping("/user")
public String addUser(@Validated @RequestBody User user) {
return "User added successfully";
}
}
易混淆注解总结
@NotNull:
- 适用于包装类型,若被标注字段的值为 null,校验失败并抛出异常。
- 不能用于字符串类型的校验。
.@NotEmpty:
- 可用于 CharSequence、Collection、Map 或数组对象类型的属性。对于其他类型的属性该注解无效
- 校验规则:若属性为 null 或 size() 为 0,则校验失败。
- 对于仅包含空格的字符串(中间有空格也算),不会认为是空字符串,校验不会失败。
.@NotBlank:
- 专门用于字符串类型属性的校验。
- 校验规则:若属性为 null、空字符串 "" 或仅含空格,则校验失败。
.在数据校验时,要根据具体情况选用合适的注解:
- 对包装类型且不能为 null 的,使用
@NotNull
。- 对于 CharSequence、Collection、Map 或数组,不能为 null 且 size() 不为 0 的,使用
@NotEmpty
。- 对于字符串不能为 null、空串或仅含空格的,使用
@NotBlank
。