当前位置: 首页 > news >正文

SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍

引言

在Spring Boot开发中,处理URL参数是Web开发的基础操作。Spring MVC提供了多种注解来简化这一过程,其中最常用的包括@PathVariable@RequestParam@GetMapping。本文将详细介绍这三种注解的使用方法、区别以及实际应用场景,帮助开发者更好地理解和运用这些注解。

实战场景:用户信息查询接口

为了更好地理解这三种注解的实际应用,下面我们通过一个完整的用户信息查询接口示例,展示如何同时使用@PathVariable@RequestParam@GetMapping注解。

1. 需求分析

假设我们需要开发一个用户信息查询接口,要求:

  • 通过用户ID(路径参数)获取用户基本信息
  • 支持通过查询参数筛选用户状态
  • 支持分页查询用户列表
  • 所有接口都使用GET请求

2. Controller 层实现

首先创建 UserController,定义三个接口:

import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.Min; import javax.validation.constraints.Pattern; import java.util.List; @RestController @RequestMapping("/api/users") @Validated // 开启方法级别的参数校验 public class UserController { private final UserService userService; // 推荐:使用构造器注入,final 字段确保不可变性 public UserController(UserService userService) { this.userService = userService; } /** * 接口1:根据用户ID查询用户详情 * 优化点: * 1. 增加参数校验 (@Min),防止非法ID进入业务层。 * 2. 返回 ResponseEntity,以便更灵活地控制 HTTP 状态码(如 404 Not Found)。 */ @GetMapping("/{userId}") public ResponseEntity<User> getUserById(@PathVariable("userId") @Min(1) Long userId) { User user = userService.getUserById(userId); if (user == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); } /** * 接口2:根据状态筛选用户 * 优化点: * 1. URL 路径优化:RESTful 风格中,筛选通常作为查询参数,路径保持资源名词复数形式。 * 原 /filter 略显冗余,建议直接由 /api/users?status=ACTIVE 处理,或保留 /filter 但需明确语义。 * 此处保留原路径以兼容现有调用,但建议后续统一为根路径查询。 * 2. 增加枚举校验,防止非法状态值。 */ @GetMapping("/filter") public ResponseEntity<List<User>> getUsersByStatus( @RequestParam(value = "status", defaultValue = "ACTIVE") @Pattern(regexp = "^(ACTIVE|INACTIVE|LOCKED)$", message = "无效的状态值") String status) { List<User> users = userService.getUsersByStatus(status); return ResponseEntity.ok(users); } /** * 接口3:分页查询用户列表 * 优化点: * 1. 路径语义优化:RESTful 中分页通常不作为路径变量 ({pageNum}),而是作为查询参数。 * 建议改为 GET /api/users?page=1&size=10。 * 如果必须保留原 URL 结构,需注意 pageNum 在路径中可能导致路由冲突。 * 2. 参数校验:对页码、每页大小进行非负和范围校验。 * 3. 排序字段白名单:防止 SQL 注入风险,建议在后端校验 sortField 是否为合法字段。 */ @GetMapping("/page/{pageNum}") public ResponseEntity<PageResult<User>> getUsersByPage( @PathVariable("pageNum") @Min(1) Integer pageNum, @RequestParam(value = "size", defaultValue = "20") @Min(1) @Max(100) Integer pageSize, @RequestParam(value = "sort", defaultValue = "id") String sortField, @RequestParam(value = "order", defaultValue = "asc") @Pattern(regexp = "^(asc|desc)$", message = "排序方式只能为 asc 或 desc") String sortOrder) { // 建议在此处或 Service 层对 sortField 进行白名单校验 PageResult<User> result = userService.getUsersByPage(pageNum, pageSize, sortField, sortOrder); return ResponseEntity.ok(result); } }

3. Service 层接口定义

创建 UserService 接口,定义业务逻辑:

