符合 REST 架构风格的 API,能够使服务具有良好的可扩展性和松耦合性。
RESTful API 的核心概念包括资源、URI(统一资源标识符)、无状态性,以及使用标准的 HTTP 方法来对资源进行操作。
HTTP 方法在 RESTful API 中用于定义资源操作:
GET
用于获取资源数据,不会对服务器端的数据造成任何修改。示例:GET /api/users/1
用于获取 ID 为 1 的用户信息。POST
用于在服务器端创建新资源,通常会在成功创建后返回新资源的 URI。示例:POST /api/users
用于创建一个新用户。PUT
用于更新资源,通常用于更新整个资源或根据请求内容对资源进行修改。示例:PUT /api/users/1
用于更新 ID 为 1 的用户信息。PATCH
用于部分更新资源,区别于 PUT
,PATCH
只更新提供的字段。示例:PATCH /api/users/1
用于更新 ID 为 1 的用户的部分信息。DELETE
用于删除资源。示例:DELETE /api/users/1
用于删除 ID 为 1 的用户。状态码在 RESTful API 中用于表示请求结果的状态:
200 OK
表示请求成功,返回期望的响应数据。201 Created
表示成功创建资源,返回新资源的 URI。204 No Content
表示请求成功,但不返回任何内容,通常用于删除操作。400 Bad Request
表示客户端请求无效,通常由于请求参数有误。401 Unauthorized
表示请求未经授权,通常因为身份验证失败。403 Forbidden
表示请求被服务器拒绝,通常因为权限不足。404 Not Found
表示请求的资源不存在。500 Internal Server Error
表示服务器内部错误。RESTful API 的 URI 设计应清晰明了、符合层次结构,并使用名词表示资源。通常使用名词的复数形式表示资源集合,例如 /api/users
表示用户资源。路径设计中避免使用动词,如 /getUser
,而应通过 HTTP 方法表达操作类型。版本控制可以通过在 URI 中添加版本号(如 /api/v1/users
),或者通过请求头指定
在 RESTful API 的版本控制中,使用 URI 版本控制或请求头版本控制是较为常见的两种策略:
/api/v1/users
。这种方式简单直观,易于理解,但会导致 URI 更加冗长,并且如果版本众多,管理起来会比较复杂。Accept: application/vnd.example.v1+json
。这种方式更加灵活,URI 不会随着版本的增加而变得混乱,但客户端需要明确设置请求头。在实际应用中,RESTful API 通常需要实现基本的 CRUD 操作,并遵循最佳实践以确保 API 的易用性、可维护性和安全性。
实现 CRUD 操作的一个基本示例使用 Spring Boot 框架。以下是如何在 Spring Boot 中创建一个完整的 RESTful CRUD 操作示例。
创建实体类 User
表示用户资源:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增策略
private Long id;
@NotBlank(message = "Name is mandatory") // 校验字段不为空
private String name;
@Email(message = "Email should be valid") // 校验邮箱格式
private String email;
// Getters 和 Setters
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;
}
}
在数据访问层,创建一个接口 UserRepository
,继承自 JpaRepository
,用于基本的 CRUD 操作:
public interface UserRepository extends JpaRepository<User, Long> {
// 自定义查询方法:按名称查找用户
List<User> findByName(String name);
}
服务层的实现用于处理业务逻辑。UserService
提供了用户的增删改查操作:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 创建用户
public User createUser(User user) {
return userRepository.save(user);
}
// 根据ID获取用户
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
// 获取所有用户
public List<User> getAllUsers() {
return userRepository.findAll();
}
// 更新用户
public User updateUser(Long id, User userDetails) {
User user = getUserById(id);
if (user != null) {
user.setName(userDetails.getName());
user.setEmail(userDetails.getEmail());
return userRepository.save(user);
}
return null;
}
// 删除用户
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
控制器层 UserController
负责处理 HTTP 请求并返回相应的响应数据:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping // 创建用户
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
@GetMapping("/{id}") // 根据ID获取用户
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@GetMapping // 获取所有用户
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return new ResponseEntity<>(users, HttpStatus.OK);
}
@PutMapping("/{id}") // 更新用户
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
User updatedUser = userService.updateUser(id, userDetails);
if (updatedUser != null) {
return new ResponseEntity<>(updatedUser, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@DeleteMapping("/{id}") // 删除用户
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
RESTful API 设计的最佳实践包括:
拦截器在 Web 应用中是一种特殊的过滤器,用于在请求到达控制器之前或返回响应之前执行某些逻辑。拦截器通常用于日志记录、权限验证、数据预处理、异常处理等场景。
在 Spring MVC 中,可以通过实现 HandlerInterceptor
接口来定义自定义拦截器。拦截器通常有三个方法:
preHandle
:在请求到达控制器之前调用。可以用于身份验证、权限检查、日志记录等操作。如果该方法返回 false
,请求将不会继续向下传递。postHandle
:在控制器执行完逻辑之后,但在视图渲染之前调用。可以用于修改视图或向模型中添加通用数据。afterCompletion
:在请求完全处理完毕后调用,通常用于清理资源、记录日志等操作。在 Spring Boot 中,拦截器的配置可以通过创建一个配置类并实现 WebMvcConfigurer
接口来完成:
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Request URI: " + request.getRequestURI());
return true; // 继续处理请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Post Handle method is Calling");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
System.out.println("Request and Response is completed");
}
}
通过创建一个配置类并将拦截器注册到 Spring 的拦截器链中:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor).addPathPatterns("/**");
}
}
使用 @Validated
注解可以结合拦截器实现数据校验。例如,在拦截器中可以结合方法参数中的注解进行校验,确保请求数据的合法性。
全局异常处理可以确保应用程序在出现异常时返回统一的错误响应,从而提高应用的健壮性和用户体验。在 Spring Boot 中,可以使用 @ControllerAdvice
和 @ExceptionHandler
注解实现全局异常处理。
通过定义一个全局异常处理类,并使用 @ControllerAdvice
注解标识,该类会捕获并处理应用程序中的所有异常:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
自定义异常类 ResourceNotFoundException
用于表示资源未找到的情况:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
通过这种方式,应用程序的异常处理逻辑集中在一个地方,便于管理和维护。异常处理程序根据捕获的异常类型,返回适当的 HTTP 状态码和错误信息。
使用 ResponseEntity
可以灵活地构建标准化的错误响应对象。在实际应用中,为了提高 API 的可用性和可维护性,统一的错误响应格式是一个良好的实践。通常会包含以下信息:
timestamp
:错误发生的时间。message
:错误的具体信息。details
:错误的详细描述,例如请求的 URI。示例的错误响应对象 ErrorDetails
类如下:
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
// Getters 和 Setters
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
通过全局异常处理器,所有未捕获的异常都会被自动处理,并返回标准化的错误响应。这样可以确保 API 对外暴露的接口稳定,同时为客户端提供一致的错误信息格式,方便调试和错误处理。
在实际应用中,有时我们需要对输入数据进行复杂的校验,而这些校验可能无法通过简单的注解(如 @NotNull
、@Email
等)来实现。此时可以通过实现自定义校验器来完成。
创建一个自定义校验注解,如 @ValidPassword
,用于验证用户密码的复杂性(例如必须包含大写字母、小写字母、数字和特殊字符):
ValidPassword
:@Constraint(validatedBy = PasswordValidator.class) // 绑定自定义校验器
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidPassword {
String message() default "Invalid password"; // 默认错误信息
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
private Pattern pattern;
private Matcher matcher;
private static final String PASSWORD_PATTERN =
"^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$";
@Override
public void initialize(ValidPassword constraintAnnotation) {
pattern = Pattern.compile(PASSWORD_PATTERN); // 初始化密码匹配正则
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (password == null) {
return false; // 密码不能为空
}
matcher = pattern.matcher(password);
return matcher.matches(); // 返回匹配结果
}
}
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Name is mandatory")
private String name;
@Email(message = "Email should be valid")
private String email;
@ValidPassword // 使用自定义的密码校验注解
private String password;
// Getters 和 Setters
}
当客户端请求中包含无效密码时,自定义校验器会触发相应的错误,并在响应中返回错误信息。通过自定义校验器,可以灵活地满足各种复杂的校验需求。
使用 @Validated
注解结合 BindingResult
对象,可以在控制器中实现更加细粒度的错误处理逻辑。
示例中,我们在创建用户时应用这些技术:
@PostMapping("/users")
public ResponseEntity<?> createUser(@Validated @RequestBody User user, BindingResult result) {
if (result.hasErrors()) { // 检查校验结果是否有错误
List<String> errors = result.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage) // 提取所有错误消息
.collect(Collectors.toList());
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); // 返回错误响应
}
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
通过 BindingResult
对象,可以获取所有的校验错误,并返回给客户端。这样,客户端可以根据具体的错误消息提示用户输入的错误信息。
跨域资源共享(CORS)是一种浏览器安全机制,用于防止不受信任的域对资源的未经授权的访问。RESTful API 通常需要支持 CORS,以允许来自不同域的客户端(如 Web 应用程序)与服务器通信。
在 Spring Boot 中,可以使用 @CrossOrigin
注解启用 CORS。例如,在控制器方法上添加此注解:
@CrossOrigin(origins = "http://example.com") // 允许来自指定来源的跨域请求
@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return new ResponseEntity<>(users, HttpStatus.OK);
}
或者在全局配置中启用 CORS 支持:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许跨域的路径
.allowedOrigins("http://example.com") // 允许的来源
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的 HTTP 方法
.allowCredentials(true); // 是否允许发送凭证(如 cookies)
}
}
通过正确配置 CORS,客户端可以安全地访问 RESTful API,同时确保数据和用户隐私的安全性。
© 著作权归作者所有
本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。