spring mvc详细讲解(前后端分离模式)

在前后端分离模式下,Spring MVC 的作用主要集中在处理后端的业务逻辑和 API 接口,而不再直接管理视图部分。也就是说,Spring MVC 的重点是如何处理客户端的请求并返回数据(通常以 JSON 或 XML 格式),而视图渲染交给前端框架(如 Vue.js、React 等)来完成。

下面是针对前后端分离模式的 Spring MVC 详细讲解。

1、Spring MVC 工作流程

在前后端分离的模式下,Spring MVC 的工作流程仍然包含以下步骤,但其核心作用是处理客户端发送的请求并返回数据,而不再渲染视图。

  1. 客户端发送请求:前端应用(如 Vue.js 或 React)通过 AJAX 或 Axios 向后端发送 HTTP 请求,通常是 RESTful API 请求。
  2. DispatcherServlet 拦截请求DispatcherServlet 作为 Spring MVC 的前端控制器,拦截所有的 HTTP 请求,并将请求转发给合适的处理器(Controller)。
  3. 处理请求:Controller 根据请求路径和请求参数,调用业务逻辑处理或数据库操作,生成相应的响应数据。
  4. 返回 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 请求方法,例如 GETPOSTPUTDELETE 等。

@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 属性

pathvalue 的别名,用于定义请求的 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 变量将被设置为 123detailId 变量将被设置为 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 变量将被设置为 1itemId 变量将被设置为 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 请求的查询字符串或表单参数中提取数据。它可以用于处理 GETPOSTPUTDELETE 等请求方法中的参数。

补充:

查询字符串 是附加在 URL 末尾的键值对,用于向服务器传递参数。它通常用于 GET 请求,也可以用于其他请求类型。查询字符串以 ? 开始,后面跟随参数键值对,用 & 分隔多个参数。

查询字符串的格式为:

http://example.com/resource?key1=value1&key2=value2

表单参数 是通过 HTML 表单提交的键值对,通常在 POST 请求中使用。表单参数可以通过 application/x-www-form-urlencodedmultipart/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 使用 JSONXML 来表示资源的状态。例如,获取用户资源可能会返回这样的 JSON 数据:

{
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com"
}

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 等方法。
  • 请求 URLRequestEntity 包含完整的请求 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 OK404 Not Found500 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 RequestEntityResponseEntity 的关系

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 中,文件上传和下载是常见的需求,通常通过 MultipartFileHttpServletResponse 等组件来处理。下面我们来详细讲解文件上传和下载的实现。

1. 文件上传

1.1 使用 MultipartFile 实现文件上传

Spring MVC 提供了 MultipartFile 接口来处理文件上传。为了支持文件上传,首先需要在项目的配置文件中启用对多部分请求的支持。

1.2 配置 Spring Boot 以支持文件上传

application.propertiesapplication.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") 获取上传的文件。
  • transferToMultipartFile 提供的一个方法,用于将上传的文件保存到指定路径。
  • 表单的 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 提供了简洁的方式来处理文件上传和下载。通过使用 MultipartFileHttpServletResponse,开发者可以轻松实现文件上传和下载的功能。

8、拦截器

在 Spring MVC 中,拦截器(Interceptor)是一个用于在请求处理的不同阶段进行拦截和处理的机制。拦截器可以拦截进入控制器之前、控制器执行之后以及请求完成之后的操作,并允许开发者在这些过程中自定义逻辑。拦截器的使用使得我们可以实现诸如权限验证、日志记录、数据预处理、响应数据后处理等功能。

拦截器类似于过滤器(Filter),但与过滤器不同的是,拦截器专注于与 Spring MVC 的请求处理流程结合得更紧密,并且能够直接访问 Spring 的上下文和数据。


1. 拦截器的工作原理

拦截器通过实现 HandlerInterceptor 接口来实现,它定义了三个主要的回调方法,分别对应请求处理的不同阶段:

三个主要方法:

  1. preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

    • 时机:在请求到达控制器之前执行(即在执行控制器方法之前)。
    • 作用:用于处理一些前置逻辑,如认证、权限检查、日志记录等。
    • 返回值:返回 true 时继续执行下一个拦截器或进入控制器方法,返回 false 则中断请求处理,不会继续执行后续拦截器或控制器。
  2. postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

    • 时机:在控制器方法执行之后,视图渲染之前执行。
    • 作用:可以修改控制器返回的 ModelAndView,对数据进行二次处理,或者决定渲染的视图。
  3. 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 只能操作原生的 HttpServletRequestHttpServletResponse,不涉及 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. 日志记录

preHandleafterCompletion 中记录请求的日志,包括请求的 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 方法按照顺序依次执行,所有 postHandleafterCompletion 方法按照相反的顺序执行。

假设有两个拦截器 Interceptor1Interceptor2,它们的执行顺序如下:

  • Interceptor1.preHandle() -> Interceptor2.preHandle() -> 控制器方法 -> Interceptor2.postHandle() -> Interceptor1.postHandle() -> Interceptor2.afterCompletion() -> Interceptor1.afterCompletion()

6. 总结

  • HandlerInterceptor 是 Spring MVC 中的拦截器接口,它通过 preHandlepostHandleafterCompletion 三个方法来拦截请求的不同阶段。
  • 拦截器通常用于权限验证、日志记录、数据预处理和后处理等场景。
  • 与过滤器相比,拦截器与 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 接口。后端仅负责数据处理和业务逻辑,而前端负责用户界面、数据展示和交互。

  1. 前端使用 Axios 或 Fetch 调用后端 API:
    前端通过 AJAX 向后端发送请求:

    axios.get('/api/users/1')
        .then(response => {
            console.log(response.data);
        })
        .catch(error => {
            console.error(error);
        });
    
  2. 后端返回 JSON 数据:
    后端 Controller 处理请求并返回 JSON 数据,前端接收数据后进行页面更新。

9、总结

在前后端分离模式下,Spring MVC 的主要职责是处理请求、进行业务逻辑处理、返回 JSON 或 XML 格式的数据。其核心组件如 @RestController@RequestMapping@ResponseBody 等,简化了 RESTful API 的开发流程。此外,Spring MVC 还提供了强大的异常处理机制和跨域支持,使前后端分离的开发更加顺畅。