import java.util.List; import javax.validation.constraints.Min; import javax.validation.constraints.Pattern; import org.springframework.validation.annotation.Validated; /** * 用户服务接口 */ @Validated // 开启方法参数校验支持 public interface UserService { /** * 根据用户ID查询用户详情 * * @param userId 用户ID,必须大于0 * @return 用户对象,若不存在可能返回 null 或抛出异常(视具体实现策略而定) */ User getUserById(@Min(value = 1, message = "用户ID必须为正整数") Long userId); /** * 根据状态筛选用户列表 * <p> * 建议使用枚举类型替代 String,以增强类型安全和可维护性。 * * @param status 用户状态 (ACTIVE, INACTIVE, DELETED) * @return 用户列表 */ List<User> getUsersByStatus(UserStatus status); /** * 分页查询用户列表 * <p> * 优化点: * 1. 增加参数校验,防止非法页码和pageSize导致性能问题。 * 2. sortField 建议在后端实现层进行白名单校验,防止SQL注入。 * 3. 推荐使用 Spring Data 的 Pageable 接口简化分页参数传递(可选)。 * * @param pageNum 页码,从1开始,必须 >= 1 * @param pageSize 每页大小,必须 >= 1 且 <= 100(防止过大查询) * @param sortField 排序字段 * @param sortOrder 排序方式 (asc/desc) * @return 分页结果对象 */ PageResult<User> getUsersByPage( @Min(value = 1, message = "页码必须大于等于1") Integer pageNum, @Min(value = 1, message = "每页大小必须大于等于1") @Max(value = 100, message = "每页大小不能超过100") Integer pageSize, String sortField, @Pattern(regexp = "^(asc|desc)$", message = "排序方式只能为 asc 或 desc") String sortOrder ); }

4. Service 实现类

实现 UserService 接口,这里使用模拟数据:

