在前后端分离模式下,Spring MVC 的作用主要集中在处理后端的业务逻辑和 API 接口,而不再直接管理视图部分。也就是说,Spring MVC 的重点是如何处理客户端的请求并返回数据(通常以 JSON 或 XML 格式),而视图渲染交给前端框架(如 Vue.js、React 等)来完成。
下面是针对前后端分离模式的 Spring MVC 详细讲解。
在前后端分离的模式下,Spring MVC 的工作流程仍然包含以下步骤,但其核心作用是处理客户端发送的请求并返回数据,而不再渲染视图。
DispatcherServlet
作为 Spring MVC 的前端控制器,拦截所有的 HTTP 请求,并将请求转发给合适的处理器(Controller)。在前后端分离的模式中,Spring MVC 的核心组件有以下几个:
控制器主要负责接收前端的请求,并返回数据。通常,控制器中的方法会通过 @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 页面。
@RestController
是 @Controller
和 @ResponseBody
的组合注解,简化了返回 JSON、XML 等格式的开发流程。每个方法返回的数据会自动序列化为客户端期望的格式。
@RequestMapping
或简化的 @GetMapping
、@PostMapping
用于定义接口的 URL 路径,并指定 HTTP 方法类型(GET、POST、PUT、DELETE 等)。它们用来路由前端请求到具体的控制器方法。
@RequestBody
:用于从请求体中提取 JSON 或 XML 格式的数据并映射到方法参数上。前端通常发送 JSON 格式的数据,Spring MVC 会自动将其转换为 Java 对象。@ResponseBody
:用于将方法的返回值自动序列化为 JSON 或 XML 格式返回给客户端。方法的返回值会直接作为 HTTP 响应体返回,而不是视图名称。@RequestMapping
是 Spring MVC 中的核心注解之一,用于将 HTTP 请求映射到处理器方法(通常是控制器类中的方法)。在前后端分离模式下,@RequestMapping
和相关的注解主要用于构建 RESTful API,确保前端能通过 HTTP 请求与后端进行交互。@RequestMapping
可以精确地映射 HTTP 请求的 URL、请求方法、请求参数等,以便后端能够根据前端的请求路径和请求类型执行相应的操作。
@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 getAllUsers() {
// 处理 "/api/users/list" 请求
return userService.getAllUsers();
}
@RequestMapping("/details/{id}")
public User getUserById(@PathVariable Long id) {
// 处理 "/api/users/details/{id}" 请求
return userService.getUserById(id);
}
}
@RequestMapping
的常用属性@RequestMapping
提供了多个属性用于更加精确地匹配 HTTP 请求:
value
属性value
属性指定请求的路径,可以是一个字符串数组,表示允许多个 URL 访问同一个处理器方法。
@RequestMapping(value = {"/list", "/all"})
public List getAllUsers() {
// "/list" 或 "/all" 都会被映射到这个方法
return userService.getAllUsers();
}
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
。
params
属性params
属性可以用于限制请求参数的存在与否。例如,如果你希望方法仅在某个特定参数存在时被调用,可以使用 params
属性。
http://example.com/login?username=yourUsername&password=yourPassword
@RequestMapping(value = "/search", params = {"username", "password"})
public List searchUsersByName(@RequestParam String username, @RequestParam String password) {
// 只有请求中包含 username 参数时,这个方法才会被执行
return userService.searchByName(username);
}
headers
属性headers
属性用于根据 HTTP 请求头匹配请求。这个属性可以用来在某些特定的请求头存在时执行某个处理方法。
@RequestMapping(value = "/info", headers = "Accept=application/json")
public User getUserInfo() {
// 仅在 Accept 头为 "application/json" 时处理请求
return userService.getUserInfo();
}
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);
}
produces
属性produces
属性用于指定返回的响应类型(Content-Type
)。例如,如果你希望返回 JSON 格式的响应,可以这样定义:
@RequestMapping(value = "/user/{id}", produces = "application/json")
public User getUserById(@PathVariable Long id) {
// 返回 JSON 格式的数据
return userService.getUserById(id);
}
path
属性path
是 value
的别名,用于定义请求的 URL 路径。如果需要更具语义化地定义路径,可以使用 path
属性。
@RequestMapping(path = "/details/{id}")
public User getUserDetails(@PathVariable Long id) {
return userService.getUserDetails(id);
}
@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);
}
为了简化常见的 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);
}
@PathVariable
@PathVariable
注解用于从 URL 的路径中提取数据,并将这些数据绑定到控制器方法的参数上。它通常与 URL 模板中的占位符 {}
一起使用。
在控制器方法中,可以使用 @PathVariable
注解来提取单个路径变量。
示例:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable("id") String id) {
return "User ID: " + id;
}
}
/users/123
id
变量将被设置为 123
,结果:User ID: 123
可以在 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;
}
}
/users/123/details/456
id
变量将被设置为 123
,detailId
变量将被设置为 456
,结果:User ID: 123, Detail ID: 456
路径变量用于 URL 模板中的单层路径。
示例:
@GetMapping("/products/{productId}")
public String getProduct(@PathVariable("productId") String productId) {
return "Product ID: " + productId;
}
/products/789
productId
变量将被设置为 789
路径变量可以用在 URL 的多层路径中,提取不同层次的数据。
示例:
@GetMapping("/categories/{categoryId}/items/{itemId}")
public String getItem(@PathVariable("categoryId") String categoryId,
@PathVariable("itemId") String itemId) {
return "Category ID: " + categoryId + ", Item ID: " + itemId;
}
/categories/1/items/42
categoryId
变量将被设置为 1
,itemId
变量将被设置为 42
@PathVariable
的名称需要与 URL 模板中的占位符名称匹配。如果不匹配,会导致绑定失败。示例:
@GetMapping("/items/{itemId}")
public String getItem(@PathVariable("itemId") String id) {
// 此处 id 是 itemId 的别名,实际名称应与 URL 模板一致
return "Item ID: " + id;
}
@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 表单:
username
的值是 alice
。
age
的值是 30
。
表单提交的数据:username=alice&age=30
返回结果:Registered user: alice, Age: 30
总结
GET
请求。POST
请求。@RequestParam
注解可以用于提取这两种类型的请求参数。当使用 GET
请求时,参数通常附加在 URL 的查询字符串中。
示例:
@RestController
public class UserController {
@GetMapping("/greet")
public String greet(@RequestParam("name") String name) {
return "Hello, " + name + "!";
}
}
/greet?name=Alice
Hello, Alice!
当使用 POST
请求时,参数通常是表单数据的一部分。
示例:
@RestController
public class UserController {
@PostMapping("/register")
public String register(@RequestParam("username") String username,
@RequestParam("password") String password) {
return "Registered user: " + username;
}
}
@RequestParam
的属性value
属性value
(或 name
)属性指定请求参数的名称。
示例:
@RequestParam(value = "name") String name
// 或
@RequestParam("name") String name
required
属性required
属性用于指示参数是否是必需的。默认为 true
,如果请求中没有提供该参数,Spring 将抛出异常。如果设置为 false
,则可以省略该参数。
示例:
@RequestParam(name = "name", required = false) String name
name
参数,name
将被设置为 null
,而不是抛出异常。defaultValue
属性defaultValue
属性用于指定当请求参数缺失时的默认值。如果参数缺失,则使用默认值而不是抛出异常。
示例:
@RequestParam(name = "name", defaultValue = "Guest") String name
name
参数,name
将被设置为 "Guest"
。/users/{id}
。/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
是查询字符串的一部分。@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");
}
}
application/x-www-form-urlencoded
:传统的表单提交方式。multipart/form-data
:用于文件上传等复杂表单数据。application/json
:通常与 @RequestBody
结合使用,但在特定情况下也可以用于 @RequestParam
。RESTful 风格一种基于资源和 HTTP 方法的 Web 服务设计方式,利用标准的 HTTP 动词来处理资源操作,通过 JSON 等格式表示资源状态。它具有可读性高、灵活性强、标准化等优点,是现代 Web 开发中广泛采用的 API 设计模式。
REST 代表 REpresentational State Transfer,意思是通过表现层状态转换进行系统交互。RESTful API 是基于 REST 原则设计的应用程序接口(API),常用于构建面向资源的网络服务。
RESTful 风格主要围绕以下几个核心概念展开:
资源是 REST 的核心,表示网络上的数据或服务。每一个资源都通过一个唯一的 URI(Uniform Resource Identifier,统一资源标识符)来标识。例如:
http://example.com/users
:表示用户集合资源。http://example.com/users/1
:表示具体用户资源,用户 ID 为 1。资源通常以 名词 来命名,并且在 URI 中尽量避免使用动词。
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 的用户。当客户端请求一个资源时,服务器将该资源的当前状态通过 状态表示 发送给客户端。通常,RESTful API 使用 JSON 或 XML 来表示资源的状态。例如,获取用户资源可能会返回这样的 JSON 数据:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
RESTful 服务是 无状态的,这意味着每个请求都应该包含所有必要的信息,服务器不会在不同请求之间保留任何客户端的状态。每次请求都是独立的,服务器不会依赖之前请求中的信息。
RESTful API 的 URI 通常是结构化的,以层级关系表示资源。例如:
/users
:表示所有用户。/users/1
:表示 ID 为 1 的用户。/users/1/orders
:表示 ID 为 1 的用户的订单。RESTful API 设计遵循以下几个原则:
每个资源都应该有唯一的 URI,并且通过这个 URI 可以对资源进行操作。例如,用户资源 /users
的 URI 是 /users
,单个用户资源 /users/1
的 URI 是 /users/1
。
HTTP 方法(GET、POST、PUT、DELETE 等)应该明确表示对资源的操作,避免在 URI 中使用动词。例如,不推荐使用 /getUser
或 /deleteUser
这样的 URI,应该使用 /users
并结合 HTTP 方法来实现。
资源的状态表示通常使用标准的格式,比如 JSON 或 XML,其中 JSON 更常用,因为它简洁、易于解析。响应体中包含资源的状态信息,并返回给客户端。
每次客户端与服务器之间的交互都是独立的,服务器不保留客户端的状态。请求中的所有必要信息(如认证信息)都应该包含在每次请求中。
RESTful API 应该使用标准的 HTTP 状态码来反映操作的结果:
假设我们设计一个管理用户的 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 | 无 | 无 |
/getUser
、/deleteUser
,并且可能不遵循 HTTP 方法的语义。在前后端分离的模式下,数据的返回格式通常是 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 依赖
RequestEntity
是 Spring 中用来封装 HTTP 请求信息的类,它继承自 HttpEntity
,并扩展了对 HTTP 请求头、HTTP 方法(如 GET、POST)、URL 等的支持。RequestEntity
可以用来在处理请求时,获取完整的请求信息,例如请求头、请求体、请求方法等。
RequestEntity
获取请求中的 HTTP 头信息。RequestEntity
知晓请求是 GET、POST、PUT、DELETE 等方法。RequestEntity
包含完整的请求 URL,便于在处理请求时获取 URL 参数。RequestEntity
获取请求体中的数据。getMethod()
:获取 HTTP 请求方法。getHeaders()
:获取请求头。getBody()
:获取请求体。getUrl()
:获取请求的 URL。假设我们需要创建一个处理 POST 请求的接口,可以使用 RequestEntity
获取请求的所有细节,包括请求头、请求体和请求方法。
@PostMapping("/process")
public ResponseEntity handleRequestEntity(RequestEntity 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 requestEntity = RequestEntity
.post(new URI("http://localhost:8080/process"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body("{"key": "value"}");
这里我们通过 RequestEntity
构造了一个 POST 请求,设置了请求头和请求体。
RequestEntity
非常有用。ResponseEntity
是 Spring 中用来封装 HTTP 响应信息的类,它同样继承自 HttpEntity
,扩展了对 HTTP 响应头、响应状态码的支持。ResponseEntity
允许我们更灵活地设置返回给客户端的响应,包括状态码、响应体、响应头等。
ResponseEntity
设置或获取返回给客户端的 HTTP 头信息。ResponseEntity
设置返回给客户端的响应数据。ResponseEntity
设置 HTTP 响应的状态码(如 200 OK
,404 Not Found
,500 Internal Server Error
等)。status(HttpStatus)
:设置响应状态码。body(Object)
:设置响应体内容。header(String, String)
:设置响应头。ok()
:返回状态码 200 的响应。notFound()
:返回状态码 404 的响应。badRequest()
:返回状态码 400 的响应。假设我们需要处理一个文件上传操作,并返回不同的响应状态码来表示上传的结果:
@PostMapping("/upload")
public ResponseEntity 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 response = ResponseEntity
.status(HttpStatus.CREATED)
.header(HttpHeaders.LOCATION, "/new-resource")
.body("资源创建成功");
这个例子返回了一个 201 Created 的响应,同时设置了 Location
响应头,指向新创建的资源路径。
ResponseEntity
提供了很好的灵活性。RequestEntity
和 ResponseEntity
的关系RequestEntity
主要用于处理 HTTP 请求,帮助我们获取请求的所有详细信息;而 ResponseEntity
则用于构建和返回 HTTP 响应,帮助我们返回自定义的响应数据、头和状态码。
在 Spring MVC 的控制器方法中,你可以同时使用 RequestEntity
来获取请求信息,使用 ResponseEntity
返回响应。例如:
@PostMapping("/process-data")
public ResponseEntity handleRequest(RequestEntity requestEntity) {
// 获取请求体
String requestBody = requestEntity.getBody();
// 处理逻辑...
// 返回响应
return ResponseEntity.ok("处理成功:" + requestBody);
}
在 Spring MVC 中,文件上传和下载是常见的需求,通常通过 MultipartFile
和 HttpServletResponse
等组件来处理。下面我们来详细讲解文件上传和下载的实现。
MultipartFile
实现文件上传Spring MVC 提供了 MultipartFile
接口来处理文件上传。为了支持文件上传,首先需要在项目的配置文件中启用对多部分请求的支持。
在 application.properties
或 application.yml
中配置最大文件上传大小:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
max-file-size
:限制单个文件的大小。max-request-size
:限制整个请求的大小。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 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("文件上传失败");
}
}
}
在这个例子中,文件上传表单使用 enctype="multipart/form-data"
进行编码,使文件能被正确地传输到服务器。
文件下载通常通过 HttpServletResponse
来设置响应头并将文件的内容写入到输出流中。
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);
}
}
}
下载文件
在这个示例中,当用户点击链接时,浏览器会发出下载请求,并触发 downloadFile
方法。该方法会将服务器上的文件内容以流的形式发送到客户端。
MultipartFile
:Spring MVC 提供的接口,用于处理上传的文件。你可以通过 @RequestParam("file")
获取上传的文件。transferTo
:MultipartFile
提供的一个方法,用于将上传的文件保存到指定路径。enctype
:文件上传表单的 enctype="multipart/form-data"
,它允许文件和其他数据一起被发送。HttpServletResponse
:用于生成 HTTP 响应,包括设置响应头和写入响应体。Content-Type
:指定响应的内容类型为 application/octet-stream
,表示该响应是一个二进制流。Content-Disposition
:用于指示浏览器将响应内容作为附件下载,而不是直接显示。例如,attachment; filename="file.txt"
会强制浏览器下载文件,并将其命名为 file.txt
。FileInputStream
读取文件内容,并通过 OutputStream
将内容写入响应体中。Spring MVC 提供了简洁的方式来处理文件上传和下载。通过使用 MultipartFile
和 HttpServletResponse
,开发者可以轻松实现文件上传和下载的功能。
在 Spring MVC 中,拦截器(Interceptor
)是一个用于在请求处理的不同阶段进行拦截和处理的机制。拦截器可以拦截进入控制器之前、控制器执行之后以及请求完成之后的操作,并允许开发者在这些过程中自定义逻辑。拦截器的使用使得我们可以实现诸如权限验证、日志记录、数据预处理、响应数据后处理等功能。
拦截器类似于过滤器(Filter
),但与过滤器不同的是,拦截器专注于与 Spring MVC 的请求处理流程结合得更紧密,并且能够直接访问 Spring 的上下文和数据。
拦截器通过实现 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)
拦截器类需要实现 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/**
的路径不被拦截。
拦截器 (Interceptor) | 过滤器 (Filter) |
---|---|
作用于 Spring MVC 的请求处理流程,与 Spring 的上下文结合紧密。 | 作用于整个 web 应用的请求链,与 Spring 无关。 |
可以获取控制器信息,并且可以操作 ModelAndView 。 |
只能操作原生的 HttpServletRequest 和 HttpServletResponse ,不涉及 Spring MVC 的特性。 |
实现 HandlerInterceptor 接口。 |
实现 javax.servlet.Filter 接口。 |
常用于 RESTful API 中的数据预处理、后处理。 | 常用于全局的过滤、编码处理、安全检查等。 |
通过 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;
}
在 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");
}
在 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;
}
在 postHandle
中对控制器返回的 ModelAndView
进行修改,添加公共的页面元素或数据。
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.addObject("footerMessage", "版权所有 © 2024");
}
}
如果有多个拦截器时,它们的执行顺序是根据注册时的顺序来决定的。所有 preHandle
方法按照顺序依次执行,所有 postHandle
和 afterCompletion
方法按照相反的顺序执行。
假设有两个拦截器 Interceptor1
和 Interceptor2
,它们的执行顺序如下:
Interceptor1.preHandle()
-> Interceptor2.preHandle()
-> 控制器方法 -> Interceptor2.postHandle()
-> Interceptor1.postHandle()
-> Interceptor2.afterCompletion()
-> Interceptor1.afterCompletion()
HandlerInterceptor
是 Spring MVC 中的拦截器接口,它通过 preHandle
、postHandle
和 afterCompletion
三个方法来拦截请求的不同阶段。前后端分离时,前端和后端通常部署在不同的服务器上,这会导致跨域问题。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");
}
}
在前后端分离中,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 数据,前端接收数据后进行页面更新。
在前后端分离模式下,Spring MVC 的主要职责是处理请求、进行业务逻辑处理、返回 JSON 或 XML 格式的数据。其核心组件如 @RestController
、@RequestMapping
、@ResponseBody
等,简化了 RESTful API 的开发流程。此外,Spring MVC 还提供了强大的异常处理机制和跨域支持,使前后端分离的开发更加顺畅。
参与评论
手机查看
返回顶部