spring mvc详细讲解(前后端分离模式)
在前后端分离模式下,Spring MVC 的作用主要集中在处理后端的业务逻辑和 API 接口,而不再直接管理视图部分。也就是说,Spring MVC 的重点是如何处理客户端的请求并返回数据(通常以 JSON 或 XML 格式),而视图渲染交给前端框架(如 Vue.js、React 等)来完成。
下面是针对前后端分离模式的 Spring MVC 详细讲解。
1、Spring MVC 工作流程
在前后端分离的模式下,Spring MVC 的工作流程仍然包含以下步骤,但其核心作用是处理客户端发送的请求并返回数据,而不再渲染视图。
- 客户端发送请求:前端应用(如 Vue.js 或 React)通过 AJAX 或 Axios 向后端发送 HTTP 请求,通常是 RESTful API 请求。
- DispatcherServlet 拦截请求:
DispatcherServlet
作为 Spring MVC 的前端控制器,拦截所有的 HTTP 请求,并将请求转发给合适的处理器(Controller)。 - 处理请求:Controller 根据请求路径和请求参数,调用业务逻辑处理或数据库操作,生成相应的响应数据。
- 返回 JSON 或 XML 数据:Controller 将处理结果作为 JSON 或 XML 格式的数据返回给客户端。前端应用再根据返回的数据进行页面更新或其他操作。
2、核心组件
在前后端分离的模式中,Spring MVC 的核心组件有以下几个:
2.1 Controller(控制器)
控制器主要负责接收前端的请求,并返回数据。通常,控制器中的方法会通过 @RestController
注解来简化开发,它表示这个控制器只会返回数据而不是视图。
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
// 模拟从数据库获取用户
User user = new User(id, "John", 25);
return user; // 返回 JSON 数据
}
@PostMapping("/create")
public User createUser(@RequestBody User user) {
// 保存用户逻辑
return user; // 返回保存后的用户信息
}
}
在这个例子中,UserController
通过 RESTful API 向前端返回用户数据,而不是返回 HTML 页面。
2.2 @RestController 注解
@RestController
是 @Controller
和 @ResponseBody
的组合注解,简化了返回 JSON、XML 等格式的开发流程。每个方法返回的数据会自动序列化为客户端期望的格式。
2.3 RequestMapping 注解
@RequestMapping
或简化的 @GetMapping
、@PostMapping
用于定义接口的 URL 路径,并指定 HTTP 方法类型(GET、POST、PUT、DELETE 等)。它们用来路由前端请求到具体的控制器方法。
2.4 @RequestBody 和 @ResponseBody
@RequestBody
:用于从请求体中提取 JSON 或 XML 格式的数据并映射到方法参数上。前端通常发送 JSON 格式的数据,Spring MVC 会自动将其转换为 Java 对象。@ResponseBody
:用于将方法的返回值自动序列化为 JSON 或 XML 格式返回给客户端。方法的返回值会直接作为 HTTP 响应体返回,而不是视图名称。
3、RequestMapping
@RequestMapping
是 Spring MVC 中的核心注解之一,用于将 HTTP 请求映射到处理器方法(通常是控制器类中的方法)。在前后端分离模式下,@RequestMapping
和相关的注解主要用于构建 RESTful API,确保前端能通过 HTTP 请求与后端进行交互。@RequestMapping
可以精确地映射 HTTP 请求的 URL、请求方法、请求参数等,以便后端能够根据前端的请求路径和请求类型执行相应的操作。
1. @RequestMapping
注解的基本使用
@RequestMapping
可以用于类和方法上,常见的组合是将它用在控制器类上以指定公共路径,方法上用来指定更具体的路径和请求操作。
类级别的 @RequestMapping
类级别的 @RequestMapping
用于定义公共的 URL 路径前缀,所有在此类中的方法共享这个前缀。
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
// 所有路径都会以 "/api/users" 为前缀
}
方法级别的 @RequestMapping
方法级别的 @RequestMapping
用于指定具体的请求路径和操作,可以与类级别的路径组合形成完整的 URL。
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
@RequestMapping("/list")
public List<User> getAllUsers() {
// 处理 "/api/users/list" 请求
return userService.getAllUsers();
}
@RequestMapping("/details/{id}")
public User getUserById(@PathVariable Long id) {
// 处理 "/api/users/details/{id}" 请求
return userService.getUserById(id);
}
}
2. @RequestMapping
的常用属性
@RequestMapping
提供了多个属性用于更加精确地匹配 HTTP 请求:
2.1 value
属性
value
属性指定请求的路径,可以是一个字符串数组,表示允许多个 URL 访问同一个处理器方法。
@RequestMapping(value = {"/list", "/all"})
public List<User> getAllUsers() {
// "/list" 或 "/all" 都会被映射到这个方法
return userService.getAllUsers();
}
2.2 method
属性
method
属性用于指定支持的 HTTP 请求方法,例如 GET
、POST
、PUT
、DELETE
等。
@RequestMapping(value = "/create", method = RequestMethod.POST)
public User createUser(@RequestBody User user) {
// 只处理 POST 请求
return userService.saveUser(user);
}
如果你希望更简洁地定义请求方法,可以使用特定的注解替代 @RequestMapping
,如 @GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
。
2.3 params
属性
params
属性可以用于限制请求参数的存在与否。例如,如果你希望方法仅在某个特定参数存在时被调用,可以使用 params
属性。
http://example.com/login?username=yourUsername&password=yourPassword
@RequestMapping(value = "/search", params = {"username", "password"})
public List<User> searchUsersByName(@RequestParam String username, @RequestParam String password) {
// 只有请求中包含 username 参数时,这个方法才会被执行
return userService.searchByName(username);
}
2.4 headers
属性
headers
属性用于根据 HTTP 请求头匹配请求。这个属性可以用来在某些特定的请求头存在时执行某个处理方法。
@RequestMapping(value = "/info", headers = "Accept=application/json")
public User getUserInfo() {
// 仅在 Accept 头为 "application/json" 时处理请求
return userService.getUserInfo();
}
2.5 consumes
属性
consumes
属性用于指定请求的 Content-Type
类型。例如,如果你希望处理的请求体是 JSON 格式,可以这样指定:
@RequestMapping(value = "/create", method = RequestMethod.POST, consumes = "application/json")
public User createUser(@RequestBody User user) {
// 只接受 Content-Type 为 "application/json" 的请求
return userService.saveUser(user);
}
2.6 produces
属性
produces
属性用于指定返回的响应类型(Content-Type
)。例如,如果你希望返回 JSON 格式的响应,可以这样定义:
@RequestMapping(value = "/user/{id}", produces = "application/json")
public User getUserById(@PathVariable Long id) {
// 返回 JSON 格式的数据
return userService.getUserById(id);
}
2.7 path
属性
path
是 value
的别名,用于定义请求的 URL 路径。如果需要更具语义化地定义路径,可以使用 path
属性。
@RequestMapping(path = "/details/{id}")
public User getUserDetails(@PathVariable Long id) {
return userService.getUserDetails(id);
}
3. 组合使用
@RequestMapping
可以组合使用多个属性来实现更加复杂的请求匹配。例如:
@RequestMapping(
value = "/update",
method = RequestMethod.PUT,
consumes = "application/json",
produces = "application/json"
)
public User updateUser(@RequestBody User user) { // @RequestBody 注解用于将请求体中的 JSON 数据转换为 User 对象。
// 只处理 PUT 请求,且请求体和响应体都是 JSON 格式
return userService.updateUser(user);
}
4. 常见注解简化形式
为了简化常见的 HTTP 操作,Spring 提供了几个注解作为 @RequestMapping
的简化形式:
@GetMapping
:用于处理 GET 请求@PostMapping
:用于处理 POST 请求@PutMapping
:用于处理 PUT 请求@DeleteMapping
:用于处理 DELETE 请求@PatchMapping
:用于处理 PATCH 请求
这些注解都相当于 @RequestMapping
的简化版,直接指定了请求方法类型。
示例:
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping("/user/create")
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
4、路径变量和请求参数
4.1 @PathVariable
@PathVariable
注解用于从 URL 的路径中提取数据,并将这些数据绑定到控制器方法的参数上。它通常与 URL 模板中的占位符 {}
一起使用。
1. 基本用法
1.1 单一路径变量
在控制器方法中,可以使用 @PathVariable
注解来提取单个路径变量。
示例:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable("id") String id) {
return "User ID: " + id;
}
}
- 请求 URL:
/users/123
id
变量将被设置为123
,结果:User ID: 123
1.2 多个路径变量
可以在 URL 模板中使用多个路径变量,并在方法中提取这些变量。
示例:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}/details/{detailId}")
public String getUserDetails(@PathVariable("id") String id,
@PathVariable("detailId") String detailId) {
return "User ID: " + id + ", Detail ID: " + detailId;
}
}
- 请求 URL:
/users/123/details/456
id
变量将被设置为123
,detailId
变量将被设置为456
,结果:User ID: 123, Detail ID: 456
2. URL 模板中的占位符
2.1 单层路径变量
路径变量用于 URL 模板中的单层路径。
示例:
@GetMapping("/products/{productId}")
public String getProduct(@PathVariable("productId") String productId) {
return "Product ID: " + productId;
}
- URL:
/products/789
productId
变量将被设置为789
2.2 多层路径变量
路径变量可以用在 URL 的多层路径中,提取不同层次的数据。
示例:
@GetMapping("/categories/{categoryId}/items/{itemId}")
public String getItem(@PathVariable("categoryId") String categoryId,
@PathVariable("itemId") String itemId) {
return "Category ID: " + categoryId + ", Item ID: " + itemId;
}
- URL:
/categories/1/items/42
categoryId
变量将被设置为1
,itemId
变量将被设置为42
3. 路径变量的名称匹配
@PathVariable
的名称需要与 URL 模板中的占位符名称匹配。如果不匹配,会导致绑定失败。
示例:
@GetMapping("/items/{itemId}")
public String getItem(@PathVariable("itemId") String id) {
// 此处 id 是 itemId 的别名,实际名称应与 URL 模板一致
return "Item ID: " + id;
}
4.2 @RequestParam
@RequestParam
是 Spring MVC 中用于处理 HTTP 请求参数的注解。它用于从 HTTP 请求的查询字符串或表单参数中提取数据。它可以用于处理 GET
、POST
、PUT
、DELETE
等请求方法中的参数。
补充:
查询字符串 是附加在 URL 末尾的键值对,用于向服务器传递参数。它通常用于 GET
请求,也可以用于其他请求类型。查询字符串以 ?
开始,后面跟随参数键值对,用 &
分隔多个参数。
查询字符串的格式为:
http://example.com/resource?key1=value1&key2=value2
表单参数 是通过 HTML 表单提交的键值对,通常在 POST
请求中使用。表单参数可以通过 application/x-www-form-urlencoded
或 multipart/form-data
编码发送,具体取决于表单的内容类型。
表单数据的格式在 application/x-www-form-urlencoded
格式中,表单数据类似于查询字符串:
key1=value1&key2=value2
示例
HTML 表单:
<form action="/register" method="post">
<input type="text" name="username" value="alice">
<input type="number" name="age" value="30">
<button type="submit">Register</button>
</form>
-
username
的值是alice
。 -
age
的值是30
。 -
表单提交的数据:
username=alice&age=30
-
返回结果:
Registered user: alice, Age: 30
总结
- 查询字符串 是在 URL 中附加的参数,用于
GET
请求。 - 表单参数 是通过 HTML 表单提交的参数,通常用于
POST
请求。 - 在 Spring MVC 中,
@RequestParam
注解可以用于提取这两种类型的请求参数。
1. 基本用法
1.1 从查询字符串中获取参数(GET 请求)
当使用 GET
请求时,参数通常附加在 URL 的查询字符串中。
示例:
@RestController
public class UserController {
@GetMapping("/greet")
public String greet(@RequestParam("name") String name) {
return "Hello, " + name + "!";
}
}
- 请求 URL:
/greet?name=Alice
- 结果:
Hello, Alice!
1.2 从表单数据中获取参数(POST 请求)
当使用 POST
请求时,参数通常是表单数据的一部分。
示例:
@RestController
public class UserController {
@PostMapping("/register")
public String register(@RequestParam("username") String username,
@RequestParam("password") String password) {
return "Registered user: " + username;
}
}
- 表单数据:
<form action="/register" method="post"> <input type="text" name="username"> <input type="password" name="password"> <button type="submit">Register</button> </form>
2. @RequestParam
的属性
2.1 value
属性
value
(或 name
)属性指定请求参数的名称。
示例:
@RequestParam(value = "name") String name
// 或
@RequestParam("name") String name
2.2 required
属性
required
属性用于指示参数是否是必需的。默认为 true
,如果请求中没有提供该参数,Spring 将抛出异常。如果设置为 false
,则可以省略该参数。
示例:
@RequestParam(name = "name", required = false) String name
- 如果请求中不包含
name
参数,name
将被设置为null
,而不是抛出异常。
2.3 defaultValue
属性
defaultValue
属性用于指定当请求参数缺失时的默认值。如果参数缺失,则使用默认值而不是抛出异常。
示例:
@RequestParam(name = "name", defaultValue = "Guest") String name
- 如果请求中不包含
name
参数,name
将被设置为"Guest"
。
3. 路径变量与请求参数的区别
- 路径变量(Path Variables):用于从 URL 的路径中提取数据。例如:
/users/{id}
。 - 请求参数(Request Parameters):用于从查询字符串或表单数据中提取数据。例如:
/users?id=1
。
示例:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") String id) {
return "User ID: " + id;
}
@GetMapping("/search")
public String search(@RequestParam("query") String query) {
return "Search query: " + query;
}
}
/users/1
使用路径变量,id
是路径的一部分。/search?query=spring
使用请求参数,query
是查询字符串的一部分。
4. 组合使用
@RequestParam
可以与其他注解(如 @PathVariable
、@RequestBody
)组合使用,处理复杂的请求。
示例:
@RestController
public class UserController {
@PostMapping("/users/{id}")
public String updateUser(@PathVariable("id") String id,
@RequestParam("name") String name,
@RequestParam(name = "email", required = false) String email) {
return "Updated user ID: " + id + ", Name: " + name + ", Email: " + (email != null ? email : "Not provided");
}
}
5. 支持的请求内容类型
application/x-www-form-urlencoded
:传统的表单提交方式。multipart/form-data
:用于文件上传等复杂表单数据。application/json
:通常与@RequestBody
结合使用,但在特定情况下也可以用于@RequestParam
。
5、RESTful 风格
RESTful 风格一种基于资源和 HTTP 方法的 Web 服务设计方式,利用标准的 HTTP 动词来处理资源操作,通过 JSON 等格式表示资源状态。它具有可读性高、灵活性强、标准化等优点,是现代 Web 开发中广泛采用的 API 设计模式。
REST 代表 REpresentational State Transfer,意思是通过表现层状态转换进行系统交互。RESTful API 是基于 REST 原则设计的应用程序接口(API),常用于构建面向资源的网络服务。
1. RESTful 风格的核心概念
RESTful 风格主要围绕以下几个核心概念展开:
1.1 资源(Resource)
资源是 REST 的核心,表示网络上的数据或服务。每一个资源都通过一个唯一的 URI(Uniform Resource Identifier,统一资源标识符)来标识。例如:
http://example.com/users
:表示用户集合资源。http://example.com/users/1
:表示具体用户资源,用户 ID 为 1。
资源通常以 名词 来命名,并且在 URI 中尽量避免使用动词。
1.2 HTTP 方法
RESTful API 使用标准的 HTTP 方法来对资源执行不同的操作,每个方法都与一个特定的动作语义相关:
-
GET:从服务器获取资源。
- 示例:
GET /users
获取所有用户,GET /users/1
获取 ID 为 1 的用户。
- 示例:
-
POST:向服务器创建一个新的资源。
- 示例:
POST /users
在用户集合中创建一个新用户。
- 示例:
-
PUT:更新服务器上的资源(通常是替换整个资源)。
- 示例:
PUT /users/1
更新 ID 为 1 的用户信息。
- 示例:
-
PATCH:部分更新服务器上的资源(通常是更新资源的某些字段)。
- 示例:
PATCH /users/1
更新 ID 为 1 的用户的部分信息。
- 示例:
-
DELETE:从服务器上删除资源。
- 示例:
DELETE /users/1
删除 ID 为 1 的用户。
- 示例:
1.3 状态表示(Representation)
当客户端请求一个资源时,服务器将该资源的当前状态通过 状态表示 发送给客户端。通常,RESTful API 使用 JSON 或 XML 来表示资源的状态。例如,获取用户资源可能会返回这样的 JSON 数据:
{
"id": 1,
"name": "Alice",
"email": "[email protected]"
}
1.4 无状态性(Stateless)
RESTful 服务是 无状态的,这意味着每个请求都应该包含所有必要的信息,服务器不会在不同请求之间保留任何客户端的状态。每次请求都是独立的,服务器不会依赖之前请求中的信息。
1.5 URI 的结构化
RESTful API 的 URI 通常是结构化的,以层级关系表示资源。例如:
/users
:表示所有用户。/users/1
:表示 ID 为 1 的用户。/users/1/orders
:表示 ID 为 1 的用户的订单。
2. RESTful 风格的设计原则
RESTful API 设计遵循以下几个原则:
2.1 资源的标识通过 URI
每个资源都应该有唯一的 URI,并且通过这个 URI 可以对资源进行操作。例如,用户资源 /users
的 URI 是 /users
,单个用户资源 /users/1
的 URI 是 /users/1
。
2.2 使用 HTTP 方法明确资源操作
HTTP 方法(GET、POST、PUT、DELETE 等)应该明确表示对资源的操作,避免在 URI 中使用动词。例如,不推荐使用 /getUser
或 /deleteUser
这样的 URI,应该使用 /users
并结合 HTTP 方法来实现。
2.3 使用 JSON 或 XML 表示资源
资源的状态表示通常使用标准的格式,比如 JSON 或 XML,其中 JSON 更常用,因为它简洁、易于解析。响应体中包含资源的状态信息,并返回给客户端。
2.4 无状态的请求
每次客户端与服务器之间的交互都是独立的,服务器不保留客户端的状态。请求中的所有必要信息(如认证信息)都应该包含在每次请求中。
2.5 遵循标准的状态码
RESTful API 应该使用标准的 HTTP 状态码来反映操作的结果:
- 200 OK:请求成功并返回数据。
- 201 Created:成功创建资源。
- 204 No Content:操作成功但不返回数据(如 DELETE 请求)。
- 400 Bad Request:客户端发送的请求无效(如参数错误)。
- 401 Unauthorized:未经授权的请求。
- 404 Not Found:请求的资源不存在。
- 500 Internal Server Error:服务器内部错误。
3. RESTful 风格的示例
假设我们设计一个管理用户的 RESTful API,以下是一些操作和对应的 RESTful URL 和 HTTP 方法:
操作 | HTTP 方法 | URL | 请求体 | 响应体 |
---|---|---|---|---|
获取所有用户 | GET | /users | 无 | 用户列表 (JSON) |
获取特定用户(ID = 1) | GET | /users/1 | 无 | 用户信息 (JSON) |
创建新用户 | POST | /users | 用户信息 (JSON) | 新建用户信息 |
更新特定用户(ID = 1) | PUT | /users/1 | 更新后的用户信息 | 更新后的用户信息 |
删除特定用户(ID = 1) | DELETE | /users/1 | 无 | 无 |
4. RESTful 与传统 API 的对比
- RESTful API:基于资源和 HTTP 方法,简洁、标准化、易于理解和扩展。请求和响应体通常使用 JSON,客户端和服务器之间的通信是无状态的。
- 传统 API:有时会在 URL 中包含动词,例如
/getUser
、/deleteUser
,并且可能不遵循 HTTP 方法的语义。
5. RESTful API 的优势
- 可读性强:基于资源的设计和标准的 HTTP 方法,使得 API 直观且易于理解。
- 灵活性高:RESTful API 可以支持多种客户端(如 Web、移动设备)。
- 无状态性:减少服务器的负担,因为它不需要保存客户端的状态。
- 标准化:使用标准的 HTTP 协议,容易被各种工具和平台集成。
6. RESTful API 的局限
- 无状态性限制:无状态性虽然简化了请求,但在一些需要持续状态的操作(如登录状态)中需要额外设计。
- 复杂查询场景:对于复杂的查询场景,RESTful API 可能需要设计额外的过滤机制,如分页、排序、搜索等。
6、数据返回格式
6.1 Json
在前后端分离的模式下,数据的返回格式通常是 JSON(默认),Spring MVC 使用 Jackson 或 Gson 等库来自动将 Java 对象转换为 JSON 格式。
示例:
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "Alice", 30); // 自动返回 JSON 数据
}
返回的 JSON 数据格式如下:
{
"id": 1,
"name": "Alice",
"age": 30
}
maven 官网 jackson 依赖
6.2 RequestEntity
RequestEntity
是 Spring 中用来封装 HTTP 请求信息的类,它继承自 HttpEntity
,并扩展了对 HTTP 请求头、HTTP 方法(如 GET、POST)、URL 等的支持。RequestEntity
可以用来在处理请求时,获取完整的请求信息,例如请求头、请求体、请求方法等。
核心属性
- HTTP 请求头 (Headers):可以通过
RequestEntity
获取请求中的 HTTP 头信息。 - HTTP 请求方法 (Method):可以通过
RequestEntity
知晓请求是 GET、POST、PUT、DELETE 等方法。 - 请求 URL:
RequestEntity
包含完整的请求 URL,便于在处理请求时获取 URL 参数。 - 请求体 (Body):对于 POST、PUT 等带有请求体的方法,可以通过
RequestEntity
获取请求体中的数据。
RequestEntity 常用方法
getMethod()
:获取 HTTP 请求方法。getHeaders()
:获取请求头。getBody()
:获取请求体。getUrl()
:获取请求的 URL。
RequestEntity 示例
假设我们需要创建一个处理 POST 请求的接口,可以使用 RequestEntity
获取请求的所有细节,包括请求头、请求体和请求方法。
示例代码:
@PostMapping("/process")
public ResponseEntity<String> handleRequestEntity(RequestEntity<String> requestEntity) {
// 获取请求方法
HttpMethod method = requestEntity.getMethod();
// 获取请求头
HttpHeaders headers = requestEntity.getHeaders();
// 获取请求体
String body = requestEntity.getBody();
// 打印请求信息
System.out.println("Method: " + method);
System.out.println("Headers: " + headers);
System.out.println("Body: " + body);
return ResponseEntity.ok("Request processed successfully");
}
请求示例:
curl -X POST http://localhost:8080/process -H "Content-Type: text/plain" -d "Hello, Spring!"
输出:
Method: POST
Headers: [Content-Type:"text/plain"]
Body: Hello, Spring!
在这个例子中,RequestEntity
提供了对请求的详细信息,便于处理复杂的请求逻辑。
构造 RequestEntity
RequestEntity
也可以通过静态工厂方法来创建,例如:
RequestEntity<String> requestEntity = RequestEntity
.post(new URI("http://localhost:8080/process"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body("{\"key\": \"value\"}");
这里我们通过 RequestEntity
构造了一个 POST 请求,设置了请求头和请求体。
RequestEntity 的适用场景
- 需要访问 HTTP 请求的完整信息:当你需要获取请求的所有细节(包括方法、头、URL、体)时,
RequestEntity
非常有用。 - 复杂的 API 请求处理:在处理 REST API 请求时,它提供了获取和操作请求细节的能力。
6.3 ResponseEntity
ResponseEntity
是 Spring 中用来封装 HTTP 响应信息的类,它同样继承自 HttpEntity
,扩展了对 HTTP 响应头、响应状态码的支持。ResponseEntity
允许我们更灵活地设置返回给客户端的响应,包括状态码、响应体、响应头等。
核心属性
- HTTP 响应头 (Headers):可以通过
ResponseEntity
设置或获取返回给客户端的 HTTP 头信息。 - HTTP 响应体 (Body):可以通过
ResponseEntity
设置返回给客户端的响应数据。 - HTTP 状态码 (Status Code):可以通过
ResponseEntity
设置 HTTP 响应的状态码(如200 OK
,404 Not Found
,500 Internal Server Error
等)。
ResponseEntity 常用方法
status(HttpStatus)
:设置响应状态码。body(Object)
:设置响应体内容。header(String, String)
:设置响应头。ok()
:返回状态码 200 的响应。notFound()
:返回状态码 404 的响应。badRequest()
:返回状态码 400 的响应。
ResponseEntity 示例
假设我们需要处理一个文件上传操作,并返回不同的响应状态码来表示上传的结果:
示例代码:
@PostMapping("/upload")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("文件为空");
}
try {
// 假设保存文件到指定路径
String uploadDir = "/path/to/upload/directory/";
file.transferTo(new File(uploadDir + file.getOriginalFilename()));
return ResponseEntity.ok("文件上传成功");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上传失败");
}
}
请求示例:
curl -X POST -F "file=@/path/to/file.txt" http://localhost:8080/upload
可能的响应:
- 成功时:
200 OK
,响应体为 "文件上传成功" - 文件为空时:
400 Bad Request
,响应体为 "文件为空" - 出现服务器错误时:
500 Internal Server Error
,响应体为 "文件上传失败"
在这个例子中,ResponseEntity
用来灵活地返回不同的状态码和消息。
构造 ResponseEntity
你可以通过静态工厂方法来构建 ResponseEntity
,例如:
ResponseEntity<String> response = ResponseEntity
.status(HttpStatus.CREATED)
.header(HttpHeaders.LOCATION, "/new-resource")
.body("资源创建成功");
这个例子返回了一个 201 Created 的响应,同时设置了 Location
响应头,指向新创建的资源路径。
ResponseEntity 的适用场景
- 需要灵活设置响应:当你需要自定义响应的状态码、头、体时,
ResponseEntity
提供了很好的灵活性。 - RESTful API 响应:在 REST API 开发中,它是一个常见的选择,便于构建符合 HTTP 标准的响应。
6.4 RequestEntity
和 ResponseEntity
的关系
RequestEntity
主要用于处理 HTTP 请求,帮助我们获取请求的所有详细信息;而 ResponseEntity
则用于构建和返回 HTTP 响应,帮助我们返回自定义的响应数据、头和状态码。
常见组合使用场景
在 Spring MVC 的控制器方法中,你可以同时使用 RequestEntity
来获取请求信息,使用 ResponseEntity
返回响应。例如:
@PostMapping("/process-data")
public ResponseEntity<String> handleRequest(RequestEntity<String> requestEntity) {
// 获取请求体
String requestBody = requestEntity.getBody();
// 处理逻辑...
// 返回响应
return ResponseEntity.ok("处理成功:" + requestBody);
}
7、文件的上传和下载
在 Spring MVC 中,文件上传和下载是常见的需求,通常通过 MultipartFile
和 HttpServletResponse
等组件来处理。下面我们来详细讲解文件上传和下载的实现。
1. 文件上传
1.1 使用 MultipartFile
实现文件上传
Spring MVC 提供了 MultipartFile
接口来处理文件上传。为了支持文件上传,首先需要在项目的配置文件中启用对多部分请求的支持。
1.2 配置 Spring Boot 以支持文件上传
在 application.properties
或 application.yml
中配置最大文件上传大小:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
max-file-size
:限制单个文件的大小。max-request-size
:限制整个请求的大小。
1.3 文件上传的 Controller 示例
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import java.io.File;
import java.io.IOException;
@RestController
@RequestMapping("/files")
public class FileUploadController {
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
// 检查文件是否为空
if (file.isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("文件为空");
}
// 获取文件名
String fileName = file.getOriginalFilename();
try {
// 文件保存路径(可以根据需求修改)
String uploadDir = "/path/to/upload/directory/";
File dest = new File(uploadDir + fileName);
// 保存文件到服务器
file.transferTo(dest);
return ResponseEntity.ok("文件上传成功:" + fileName);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上传失败");
}
}
}
1.4 前端文件上传表单示例
<form method="POST" enctype="multipart/form-data" action="/files/upload">
<input type="file" name="file"/>
<button type="submit">上传文件</button>
</form>
在这个例子中,文件上传表单使用 enctype="multipart/form-data"
进行编码,使文件能被正确地传输到服务器。
2. 文件下载
文件下载通常通过 HttpServletResponse
来设置响应头并将文件的内容写入到输出流中。
2.1 文件下载的 Controller 示例
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
@RestController
@RequestMapping("/files")
public class FileDownloadController {
@GetMapping("/download/{fileName}")
public void downloadFile(@PathVariable("fileName") String fileName, HttpServletResponse response) {
// 文件存储路径
String filePath = "/path/to/upload/directory/" + fileName;
File file = new File(filePath);
if (!file.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentLengthLong(file.length());
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
// 将文件数据写入输出流
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
} catch (IOException e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
2.2 前端文件下载链接示例
<a href="/files/download/sample.txt">下载文件</a>
在这个示例中,当用户点击链接时,浏览器会发出下载请求,并触发 downloadFile
方法。该方法会将服务器上的文件内容以流的形式发送到客户端。
3. 文件上传与下载的详细解释
3.1 文件上传
MultipartFile
:Spring MVC 提供的接口,用于处理上传的文件。你可以通过@RequestParam("file")
获取上传的文件。transferTo
:MultipartFile
提供的一个方法,用于将上传的文件保存到指定路径。- 表单的
enctype
:文件上传表单的enctype="multipart/form-data"
,它允许文件和其他数据一起被发送。
3.2 文件下载
HttpServletResponse
:用于生成 HTTP 响应,包括设置响应头和写入响应体。Content-Type
:指定响应的内容类型为application/octet-stream
,表示该响应是一个二进制流。Content-Disposition
:用于指示浏览器将响应内容作为附件下载,而不是直接显示。例如,attachment; filename="file.txt"
会强制浏览器下载文件,并将其命名为file.txt
。- 流操作:通过
FileInputStream
读取文件内容,并通过OutputStream
将内容写入响应体中。
4. 注意事项
- 文件路径安全性:在处理文件路径时,确保不要暴露服务器文件系统的敏感信息。建议使用文件存储服务或数据库来管理上传的文件路径。
- 文件上传大小限制:在实际生产环境中,除了在 Spring 配置文件中设置文件上传大小限制之外,也可以通过 Web 服务器(如 Tomcat、Nginx)进行限制。
- 异常处理:在文件上传和下载过程中,可能会出现各种异常(如文件不存在、权限问题等),应对这些异常进行适当的处理。
5. 总结
Spring MVC 提供了简洁的方式来处理文件上传和下载。通过使用 MultipartFile
和 HttpServletResponse
,开发者可以轻松实现文件上传和下载的功能。
8、拦截器
在 Spring MVC 中,拦截器(Interceptor
)是一个用于在请求处理的不同阶段进行拦截和处理的机制。拦截器可以拦截进入控制器之前、控制器执行之后以及请求完成之后的操作,并允许开发者在这些过程中自定义逻辑。拦截器的使用使得我们可以实现诸如权限验证、日志记录、数据预处理、响应数据后处理等功能。
拦截器类似于过滤器(Filter
),但与过滤器不同的是,拦截器专注于与 Spring MVC 的请求处理流程结合得更紧密,并且能够直接访问 Spring 的上下文和数据。
1. 拦截器的工作原理
拦截器通过实现 HandlerInterceptor
接口来实现,它定义了三个主要的回调方法,分别对应请求处理的不同阶段:
三个主要方法:
-
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- 时机:在请求到达控制器之前执行(即在执行控制器方法之前)。
- 作用:用于处理一些前置逻辑,如认证、权限检查、日志记录等。
- 返回值:返回
true
时继续执行下一个拦截器或进入控制器方法,返回false
则中断请求处理,不会继续执行后续拦截器或控制器。
-
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- 时机:在控制器方法执行之后,视图渲染之前执行。
- 作用:可以修改控制器返回的
ModelAndView
,对数据进行二次处理,或者决定渲染的视图。
-
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- 时机:在整个请求完成之后(视图渲染完成后)执行。
- 作用:用于进行一些资源清理、日志记录、异常处理等工作。
2. 如何实现拦截器
步骤一:创建拦截器类
拦截器类需要实现 HandlerInterceptor
接口。
示例拦截器实现
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
// 进入控制器之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle: 请求前检查权限...");
// 可以进行权限验证逻辑,返回 false 则拦截请求
String authToken = request.getHeader("Authorization");
if (authToken == null || !authToken.equals("VALID_TOKEN")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false; // 拦截请求
}
return true; // 继续处理请求
}
// 控制器执行后,视图渲染前
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 可以修改 ModelAndView...");
}
// 请求结束,视图渲染完成后
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion: 资源清理...");
}
}
步骤二:注册拦截器
创建好拦截器类后,我们需要将其注册到 Spring 的拦截器链中。通常,我们通过配置类来注册拦截器。
示例配置
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 {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/public/**"); // 排除某些路径
}
}
在这个配置中,拦截器 MyInterceptor
被注册到拦截器链中,并指定拦截所有请求路径(/**
),同时排除 /login
和 /public/**
的路径不被拦截。
3. 拦截器与过滤器的区别
相同点:
- 两者都可以在请求到达控制器之前和请求完成之后执行逻辑。
- 都可以用于执行诸如权限验证、日志记录、性能监控等功能。
不同点:
拦截器 (Interceptor) | 过滤器 (Filter) |
---|---|
作用于 Spring MVC 的请求处理流程,与 Spring 的上下文结合紧密。 | 作用于整个 web 应用的请求链,与 Spring 无关。 |
可以获取控制器信息,并且可以操作 ModelAndView 。 |
只能操作原生的 HttpServletRequest 和 HttpServletResponse ,不涉及 Spring MVC 的特性。 |
实现 HandlerInterceptor 接口。 |
实现 javax.servlet.Filter 接口。 |
常用于 RESTful API 中的数据预处理、后处理。 | 常用于全局的过滤、编码处理、安全检查等。 |
4. 拦截器的常见应用场景
1. 权限验证
通过 preHandle
方法在请求进入控制器之前拦截请求,进行权限验证或认证处理。如果验证失败,可以返回相应的错误响应,终止请求继续执行。
示例:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authToken = request.getHeader("Authorization");
if (authToken == null || !isValidToken(authToken)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return false;
}
return true;
}
2. 日志记录
在 preHandle
或 afterCompletion
中记录请求的日志,包括请求的 URL、参数、处理时间等。
示例:
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request URL: " + request.getRequestURL() + " - Time Taken: " + (endTime - startTime) + "ms");
}
3. 数据预处理
在 preHandle
中进行数据的预处理,例如对请求参数进行格式化、数据补充等。
示例:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String param = request.getParameter("date");
if (param != null) {
// 预处理日期参数
LocalDate date = LocalDate.parse(param, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
request.setAttribute("formattedDate", date);
}
return true;
}
4. 数据后处理
在 postHandle
中对控制器返回的 ModelAndView
进行修改,添加公共的页面元素或数据。
示例:
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.addObject("footerMessage", "版权所有 © 2024");
}
}
5. 拦截器执行顺序
如果有多个拦截器时,它们的执行顺序是根据注册时的顺序来决定的。所有 preHandle
方法按照顺序依次执行,所有 postHandle
和 afterCompletion
方法按照相反的顺序执行。
假设有两个拦截器 Interceptor1
和 Interceptor2
,它们的执行顺序如下:
Interceptor1.preHandle()
->Interceptor2.preHandle()
-> 控制器方法 ->Interceptor2.postHandle()
->Interceptor1.postHandle()
->Interceptor2.afterCompletion()
->Interceptor1.afterCompletion()
6. 总结
HandlerInterceptor
是 Spring MVC 中的拦截器接口,它通过preHandle
、postHandle
和afterCompletion
三个方法来拦截请求的不同阶段。- 拦截器通常用于权限验证、日志记录、数据预处理和后处理等场景。
- 与过滤器相比,拦截器与 Spring MVC 紧密集成,能够获取
9、跨域支持(CORS)
前后端分离时,前端和后端通常部署在不同的服务器上,这会导致跨域问题。Spring MVC 提供了多种跨域配置方式,最常见的是使用 @CrossOrigin
注解。
示例:
@RestController
@CrossOrigin(origins = "http://localhost:8080")
@RequestMapping("/api/users")
public class UserController {
// 控制器方法
}
或者可以在全局配置跨域:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE");
}
}
8、常见开发模式
在前后端分离中,Spring MVC 通常与 RESTful API 模式结合使用,前端通过 HTTP 方法调用后端的 API 接口。后端仅负责数据处理和业务逻辑,而前端负责用户界面、数据展示和交互。
-
前端使用 Axios 或 Fetch 调用后端 API:
前端通过 AJAX 向后端发送请求:axios.get('/api/users/1') .then(response => { console.log(response.data); }) .catch(error => { console.error(error); });
-
后端返回 JSON 数据:
后端 Controller 处理请求并返回 JSON 数据,前端接收数据后进行页面更新。
9、总结
在前后端分离模式下,Spring MVC 的主要职责是处理请求、进行业务逻辑处理、返回 JSON 或 XML 格式的数据。其核心组件如 @RestController
、@RequestMapping
、@ResponseBody
等,简化了 RESTful API 的开发流程。此外,Spring MVC 还提供了强大的异常处理机制和跨域支持,使前后端分离的开发更加顺畅。