import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service public class UserServiceImpl implements UserService { // 1. 使用不可变列表,确保线程安全且防止外部修改 private final List<User> mockUsers = Collections.unmodifiableList(createMockUsers()); @Override public User getUserById(Long userId) { if (userId == null) { throw new IllegalArgumentException("用户ID不能为空"); } return mockUsers.stream() .filter(user -> user.getId() != null && user.getId().equals(userId)) .findFirst() .orElseThrow(() -> new NoSuchElementException("用户不存在,ID: " + userId)); } @Override public List<User> getUsersByStatus(String status) { if (status == null || status.isEmpty()) { return Collections.emptyList(); } // 2. 增加空值保护,防止 user.getStatus() 为 null 导致 NPE return mockUsers.stream() .filter(user -> user.getStatus() != null && user.getStatus().equalsIgnoreCase(status)) .collect(Collectors.toList()); } @Override public PageResult<User> getUsersByPage(Integer pageNum, Integer pageSize, String sortField, String sortOrder) { // 3. 参数校验 if (pageNum == null || pageNum < 1) pageNum = 1; if (pageSize == null || pageSize < 1) pageSize = 10; if (pageSize > 100) pageSize = 100; // 限制最大页数,防止性能问题 // 4. 实现排序逻辑 (原代码缺失) List<User> sortedUsers = sortUsers(mockUsers, sortField, sortOrder); int total = sortedUsers.size(); int start = (pageNum - 1) * pageSize; // 处理越界情况 if (start >= total) { return new PageResult<>(Collections.emptyList(), pageNum, pageSize, total); } int end = Math.min(start + pageSize, total); // subList 返回的是视图,建议包装为新 ArrayList 以解耦 List<User> pageData = new ArrayList<>(sortedUsers.subList(start, end)); return new PageResult<>(pageData, pageNum, pageSize, total); } /** * 辅助方法:根据字段和顺序排序 */ private List<User> sortUsers(List<User> users, String sortField, String sortOrder) { if (users == null || users.isEmpty()) { return Collections.emptyList(); } Comparator<User> comparator = null; // 简单的白名单机制,防止非法字段导致错误 if ("name".equalsIgnoreCase(sortField)) { comparator = Comparator.comparing(User::getName, Comparator.nullsLast(String::compareTo)); } else if ("id".equalsIgnoreCase(sortField) || sortField == null) { // 默认按 ID 排序 comparator = Comparator.comparing(User::getId, Comparator.nullsLast(Long::compareTo)); } else if ("status".equalsIgnoreCase(sortField)) { comparator = Comparator.comparing(User::getStatus, Comparator.nullsLast(String::compareTo)); } else { // 未知字段默认按 ID 排序 comparator = Comparator.comparing(User::getId, Comparator.nullsLast(Long::compareTo)); } // 处理排序方向 if ("desc".equalsIgnoreCase(sortOrder)) { comparator = comparator.reversed(); } // 创建新列表并排序,避免修改原始不可变列表 return users.stream() .sorted(comparator) .collect(Collectors.toList()); } private List<User> createMockUsers() { List<User> users = new ArrayList<>(); users.add(new User(1L, "张三", "zhangsan@example.com", "ACTIVE")); users.add(new User(2L, "李四", "lisi@example.com", "ACTIVE")); users.add(new User(3L, "王五", "wangwu@example.com", "INACTIVE")); users.add(new User(4L, "赵六", "zhaoliu@example.com", "ACTIVE")); users.add(new User(5L, "钱七", "qianqi@example.com", "DELETED")); return users; } }

5. 实体类定义

定义 User 和 PageResult 实体类:

import java.io.Serializable; import java.util.Objects; /** * 用户实体类 * 实现 Serializable 以便支持分布式缓存或会话持久化 */ public class User implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String name; private String email; private String status; // 默认构造方法(框架序列化/反序列化通常需要) public User() { } public User(Long id, String name, String email, String status) { this.id = id; this.name = name; this.email = email; this.status = status; } // Getter 和 Setter public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } /** * 重写 equals 和 hashCode,基于业务主键 id 判断相等性 */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); } @Override public int hashCode() { return Objects.hash(id); } /** * 重写 toString,方便日志打印和调试 */ @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", status='" + status + '\'' + '}'; } }
import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.Objects; /** * 通用分页结果封装类 * @param <T> 数据类型 */ public class PageResult<T> implements Serializable { private static final long serialVersionUID = 1L; private List<T> data; private Integer pageNum; private Integer pageSize; private Long total; // 建议改为 Long,防止数据量极大时 Integer 溢出 // 默认构造方法 public PageResult() { } public PageResult(List<T> data, Integer pageNum, Integer pageSize, Long total) { this.data = data == null ? Collections.emptyList() : data; // 防止 NPE this.pageNum = pageNum == null ? 1 : pageNum; this.pageSize = pageSize == null ? 10 : pageSize; this.total = total == null ? 0L : total; } // Getter 和 Setter public List<T> getData() { return data; } public void setData(List<T> data) { this.data = data; } public Integer getPageNum() { return pageNum; } public void setPageNum(Integer pageNum) { this.pageNum = pageNum; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } public Long getTotal() { return total; } public void setTotal(Long total) { this.total = total; } /** * 计算总页数 * @return 总页数 */ public int getTotalPages() { if (pageSize == null || pageSize <= 0) { return 0; } return (int) Math.ceil((double) total / pageSize); } /** * 判断是否有下一页 * @return true 如果有下一页 */ public boolean hasNext() { return getPageNum() < getTotalPages(); } /** * 判断是否有上一页 * @return true 如果有上一页 */ public boolean hasPrevious() { return getPageNum() > 1; } @Override public String toString() { return "PageResult{" + "data=" + data + ", pageNum=" + pageNum + ", pageSize=" + pageSize + ", total=" + total + ", totalPages=" + getTotalPages() + '}'; } }

6,全局异常处理

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()) ); return ResponseEntity.badRequest().body(errors); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<Map<String, String>> handleConstraintViolation(ConstraintViolationException ex) { // 处理 @Validated 在方法参数上的校验异常 Map<String, String> errors = new HashMap<>(); ex.getConstraintViolations().forEach(violation -> errors.put(violation.getPropertyPath().toString(), violation.getMessage()) ); return ResponseEntity.badRequest().body(errors); } }

7. 接口测试示例

使用 Postman 或浏览器测试上述接口:

接口HTTP方法URL示例参数说明返回示例
查询用户详情GET/api/users/1@PathVariable获取userId=1{"id":1,"name":"张三","email":"zhangsan@example.com","status":"ACTIVE"}
按状态筛选GET/api/users/filter?status=ACTIVE@RequestParam获取status参数[{"id":1,"name":"张三",...},{"id":2,"name":"李四",...}]
分页查询GET/api/users/page/1?size=2&sort=name&order=asc组合使用@PathVariable@RequestParam{"data":[{"id":1,...},{"id":2,...}],"pageNum":1,"pageSize":2,"total":5}

8. 注解使用总结

在这个实战场景中,我们综合运用了三种注解:

  1. @GetMapping:所有接口都使用该注解声明GET请求映射,代码更简洁
  2. @PathVariable:用于获取URL路径中的参数(如/api/users/{userId}中的userId)
  3. @RequestParam:用于获取查询参数,支持默认值设置(如defaultValue = "ACTIVE"

这种组合使用方式在实际开发中非常常见,能够清晰地区分路径参数和查询参数,提高API的可读性和可维护性。

一、@PathVariable:获取URL路径中的参数

@PathVariable注解用于从URL路径模板中提取变量值。它通常用于RESTful风格的API设计,将参数作为URL路径的一部分。

1.1 基本用法

假设我们需要获取URLlocalhost:80/consumer/get/{id}中的id参数,并返回对应的部门信息,实现代码如下:

@RestController @RequestMapping("/consumer") public class DeptController { @GetMapping("/get/{id}") public Dept getDeptById(@PathVariable("id") Long id) { // 根据id查询部门信息 return deptService.findById(id); } }

1.2 使用前提

使用@PathVariable注解的前提是:

  1. 明确URL格式:必须知道URL的路径模板格式
  2. 路径变量名匹配:方法参数名或@PathVariable的value属性必须与路径模板中的变量名一致
  3. 位置对应:参数在URL路径中的位置必须与方法定义中的路径变量位置对应

二、@RequestParam:获取请求参数的值

@RequestParam注解用于从请求的查询参数(query string)中获取值。它适用于传统的表单提交和GET请求带参数的情况。

2.1 基本用法

通过@RequestParam获取查询参数的示例代码如下:

@GetMapping("/dept/get/{id}") public Dept getDeptWithParam(@PathVariable("id") Long id, @RequestParam("deptName") String deptName) { // 同时使用路径参数和查询参数 return deptService.findDept(id, deptName); }

2.2 参数验证与默认值

@RequestParam提供了参数验证和默认值设置功能,增强了接口的健壮性。

2.2.1 正常情况

当输入完整参数时:http://localhost/consumer/dept/get/1?id=1

正常返回部门结果:

2.2.2 异常情况

情况一:缺少参数
输入:http://localhost/consumer/dept/get/1(不输入id参数和参数值)

情况二:参数值为空
输入:http://localhost/consumer/dept/get/1?id(不输入id参数值)

以上两种情况都会报错:

2.3 设置默认值

@RequestParam注解提供了defaultValue属性,允许为参数设置默认值,避免因参数缺失导致的异常:

@GetMapping("/dept/get/{id}") public Dept getDeptWithDefault(@PathVariable("id") Long id, @RequestParam(value = "id", defaultValue = "1") Long paramId) { // 当id参数不存在时,使用默认值1 return deptService.findDept(id, paramId); }

设置默认值后:

  1. 输入:http://localhost/consumer/dept/get/1?id(不再报错,使用默认值)

  1. 输入:http://localhost/consumer/dept/get/1

三、@GetMapping:组合注解简化代码

@GetMapping是一个组合注解,它是@RequestMapping(method = RequestMethod.GET)的缩写。该注解将HTTP GET请求映射到特定的处理方法上。

3.1 基本用法

可以使用@GetMapping(value = "/dept/get/{id}")来代替@RequestMapping(value="/dept/get/{id}", method= RequestMethod.GET),从而精简代码:

// 使用@RequestMapping的写法 @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) public Dept getDeptOld(@PathVariable("id") Long id) { return deptService.findById(id); } // 使用@GetMapping的写法(更简洁) @GetMapping("/dept/get/{id}") public Dept getDeptNew(@PathVariable("id") Long id) { return deptService.findById(id); }

3.2 实际测试

使用@GetMapping注解后,接口测试结果与之前一致:

  1. 输入:http://localhost/consumer/dept/get/1?id

  1. 输入:http://localhost/consumer/dept/get/1

四、三种注解对比与选择建议

注解作用适用场景示例URL
@PathVariable获取URL路径中的参数RESTful API,资源标识在路径中/users/{id}
@RequestParam获取请求参数的值查询参数,可选参数,分页排序/search?keyword=xxx&page=1
@GetMappingHTTP GET请求映射简化GET请求映射声明替代@RequestMapping(method=GET)

4.1 选择建议

  • 使用@PathVariable时:参数是资源标识的一部分,且位置固定
  • 使用@RequestParam时:参数是可选的,或者有多个同类型参数
  • 使用@GetMapping时:处理HTTP GET请求,简化代码书写
  • 组合使用:可以同时使用@PathVariable@RequestParam处理复杂场景

五、常见问题与解决方案

5.1 参数类型转换失败

当URL参数与方法参数类型不匹配时,Spring会抛出类型转换异常。解决方案:

  1. 确保URL参数格式正确
  2. 使用@RequestParam(required = false)设置参数非必需
  3. 提供合适的默认值

5.2 参数名不匹配

当URL参数名与方法参数名不一致时,需要使用注解的value属性明确指定:

// 正确:明确指定参数名 @GetMapping("/user") public User getUser(@RequestParam("user_id") Long userId) { return userService.findById(userId); }

5.3 中文参数乱码

处理中文参数时可能出现乱码问题,解决方案:

  1. 确保请求编码为UTF-8
  2. 在Spring配置中设置字符编码过滤器
  3. 对参数进行URL编码

总结

本文详细介绍了Spring MVC中三种常用的URL参数处理注解:

  1. @PathVariable:用于获取URL路径中的参数,适合RESTful风格的API设计
  2. @RequestParam:用于获取查询参数,支持默认值和参数验证
  3. @GetMapping:组合注解,简化GET请求的映射声明

在实际开发中,应根据具体需求选择合适的注解或组合使用。正确使用这些注解可以提高代码的可读性、可维护性和接口的健壮性。希望本文能帮助您更好地理解和使用Spring MVC中的参数处理机制。

http://www.jsqmd.com/news/1389663/

相关文章:

  • 拒绝花架子做实体:揭秘兰州营销型网站建设如何让本地企业订单翻倍
  • 强引用,弱引用,软引用,虚引用它们有什么区别?你知道吗?
  • 记录C++ 4
  • 一天内用AI构建Netty MVC框架:探索AI编程的工程实践与边界
  • Ubuntu(Linux系统)安装Vulhub靶场
  • 【高级数字信号处理】超详细指南lab1a:MATLAB 信号采样、频谱分析、非线性削波及混叠现象完全解读【含matlab代码】
  • DeepSeek V4与GPT-5.5实测对比:AI大模型选型指南
  • Windows 10硬盘卡顿终极解决指南:从诊断到优化,告别系统延迟
  • Transformer架构深度解析:从自注意力到编码器层的工程实现
  • 数据倾斜原理及解决方案
  • 广东网站建设公司xywdl如何打造高转化率企业官网全解析与避坑指南
  • 白嫖大模型
  • Git 上传大文件
  • 手搓代码解释器:基于AST解析与语义分析的代码理解实践
  • Windows系统下Python多版本管理——Pyenv安装及使用
  • 政务内网网站建设方案深度解析:构建安全、高效、智能的数字化办公核心引擎
  • DeepSeek-V4-Flash:高效能AI模型的本地部署与实战应用指南
  • AI周刊:多模态突破、模型专业化与国产生态竞争深度解析
  • StarRocks多模态检索:一条SQL统一向量、全文与AI分析
  • 信号与系统考研强化:考点精讲与专题突破实战指南
  • DNS服务器搭建
  • 云南昆明电梯维保
  • 数学建模竞赛实战复盘:从数据预处理到模型构建的全流程解析
  • 海宁市本地防水补漏维修靠谱团队有哪些怎么选_屋顶漏水维修正规资质口碑实力深度对比 - 雨婺虹修缮
  • ns-3-model-library wifi 浅析_ns-3wifi部分解析_ns-3网络模拟器wifi部分文档分析_Part4_MAC模块
  • 从0到1打造高收益平台:深度解析投资理财网站建设的关键要素与实战策略
  • 【路径规划】基于RRT算法机器人最短路径规划matlab代码
  • MathorCup数学建模竞赛:从团队组建到实战策略的完整指南
  • 新电脑首次安装通讯软件前的安全核对清单
  • 驾驶事故处理策略