1. Spring MVC与REST API基础
1.1 RESTful架构的六大约束详解
RESTful架构是Roy Thomas Fielding在2000年博士论文中提出的软件架构风格,它包含六个核心约束,这些约束共同构成了RESTful API的设计原则。
客户端-服务器约束(Client-Server):将系统分为客户端和服务器两个独立部分,客户端负责用户界面和交互,服务器负责数据存储和业务逻辑 。这种分离使得客户端和服务器可以独立演进,提高系统的可维护性和可扩展性。
无状态约束(Stateless):每个请求都必须包含理解该请求所需的所有信息,服务器不保存客户端的状态 。这种设计带来了可见性、可靠性和可伸缩性的优势,但也要求客户端在每次请求中携带必要的身份验证信息。
缓存约束(Cacheable):允许客户端或中间层缓存服务器响应,减少重复请求,提高系统性能 。服务器需要明确标记响应是否可缓存,客户端则需要正确处理缓存数据的有效性。
统一接口约束(Uniform Interface):这是RESTful架构的核心特征,要求接口遵循统一的标准 。具体包括:
- 资源标识:使用URI作为资源的唯一标识符
- 资源操作:通过HTTP方法对资源进行操作
- 自描述消息:每个消息包含足够的信息来描述如何处理它
- 超媒体驱动应用状态(HATEOAS):在响应中包含相关操作的链接
分层系统约束(Layered System):系统由多个层次组成,每个层次只与相邻层次交互,这种设计提高了系统的可维护性和可扩展性 。
按需代码约束(Code on Demand):允许服务器向客户端发送可执行代码,扩展客户端功能 。虽然这个约束不是必须的,但在某些场景下可以简化客户端开发。
在实际开发中,我们通常关注Richardson成熟度模型,它将RESTful API分为四个级别:
- Level 0:基于远程过程调用(RPC)的Web服务
- Level 1:将服务抽象为资源,每个资源由唯一的URI标识
- Level 2:使用HTTP方法(GET、POST、PUT、DELETE)对资源进行操作
- Level 3:实现HATEOAS,客户端通过超媒体链接发现下一步操作
在Spring MVC中构建RESTful API,我们通常至少达到Level 2级别,即使用HTTP方法对资源进行操作,但可能不完全实现HATEOAS。
1.2 Spring MVC框架核心组件与工作流程
Spring MVC是一个基于Java的Web框架,实现了MVC(模型-视图-控制器)设计模式
8
。它通过分离业务逻辑、数据和界面显示来简化Web应用开发。
核心组件:
- DispatcherServlet:前端控制器,负责接收HTTP请求并分发给相应的处理器
- HandlerMapping:处理器映射,负责将请求映射到对应的控制器方法
- Controller:控制器,处理请求并返回处理结果
- Model:模型,封装请求处理过程中产生的数据
- View:视图,负责将模型数据渲染成特定格式的响应
- ViewResolver:视图解析器,负责解析视图名称并返回视图对象
工作流程:
- 客户端发送HTTP请求到服务器
- 请求由Servlet容器(如Tomcat)接收并传递给DispatcherServlet
- DispatcherServlet查询HandlerMapping,找到处理该请求的Controller
- Controller处理请求,生成Model数据
- DispatcherServlet将Model和视图名称传递给ViewResolver
- ViewResolver解析视图名称,找到对应的View
- View使用Model数据渲染响应,返回给客户端
在构建RESTful API时,我们通常使用@RestController代替传统的@Controller和 JSP/Thymeleaf视图,这样可以将响应直接以JSON格式返回,无需视图解析器。
1.3 Spring MVC与Spring WebFlux对比分析
Spring MVC和Spring WebFlux都是Spring框架提供的Web开发解决方案,但它们在底层实现和适用场景上有显著差异。
特性 | Spring MVC | Spring WebFlux |
---|---|---|
编程模型 | 命令式编程 | 响应式编程 |
线程模型 | 阻塞式IO,每个请求一个线程 | 非阻塞式IO,使用事件循环和背压机制 |
适用场景 | 传统Web应用,高并发但非实时场景 | 高并发、实时性要求高的场景,如IoT、实时监控 |
性能 | 依赖线程池,线程数受限 | 更高的资源利用率,支持百万级并发连接 |
依赖 | Servlet API | Reactor或RxJava |
Spring MVC是基于Servlet API的传统框架,采用命令式编程方式,每个请求对应一个线程 。它适合大多数企业级应用开发,尤其是需要与大量遗留系统集成的场景。
Spring WebFlux是Spring 5引入的响应式框架,基于Reactor库实现 。它采用函数式编程方式,能够处理非阻塞IO操作,适合高并发、实时性要求高的场景。
在REST API设计中,Spring MVC提供了更为简洁和直观的实现方式,而Spring WebFlux则提供了更好的并发性能。对于大多数企业级应用,Spring MVC已经足够;而对于需要处理大量并发连接的场景,Spring WebFlux可能是更好的选择。
2. REST API设计核心流程
2.1 资源建模与路径设计原则
资源建模是REST API设计的第一步,它要求我们将业务实体抽象为资源
12
。在Spring MVC中,通常使用实体类(如User、Product)来表示资源。
路径设计原则:
- 使用名词复数形式表示资源集合(如/users)
- 使用单数形式表示单个资源(如/users/1)
- 避免使用动词(如/getUsers),而是通过HTTP方法表示操作
- 使用连字符增加可读性(如/user-profile)
- 使用层级关系表示资源关系(如/users/1/orders)
在Spring MVC中,我们可以使用@RequestMapping注解来定义资源路径:
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {@GetMappingpublic ResponseEntity<List<User>> AllUsers() {// 返回所有用户}@GetMapping("{/id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {// 返回指定ID的用户}
}
资源建模的关键点:
- 每个资源应该有明确的边界和职责
- 资源之间可以通过链接(HATEOAS)建立关系
- 路径设计应该简洁、直观,易于理解
2.2 HTTP方法与状态码的语义化使用
HTTP方法是RESTful API中操作资源的核心方式,它们具有特定的语义和约束
12
:
- GET:获取资源,应该是安全的(不修改资源状态)和幂等的
- POST:创建资源,非幂等的
- PUT:全量更新资源,幂等的
- DELETE:删除资源,幂等的
- PATCH:部分更新资源,幂等的(如果正确实现)
- HEAD:获取资源元数据,与GET类似但不返回内容
在Spring MVC中,我们可以使用以下注解来映射HTTP方法
3
:
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {@GetMappingpublic ResponseEntity<List<User>> AllUsers() {// 获取所有用户}@PostMappingpublic ResponseEntity<User> createUser(@RequestBody User user) {// 创建新用户}@PutMapping("{/id}")public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {// 全量更新用户}@DeleteMapping("{/id}")public ResponseEntity<?>.deleteUser(@PathVariable Long id) {// 删除用户}@PatchMapping("{/id}")public ResponseEntity<User> patchUser(@PathVariable Long id, @RequestBody UserPatch patch) {// 部分更新用户}
}
HTTP状态码用于表示请求的处理结果
12
:
- 200 OK:请求成功,返回资源
- 201 Created:资源创建成功,包含新资源的URI
- 204 No Content:请求成功,但没有返回内容
- 400 Bad Request:请求参数错误
- 401 Unauthorized:用户未认证
- 403 Forbidden:用户无权限
- 404 Not Found:资源不存在
- 500 Internal Server Error:服务器内部错误
在Spring MVC中,我们可以使用ResponseEntity来返回带有状态码的响应
3
:
java
深色版本
@GetMapping("{/id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity.notFound().build();}return ResponseEntity.ok(user);
}
HTTP方法与状态码的正确使用是RESTful API设计的基础,它们帮助客户端理解资源的操作方式和结果。
2.3 参数绑定与数据传输对象设计
参数绑定是REST API处理请求数据的关键步骤,在Spring MVC中我们可以通过多种注解来实现:
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {// 路径变量参数绑定@GetMapping("{/id}")public ResponseEntity<User> getUser(@PathVariable Long id) {// ...}// 查询参数绑定@GetMappingpublic ResponseEntity<List<User>> getUsers(@RequestParam(required = false, defaultValue = "0") Integer page,@RequestParam(required = false, defaultValue = "10") Integer size,@RequestParam(required = false) String name) {// ...}// 请求体参数绑定@PostMappingpublic ResponseEntity<User> createUser(@RequestBody UserDTO userDTO) {// ...}
}
数据传输对象(DTO)设计是REST API的重要环节,它帮助我们解耦业务实体和API响应
16
:
java
深色版本
@Data
public class UserDTO {private Long id;private String name;private String email;private String role;// 省略getter和setter
}@Service
public class UserService {public UserDTO getUserDTO(Long id) {User user = userRepository.findById(id).orElse(null);if (user == null) {return null;}return modelMapper.map(user, UserDTO.class);}
}
参数绑定的最佳实践:
- 使用路径变量(@PathVariable)表示资源标识符
- 使用查询参数(@RequestParam)表示过滤条件、分页参数等
- 使用请求体(@RequestBody)传递复杂数据
- 使用DTO解耦业务实体和API响应,避免暴露内部细节
- 使用参数校验注解(@Valid)确保输入数据的有效性
2.4 响应格式化与多格式支持配置
响应格式化是REST API设计的重要环节,它决定了客户端如何接收和处理数据
15
。在Spring MVC中,我们可以使用以下方式实现:
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {@GetMapping(produces = "application/json")public ResponseEntity<List<User>> AllUsers() {// 返回JSON格式}@GetMapping(produces = "application/xml")public ResponseEntity<List<User>> AllUsersXML() {// 返回XML格式}
}
多格式支持是RESTful API的重要特性,它允许客户端指定期望的数据格式
11
。在Spring Boot中,我们可以使用ContentNegotiationManager来配置多格式支持:
java
深色版本
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void configureContentNegotiation ContentNegotiationManagerBuilder builder) {builder.defaultContentNegotiationStrategy(ContentNegotiationManagerBuilder七大默认策略);}
}
响应格式化的最佳实践:
- 使用JSON作为主要数据格式,因为它轻量且易于解析
- 支持多种内容类型(如JSON、XML),通过Accept头协商
- 使用标准的HTTP状态码表示请求结果
- 对于错误响应,返回包含错误信息和代码的标准格式
7
- 使用自定义响应对象统一API响应格式
3. 关键注解与配置详解
3.1 @RestController与请求映射注解
@RestController是Spring MVC中构建RESTful API的核心注解,它相当于@Controller和@ResponseBody的组合
3
:
java
深色版本
@RestController
public class UserController {// 所有方法返回值都会被转换为响应体,无需额外使用@ResponseBody
}
请求映射注解用于将HTTP请求映射到控制器方法
3
:
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {@GetMappingpublic ResponseEntity<List<User>> AllUsers() {// ...}@PostMappingpublic ResponseEntity<User> createUser(@RequestBody User user) {// ...}@PutMapping("{/id}")public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {// ...}@DeleteMapping("{/id}")public ResponseEntity<?>.deleteUser(@PathVariable Long id) {// ...}@PatchMapping("{/id}")public ResponseEntity<User> patchUser(@PathVariable Long id, @RequestBody UserPatch patch) {// ...}
}
请求映射注解的高级用法:
- 使用consumes指定请求内容类型
- 使用produces指定响应内容类型
- 使用method指定允许的HTTP方法
- 使用params指定请求参数条件
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {@PostMapping(consumes = "application/json",produces = "application/json",method = RequestMethod.POST,params = "action=register")public ResponseEntity<User> createUser(@RequestBody User user) {// ...}
}
关键配置:
- 在Spring Boot中,无需额外配置就可以使用这些注解
- 在传统Spring MVC中,需要配置HandlerMapping和HandlerAdapter
3.2 @RequestBody与@PathVariable的深度解析
@RequestBody用于将HTTP请求体转换为Java对象
4
:
java
深色版本
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody UserDTO userDTO) {User user = modelMapper.map(userDTO, User.class);User savedUser = userService.save(user);return ResponseEntity created(URI.create("/users/" + savedUser.getId())).body(savedUser);
}
@PathVariable用于从URL路径中提取变量值
4
:
java
深色版本
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity.notFound().build();}return ResponseEntity.ok(user);
}
@RequestBody的配置:
- 默认使用Jackson库处理JSON转换
- 可以通过配置改变转换器
- 需要处理可能的转换异常
java
深色版本
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {// 添加或修改消息转换器}
}
@PathVariable的使用场景:
- 表示资源标识符(如/users/123)
- 表示资源层级关系(如/users/123/orders)
- 表示资源筛选条件(如/users?status=active)
关键配置:
- 在Spring Boot中,无需额外配置就可以使用这些注解
- 在传统Spring MVC中,需要配置HandlerMapping和HandlerAdapter
3.3 异常处理注解与全局异常处理器
异常处理是REST API设计的重要环节,它决定了API如何处理错误情况
22
。在Spring MVC中,我们可以使用以下方式实现:
java
深色版本
@RestController
@RequestMapping("/users")
public class UserController {@GetMapping("{/id}")public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {throw new UserNotFoundException("用户不存在");}return ResponseEntity.ok(user);}
}
全局异常处理器用于统一处理API中的异常
22
:
java
深色版本
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(UserNotFoundException.class)public ResponseEntity<Error> handleUserNotFound(UserNotFoundException ex) {Error error = new Error();error码 = 404;error消息 = ex.getMessage();return ResponseEntity.status(HttpStatus NOT_FOUND).body(error);}@ExceptionHandler(AssertionError.class)public ResponseEntity<Error> handleAssertionError(AssertionError ex) {Error error = new Error();error码 = 500;error消息 = ex.getMessage();return ResponseEntity.status(HttpStatus BAD_REQUEST).body(error);}
}
自定义异常用于表示特定的业务错误
22
:
java
深色版本
public class UserNotFoundException extends RuntimeException {public UserNotFoundException(String message) {super(message);}
}
异常处理的最佳实践:
- 使用自定义异常表示业务错误
- 使用全局异常处理器统一处理异常
- 返回包含错误信息和代码的标准格式
- 根据异常类型返回合适的HTTP状态码
- 避免暴露敏感信息和内部错误细节
3.4 跨域请求处理与安全配置
跨域请求处理(CORS)是REST API设计中常见的需求,在Spring MVC中可以通过以下方式实现:
java
深色版本
@RestController
@RequestMapping("/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {// ...
}
全局CORS配置用于统一处理所有API的跨域请求
17
:
java
深色版本
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://localhost:3000", "https://example.com").allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH").allowedHeaders("*").exposedHeaders("Authorization").allowCredentials(true).maxAge(3600);}
}
安全配置是REST API设计的重要环节,在Spring Security中可以通过以下方式实现:
java
深色版本
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/users/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().csrf().disable(); // 关闭CSRF防护}@Autowiredpublic void configureGlobal plasure.antMatchers("/users/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().csrf().disable(); // 关闭CSRF防护}@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password("user").roles("USER").and().withUser("admin").password("admin").roles("ADMIN");}
}
安全配置的最佳实践:
- 使用Spring Security进行身份验证和授权
- 根据需要启用或禁用CSRF防护
- 使用HTTPS保护数据传输
- 使用JWT进行无状态认证
- 使用OAuth2进行第三方认证
4. 实战案例:用户管理API开发
4.1 用户实体类与DTO设计
用户实体类(User)表示业务层的用户数据:
java
深色版本
@Entity
@Table(name = "users")
@Data
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(length = 50, nullable = false)@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50, message = "用户名长度应在3-50之间")private String username;@Column(length = 100, nullable = false)@NotBlank(message = "密码不能为空")@Size(min = 8, max = 100, message = "密码长度应在8-100之间")private String password;@Column(length = 100, nullable = false)@Email(message = "邮箱格式不正确")private String email;@Enumerated(EnumType.STRING)@Column(length = 20)private Role role = Role USER;@Column(length = 20)@Enumerated(EnumType.STRING)private Status status = Status active;@Column@Temporal(TemporalType.TIMESTAMP)private Date createTime;@Column@Temporal(TemporalType.TIMESTAMP)private Date last登陆时间;
}
用户DTO(UserDTO)表示API响应的数据:
java
深色版本
@Data
public class UserDTO {private Long id;private String username;private String email;private String role;private String status;private String createTime;private String last登陆时间;// 转换方法public static UserDTO fromUser(User user) {UserDTO userDTO = new UserDTO();userDTO.id = user.getId();userDTO.username = user username();userDTO.email = user email();userDTO.role = user.getRole().name();userDTO.status = user.getStatus().name();userDTO.createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(user.getCreateTime());userDTO.last登陆时间 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(user LastLoginTime());return userDTO;}
}
角色枚举(Role)表示用户角色:
java
深色版本
public enum Role {USER,ADMIN,MODERATOR
}
状态枚举(Status)表示用户状态:
java
深色版本
public enum Status {活跃,不活跃,已删除
}
实体类与DTO设计的关键点:
- 实体类包含完整的业务逻辑和约束
- DTO只包含需要暴露给客户端的字段
- 使用转换方法(如fromUser)将实体转换为DTO
- 使用枚举表示状态和角色,提高代码可读性
4.2 基础CRUD接口实现
创建用户接口(POST /users):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@PostMappingpublic ResponseEntity<UserDTO> createUser(@RequestBody UserCreateDTO userCreateDTO) {// 参数校验if (userCreateDTO == null || Strings.isEmpty(userCreateDTO username())) {return ResponseEntity badRequest().body(new Error("用户名不能为空"));}// 转换为实体User user = UserCreateDTO.toUser(userCreateDTO);// 保存用户User savedUser = userService.save(user);// 返回创建的用户UserDTO userDTO = UserDTO.fromUser(savedUser);return ResponseEntity created(URI.create("/api/v1/users/" + savedUser.getId())).body(userDTO);}
}
获取用户列表接口(GET /users):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic ResponseEntity<List<UserDTO>> AllUsers() {List<User> users = userService AllUsers();List<UserDTO> userDTOS = users.stream().map(UserDTO::fromUser).collect(Collectors.toList());return ResponseEntity ok(userDTOS);}
}
获取单个用户接口(GET /users/{id}):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("{/id}")public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity notFound().build();}UserDTO userDTO = UserDTO.fromUser(user);return ResponseEntity ok(userDTO);}
}
更新用户接口(PUT /users/{id}):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@PutMapping("{/id}")public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody UserUpdateDTO userUpdateDTO) {User existingUser = userService.getUser(id);if (existingUser == null) {return ResponseEntity notFound().build();}// 转换为实体User updatedUser = UserUpdateDTO.toUser(existingUser, userUpdateDTO);// 保存用户User savedUser = userService.save(updatedUser);// 返回更新后的用户UserDTO userDTO = UserDTO.fromUser(savedUser);return ResponseEntity ok(userDTO);}
}
删除用户接口(DELETE /users/{id}):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@DeleteMapping("{/id}")public ResponseEntity<?>.deleteUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity notFound().build();}userService.delete(user);return ResponseEntity noContent().build();}
}
基础CRUD接口的关键点:
- 使用标准的HTTP方法表示操作(GET、POST、PUT、DELETE)
- 使用标准的HTTP状态码表示结果(200、201、204、404、400)
- 使用DTO解耦业务实体和API响应
- 使用ResponseEntity返回带有状态码的响应
- 使用参数校验确保输入数据的有效性
4.3 分页与复杂查询接口
分页接口(GET /users?page=0&size=10):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic ResponseEntity<Page<UserDTO>>页码, @RequestParam(required = false, defaultValue = "10") Integer大小,@RequestParam(required = false) String name,@RequestParam(required = false) String email,@RequestParam(required = false) String role,@RequestParam(required = false) String status) {// 构建分页参数Pageable pageable = PageRequest ofPage(page, size);// 构建查询条件Example<User> example = Example of(User.class).with ignoreNulls().where(name != null ? Example where username(). containing(name) : null,email != null ? Example where email(). containing(email) : null,role != null ? Example where role(). eq(Role.valueOf(role)) : null,status != null ? Example where status(). eq(Status.valueOf(status)) : null);// 查询用户Page<User> users = userService AllUsers(example, pageable);// 转换为DTOPage<UserDTO> userDTOS = users.map(UserDTO::fromUser);return ResponseEntity ok(userDTOS);}
}
复杂查询接口(GET /users?sort=name&order=asc):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic ResponseEntity<List<UserDTO>>查询(@RequestParam(required = false) String name,@RequestParam(required = false) String email,@RequestParam(required = false) String role,@RequestParam(required = false) String status,@RequestParam(required = false) String sort,@RequestParam(required = false) String order) {// 构建查询条件Example<User> example = Example of(User.class).with ignoreNulls().where(name != null ? Example where username(). containing(name) : null,email != null ? Example where email(). containing(email) : null,role != null ? Example where role(). eq(Role.valueOf(role)) : null,status != null ? Example where status(). eq(Status.valueOf(status)) : null);// 构建排序条件Sort sort = Sort by((sort != null && order != null) ?new Sort(Sort Order.valueOf(order), sort) : null);// 查询用户List<User> users = userService AllUsers(example, sort);// 转换为DTOList<UserDTO> userDTOS = users.stream().map(UserDTO::fromUser).collect(Collectors.toList());return ResponseEntity ok(userDTOS);}
}
分页与复杂查询的关键点:
- 使用Pageable参数处理分页
- 使用Example参数处理复杂查询条件
- 使用Sort参数处理排序
- 使用参数校验确保输入数据的有效性
- 返回包含分页信息的响应
4.4 全局异常处理与自定义错误响应
全局异常处理器(GlobalExceptionHandler)统一处理API异常
22
:
java
深色版本
@RestControllerAdvice
public class GlobalExceptionHandler {// 处理用户不存在异常@ExceptionHandler(UserNotFoundException.class)public ResponseEntity<Error> handleUserNotFound(UserNotFoundException ex) {return createErrorEntity(HttpStatus NOT_FOUND, ex.getMessage());}// 处理参数校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Error> handleValidationException(MethodArgumentNotValidException ex) {String message = ex.getBindingResult(). All errors(). get(0).DEFAULT Message();return createErrorEntity(HttpStatus BAD_REQUEST, message);}// 处理一般异常@ExceptionHandler(Exception.class)public ResponseEntity<Error> handleGeneralException(Exception ex) {return createErrorEntity(HttpStatus INTERNAL_SERVER_ERROR, "服务器内部错误");}// 创建错误响应实体private ResponseEntity<Error> createErrorEntity(HttpStatus status, String message) {Error error = new Error();error码 = status.value();error消息 = message;return ResponseEntity.status(status).body(error);}
}
自定义错误对象(Error)表示API错误信息
13
:
java
深色版本
@Data
public class Error {private Integer code; // 错误码private String message; // 错误信息private String timestamp; // 时间戳private String path; // 请求路径// 构造方法public Error() {this.timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());}public Error(Integer code, String message) {this();this.code = code;this.message = message;}
}
全局异常处理的关键点:
- 使用@RestControllerAdvice注解声明全局异常处理器
- 使用@ExceptionHandler注解处理特定异常
- 返回统一的错误格式,包含错误码和信息
- 避免暴露敏感信息和内部错误细节
- 根据异常类型返回合适的HTTP状态码
4.5 API版本控制策略实现
URI版本控制(/api/v1/users):
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {// v1版本的接口实现
}@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {// v2版本的接口实现
}
请求头版本控制(X-API-Version: v1):
java
深色版本
// 自定义版本条件
public class VersionCondition implements RequestCondition<VersionCondition> {private final String version;public VersionCondition(String version) {this.version = version;}@Overridepublic VersionCondition combine(VersionCondition other) {return new VersionCondition(other.version);}@Overridepublic VersionCondition getMatchingCondition(HttpServletRequest request) {String requestedVersion = request.getHeader("X-API-Version");if (version.equals(requestedVersion)) {return this;}return null;}@Overridepublic int.compareTo(VersionCondition other, HttpServletRequest request) {return this.version.compareTo(other.version);}
}// 自定义版本注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interfaceApiVersion {String value() default "v1";
}// 自定义版本映射
public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {@Overrideprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> declaringClass) {RequestMappingInfo info = super.getMappingForMethod(method, declaringClass);if (info != null) {info = info.combinedWith(new VersionCondition(method.getAnnotation(ApiVersion.class) != null ?method.getAnnotation(ApiVersion.class).value() :declaringClass.getAnnotation(ApiVersion.class) != null ?declaringClass.getAnnotation(ApiVersion.class).value() :"v1"));}return info;}
}// 注册自定义版本映射
@Configuration
public class WebConfig implements WebMvcConfigurer {@Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping() {return new VersionRequestMappingHandlerMapping();}
}// 使用版本注解的控制器
@RestController
@RequestMapping("/api/users")
@ApiVersion("v1")
public class UserControllerV1 {// v1版本的接口实现
}@RestController
@RequestMapping("/api/users")
@ApiVersion("v2")
public class UserControllerV2 {// v2版本的接口实现
}
API版本控制的关键点:
- 使用URI路径或请求头标识API版本
- 使用自定义注解和条件匹配实现版本控制
- 全局配置版本映射处理器
- 不同版本的接口可以共存,客户端通过版本标识选择
- 版本控制策略应该简单明了,易于理解
5. 设计最佳实践与常见问题
5.1 参数校验与自定义校验规则
参数校验是REST API设计的重要环节,它确保客户端传递的参数符合预期
7
:
java
深色版本
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@PostMappingpublic ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserCreateDTO userCreateDTO) {// ...}
}// 参数校验DTO
@Data
public class UserCreateDTO {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50, message = "用户名长度应在3-50之间")private String username;@NotBlank(message = "密码不能为空")@Size(min = 8, max = 100, message = "密码长度应在8-100之间")private String password;@Email(message = "邮箱格式不正确")private String email;@Enumerated(EnumType STRING)@Column(length = 20)private Role role = Role USER;// 转换方法public static User toUser(UserCreateDTO userCreateDTO) {User user = new User();user.setUsername(userCreateDTO username());user.setPassword(userCreateDTO password());user.setEmail(userCreateDTO email());user.setRole(userCreateDTO.getRole());return user;}
}
自定义校验规则(CustomValidator)用于处理更复杂的校验逻辑
7
:
java
深色版本
@Constraint(validatedBy = UniqueEmailValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface UniqueEmail {String message() default "邮箱已存在";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}// 自定义校验器
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {@Autowiredprivate UserService userService;@Overridepublic void initialize(UniqueEmail constraintAnnotation) {// 初始化}@Overridepublic boolean isValid(String email, ConstraintValidatorContext context) {if (email == null || email.trim().isEmpty()) {return true;}return userService.checkEmail Unique(email);}
}// 使用自定义校验注解
@Data
public class UserCreateDTO {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50, message = "用户名长度应在3-50之间")private String username;@NotBlank(message = "密码不能为空")@Size(min = 8, max = 100, message = "密码长度应在8-100之间")private String password;@Email(message = "邮箱格式不正确")@UniqueEmailprivate String email;@Enumerated(EnumType STRING)@Column(length = 20)private Role role = Role USER;// 转换方法public static User toUser(UserCreateDTO userCreateDTO) {User user = new User();user.setUsername(userCreateDTO username());user.setPassword(userCreateDTO password());user plemail(userCreateDTO email());user.setRole(userCreateDTO.getRole());return user;}
}