拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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 ResponseEntityUser 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?statusACTIVE 处理或保留 /filter 但需明确语义。 * 此处保留原路径以兼容现有调用但建议后续统一为根路径查询。 * 2. 增加枚举校验防止非法状态值。 */ GetMapping(/filter) public ResponseEntityListUser getUsersByStatus( RequestParam(value status, defaultValue ACTIVE) Pattern(regexp ^(ACTIVE|INACTIVE|LOCKED)$, message 无效的状态值) String status) { ListUser users userService.getUsersByStatus(status); return ResponseEntity.ok(users); } /** * 接口3分页查询用户列表 * 优化点 * 1. 路径语义优化RESTful 中分页通常不作为路径变量 ({pageNum})而是作为查询参数。 * 建议改为 GET /api/users?page1size10。 * 如果必须保留原 URL 结构需注意 pageNum 在路径中可能导致路由冲突。 * 2. 参数校验对页码、每页大小进行非负和范围校验。 * 3. 排序字段白名单防止 SQL 注入风险建议在后端校验 sortField 是否为合法字段。 */ GetMapping(/page/{pageNum}) public ResponseEntityPageResultUser 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 进行白名单校验 PageResultUser 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 用户列表 */ ListUser 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 分页结果对象 */ PageResultUser 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 ListUser 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 ListUser 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 PageResultUser 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. 实现排序逻辑 (原代码缺失) ListUser 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 以解耦 ListUser pageData new ArrayList(sortedUsers.subList(start, end)); return new PageResult(pageData, pageNum, pageSize, total); } /** * 辅助方法根据字段和顺序排序 */ private ListUser sortUsers(ListUser users, String sortField, String sortOrder) { if (users null || users.isEmpty()) { return Collections.emptyList(); } ComparatorUser 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 ListUser createMockUsers() { ListUser users new ArrayList(); users.add(new User(1L, 张三, zhangsanexample.com, ACTIVE)); users.add(new User(2L, 李四, lisiexample.com, ACTIVE)); users.add(new User(3L, 王五, wangwuexample.com, INACTIVE)); users.add(new User(4L, 赵六, zhaoliuexample.com, ACTIVE)); users.add(new User(5L, 钱七, qianqiexample.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 PageResultT implements Serializable { private static final long serialVersionUID 1L; private ListT data; private Integer pageNum; private Integer pageSize; private Long total; // 建议改为 Long防止数据量极大时 Integer 溢出 // 默认构造方法 public PageResult() { } public PageResult(ListT 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 ListT getData() { return data; } public void setData(ListT 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 ResponseEntityMapString, String handleValidationExceptions(MethodArgumentNotValidException ex) { MapString, String errors new HashMap(); ex.getBindingResult().getFieldErrors().forEach(error - errors.put(error.getField(), error.getDefaultMessage()) ); return ResponseEntity.badRequest().body(errors); } ExceptionHandler(ConstraintViolationException.class) public ResponseEntityMapString, String handleConstraintViolation(ConstraintViolationException ex) { // 处理 Validated 在方法参数上的校验异常 MapString, 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/1PathVariable获取userId1{id:1,name:张三,email:zhangsanexample.com,status:ACTIVE}按状态筛选GET/api/users/filter?statusACTIVERequestParam获取status参数[{id:1,name:张三,...},{id:2,name:李四,...}]分页查询GET/api/users/page/1?size2sortnameorderasc组合使用PathVariable和RequestParam{data:[{id:1,...},{id:2,...}],pageNum:1,pageSize:2,total:5}8. 注解使用总结在这个实战场景中我们综合运用了三种注解GetMapping所有接口都使用该注解声明GET请求映射代码更简洁PathVariable用于获取URL路径中的参数如/api/users/{userId}中的userIdRequestParam用于获取查询参数支持默认值设置如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注解的前提是明确URL格式必须知道URL的路径模板格式路径变量名匹配方法参数名或PathVariable的value属性必须与路径模板中的变量名一致位置对应参数在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?id1正常返回部门结果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); }设置默认值后输入http://localhost/consumer/dept/get/1?id不再报错使用默认值输入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注解后接口测试结果与之前一致输入http://localhost/consumer/dept/get/1?id输入http://localhost/consumer/dept/get/1四、三种注解对比与选择建议注解作用适用场景示例URLPathVariable获取URL路径中的参数RESTful API资源标识在路径中/users/{id}RequestParam获取请求参数的值查询参数可选参数分页排序/search?keywordxxxpage1GetMappingHTTP GET请求映射简化GET请求映射声明替代RequestMapping(methodGET)4.1 选择建议使用PathVariable时参数是资源标识的一部分且位置固定使用RequestParam时参数是可选的或者有多个同类型参数使用GetMapping时处理HTTP GET请求简化代码书写组合使用可以同时使用PathVariable和RequestParam处理复杂场景五、常见问题与解决方案5.1 参数类型转换失败当URL参数与方法参数类型不匹配时Spring会抛出类型转换异常。解决方案确保URL参数格式正确使用RequestParam(required false)设置参数非必需提供合适的默认值5.2 参数名不匹配当URL参数名与方法参数名不一致时需要使用注解的value属性明确指定// 正确明确指定参数名 GetMapping(/user) public User getUser(RequestParam(user_id) Long userId) { return userService.findById(userId); }5.3 中文参数乱码处理中文参数时可能出现乱码问题解决方案确保请求编码为UTF-8在Spring配置中设置字符编码过滤器对参数进行URL编码总结本文详细介绍了Spring MVC中三种常用的URL参数处理注解PathVariable用于获取URL路径中的参数适合RESTful风格的API设计RequestParam用于获取查询参数支持默认值和参数验证GetMapping组合注解简化GET请求的映射声明在实际开发中应根据具体需求选择合适的注解或组合使用。正确使用这些注解可以提高代码的可读性、可维护性和接口的健壮性。希望本文能帮助您更好地理解和使用Spring MVC中的参数处理机制。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门