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;}
}