一、异常处理
1.1Http状态码
HTTP状态码是指在HTTP通信过程中,服务器向客户端返回的响应状态。它通过3位数字构成,第一个数字定义了响应的类别,后两位数字没有具体分类作用。以下是常见的HTTP状态码及其含义:
- 1xx(信息类):请求已接收,继续处理。
- 2xx(成功类):请求已成功接收、理解和处理。
- 3xx(重定向类):需要进一步操作以完成请求。
- 4xx(客户端错误类):请求包含错误或无法完成。
- 5xx(服务器错误类):服务器无法完成显然有效的请求。常见的HTTP状态码包括:
- 200(OK):请求成功。
- 301(Moved Permanently):资源的URL已永久移动。
- 404(Not Found):无法找到请求的资源。
- 500(Internal Server Error):服务器发生了未知的错误。这只是一小部分常见的HTTP状态码,实际上还有许多其他状态码。了解HTTP状态码可以帮助开发人员更好地理解请求和响应之间的交互。
详情请见有趣的小知识(一)HTTP请求响应状态码:一份不可或缺的指南,从容面对任何请求挑战!
按REST规范,服务器端发生错误/异常/无权限等,应返回相应的HTTP状态码;前端通过状态码处理异常信息
1.2异常类
- 应用可能产生的异常
- 能够在代码中,直接捕获/处理
- 转抛为自定义非受检异常
- 转抛为支持HTTP状态码的非受检异常
- 无法在代码中,直接捕获处理的。即,Spring容器处理时产生的异常
- 统一异常处常
相关代码实现
实体类
package com.handlingexception.entity;
import lombok.Data;
@Data
public class User {
private String userName;
private String password;
}
自定义异常
package com.example.springmvcexamples.example02.handlingexception.exception;
import lombok.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MyException extends RuntimeException {
private int code;
public MyException(int code, String message) {
super(message);
this.code = code;
}
}
package com.handlingexception.controller;
import com.handlingexception.exception.MyException;
import com.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice // 表示这是一个全局异常处理器类,可以处理控制器中抛出的所有异常
public class ExceptionController02 {
// 定义一个方法,用于处理MyException类型的异常
// 使用@ExceptionHandler注解,表示这个方法用于处理MyException类型的异常
// 使用@ResponseStatus注解,表示当发生MyException异常时,返回的HTTP状态码为NOT_FOUND(404)
@ExceptionHandler(MyException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResultVO handleValidException(MyException exception) {
// 返回一个ResultVO对象,包含错误码和错误信息
return ResultVO.error(exception.getCode(), exception.getMessage());
}
}
状态类
package com.vo;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
public class ResultVO {
private int code;
private String message;
private Map<String, Object> data;
public static ResultVO success(Map<String, Object> data) {
return ResultVO.builder().code(200).data(data).build();
}
public static ResultVO error(int code, String msg) {
return ResultVO.builder().code(code).message(msg).build();
}
}
package com.vo;
public class Code {
public static final Integer readFile_OK = 20011;
public static final Integer readFile_ERR = 40110;
}
服务类
package com.handlingexception.service;
import com.handlingexception.exception.MyException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@Service
@Slf4j
public class UserService02 {
public void readFile() {
try {
Files.readString(Path.of("A:/aa.aa"));
} catch (IOException e) {
throw new MyException(500, "读取文件错误!" + e.getMessage());
}
}
}
处理类
package com.handlingexception.controller;
import com.handlingexception.entity.User;
import com.handlingexception.service.UserService02;
import com.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/example02/")
public class ExampleController02 {
@Autowired
private UserService02 userService02;
@GetMapping("exception")
public void getException() {
userService02.readFile();
}
@PostMapping("login")
public ResultVO login(@RequestBody User user) {
if (!("BO".equals(user.getUserName()) && "123456".equals(user.getPassword()))) {
return ResultVO.error(401, "用户名密码错误a");
}
return ResultVO.success(Map.of());
}
}
测试
GET http://localhost:8080/api/example02/exception
Accept: application/json
相关注解解析
@ControllerAdvice
`@ControllerAdvice`注解是Spring框架中的一个注解,用于定义全局的异常处理类。当控制器中的方法抛出异常时,`@ControllerAdvice`注解的类中的处理方法会被调用,从而实现全局异常处理。
使用`@ControllerAdvice`注解需要以下步骤:
1. 创建一个类,并使用`@ControllerAdvice`注解标注该类。
2. 在该类中定义处理方法,可以使用`@ExceptionHandler`注解来指定处理特定类型的异常。
3. 在处理方法中编写异常处理逻辑,例如记录日志、返回自定义的错误信息等。以下是一个简单的示例:
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.http.HttpStatus; // 使用@ControllerAdvice注解,表示这是一个全局异常处理器类 @ControllerAdvice public class GlobalExceptionHandler { // 处理所有异常 // 当发生异常时,会调用这个方法进行处理 // @ExceptionHandler(Exception.class)表示处理所有类型的异常 // @ResponseBody表示将方法的返回值作为响应体发送给客户端 // @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)表示设置响应状态码为500(内部服务器错误) @ExceptionHandler(Exception.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public String handleAllExceptions(Exception e) { // 返回一个包含异常信息的字符串 return "服务器内部错误:" + e.getMessage(); } // 处理特定类型的异常,例如NullPointerException // 当发生NullPointerException时,会调用这个方法进行处理 // @ExceptionHandler(NullPointerException.class)表示处理NullPointerException类型的异常 // @ResponseBody表示将方法的返回值作为响应体发送给客户端 // @ResponseStatus(HttpStatus.BAD_REQUEST)表示设置响应状态码为400(错误的请求) @ExceptionHandler(NullPointerException.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleNullPointerException(NullPointerException e) { // 返回一个包含异常信息的字符串 return "请求参数错误:" + e.getMessage(); } }
在这个示例中,我们定义了一个名为`GlobalExceptionHandler`的类,并使用`@ControllerAdvice`注解标注该类。然后,我们定义了两个处理方法,分别处理所有异常和特定类型的异常(如`NullPointerException`)。在这些处理方法中,我们可以编写自己的异常处理逻辑,例如记录日志、返回自定义的错误信息等。
@ExceptionHandler注解
@ExceptionHandler
注解是Spring框架中的一个注解,用于处理特定的异常。当在方法上使用此注解时,它将捕获由该方法抛出的异常,并执行相应的异常处理方法。这样可以避免程序因为未处理的异常而崩溃,提高程序的稳定性和可靠性。修饰声明在@ControllerAdvice类中的处理方法,处理Spring容器捕获的指定异常(全局异常处理)
@ResponseStatus注解
@ResponseStatus
注解是Spring框架中的一个注解,用于指定HTTP响应的状态码。当在方法上使用此注解时,它将覆盖控制器中定义的默认状态码
@RestControllerAdvice
@RestControllerAdvice,整合@ControllerAdvice & @ResponseBody,直接返回json数据
出现错误也全部返回200状态码,之后再解析提取错误信息的设计,是不规范的
1.3HTTP状态码异常
ResponseStatusException异常是Spring Framework在需要设置响应状态码时抛出的异常。它继承自RuntimeException类,并接受两个参数:HTTP状态码和错误消息。此异常可用于控制器方法中,以向客户端返回特定的HTTP状态码和错误消息。例如,如果用户尝试更新一个不存在的记录,则可以抛出带有状态码404(未找到)和错误消息“记录未找到”的ResponseStatusException异常。
package com.handlingexception.service;
import com.handlingexception.exception.MyException;
import com.vo.Code;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@Service
@Slf4j
public class UserService02 {
public void readFile() {
try {
Files.readString(Path.of("A:/aa.aa"));
} catch (IOException e) {
// throw new MyException(500, "读取文件错误!" + e.getMessage());
//throw new MyException(Code.readFile_ERR, "读取文件错误!" + e.getMessage());
throw new ResponseStatusException(HttpStatus.NOT_FOUND,"读取文件错误");
}
}
}
二、数据校验
2.1概述
校验用户输入信息以维护数据完整性正确性,是应用程序逻辑的重要组成部分
The Java API for JavaBean Validation 2.0.2
- 标准化了Java EE平台的约束定义/描述//验证
- 提供了校验对象/属性/方法/构造函数的功能
Bean Validation内置基本校验约束,并允许开发人员扩展/自定义校验约束
基于Bea
- 基于Java EE标准,便于替换具体实现
- 支持国际化的错误信息描述Validation(Hibernate Validator 实现)
2.2相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.3相关注解
2.4校验失败异常
实体类
package com.beanvalidation.entity;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class User03 {
private int id;
@Size(min = 2, max = 6,
message = "您输入的值为${validatedValue},用户名长度必须大于{min},小于{max}")
private String name;
@Min(value = 18,
message = "您输入的值为${validatedValue},年龄不能小于{value}")
@Max(value = 60,
message = "您输入的值为${validatedValue},年龄不能大于{value}")
private int age;
@Email(message = "Email不合法")
private String email;
}
控制类
package com.beanvalidation.controller;
import com.beanvalidation.entity.User03;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/example03/")
//@Validated
public class ExampleController03 {
@PostMapping("users")
public Map postUser(@Valid @RequestBody User03 user) {
return Map.of();
}
@GetMapping("users/{uid}/file")
public void getTypeMismatchException(@PathVariable int uid) {
}
@GetMapping("users/{owner}")
public void getViolationException(
@Size(min = 2, max = 6, message = "用户信息错误")
@PathVariable String owner) {
}
}
测试
POST http://localhost:8080/api/example03/users
Content-Type: application/json
{
"name": "T",
"age": 17,
"email": "abc"
}
改进
异常处理类
package com.beanvalidation.controller;
import com.beanvalidation.vo.ResultVO;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.util.Set;
@Slf4j
@RestControllerAdvice // 控制器增强器,用于全局异常处理
public class ExceptionController03 {
/**
* 属性校验失败异常处理器
*
* @param exception MethodArgumentNotValidException异常对象
* @return 返回一个包含错误信息的ResultVO对象
*/
@ExceptionHandler(MethodArgumentNotValidException.class) // 处理MethodArgumentNotValidException异常
public ResultVO handleValidException(MethodArgumentNotValidException exception) {
StringBuilder strBuilder = new StringBuilder(); // 用于拼接错误信息
exception.getBindingResult() // 获取绑定结果对象
.getFieldErrors() // 获取字段错误集合
.forEach(e -> { // 遍历每个字段错误
strBuilder.append(e.getField()); // 添加字段名
strBuilder.append(": "); // 添加冒号分隔符
strBuilder.append(e.getDefaultMessage()); // 添加默认错误信息
strBuilder.append("; "); // 添加分号分隔符
});
return ResultVO.error(400, strBuilder.toString());
}
}
2.5请求地址参数类型转换异常
package com.beanvalidation.controller;
import com.beanvalidation.vo.ResultVO;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.util.Set;
@Slf4j
@RestControllerAdvice
public class ExceptionController03 {
/**
* 属性校验失败异常
*
* @param exception
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO handleValidException(MethodArgumentNotValidException exception) {
StringBuilder strBuilder = new StringBuilder();
exception.getBindingResult()
.getFieldErrors()
.forEach(e -> {
strBuilder.append(e.getField());
strBuilder.append(": ");
strBuilder.append(e.getDefaultMessage());
strBuilder.append("; ");
});
return ResultVO.error(400, strBuilder.toString());
}
/**
* 请求类型转换失败异常
*
* @param exception
* @return
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResultVO handleMethodArgumentTypeMismatchException(
MethodArgumentTypeMismatchException exception,
HttpServletRequest request) {
String msg = request.getRequestURI() +
": " + "请求地址参数" + exception.getValue() + "错误";
return ResultVO.error(400, msg.toString());
}
}
String msg = request.getRequestURI() + ": " + "请求地址参数" + exception.getValue() + "错误";
:这行代码首先获取请求的URI(统一资源标识符),然后拼接上字符串": "、"请求地址参数"、exception.getValue()
(异常的值)和"错误",生成一个错误信息字符串msg
。
测试
### 请求地址参数类型转换异常
GET http://localhost:8080/api/example03/users/aaa/file
2.6json类型转换异常
异常处理顺序:先类型转换,后校验。出现任何异常,不会进入controller处理方法
// 处理InvalidFormatException异常的方法
@ExceptionHandler(InvalidFormatException.class)
public ResultVO handleInvalidFormatException(InvalidFormatException exception) {
// 创建一个StringBuilder对象,用于拼接错误信息
StringBuilder strBuilder = new StringBuilder();
// 遍历异常中的路径信息
exception.getPath()
.forEach(p -> {
// 拼接属性名、输入值和类型错误信息
strBuilder.append("属性");
strBuilder.append(p.getFieldName());
strBuilder.append(",您输入的值:").append(exception.getValue());
strBuilder.append(", 类型错误!");
});
// 返回一个包含错误信息的ResultVO对象,状态码为400
return ResultVO.error(400, strBuilder.toString());
}
InvalidFormatException异常通常表示输入数据的格式不正确,无法按照预期的格式进行解析或处理。这可能是因为数据类型不匹配、数据结构错误或者数据内容不符合规范等原因导致的。在编程中,需要对输入数据进行合法性检查,并在发现异常时抛出InvalidFormatException异常,以便调用者能够及时处理并给出相应的提示信息。
测试
### json类型转换异常
POST http://localhost:8080/api/example03/users
Content-Type: application/json
{
"name": "e",
"age": "sdsd",
"email": "sdfsdf"
}
2.7请求地址参数校验
异常处理类
/**
* 方法级参数校验失败异常
*
* @param exception ConstraintViolationException类型的异常对象
* @return 返回一个ResultVO类型的对象,包含错误信息和状态码
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResultVO handleConstraintViolationException(ConstraintViolationException exception) {
// 创建一个StringBuilder对象,用于拼接错误信息
StringBuilder strBuilder = new StringBuilder();
// 获取异常对象中的约束违反集合
Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
// 遍历约束违反集合,将每个违反的错误信息拼接到StringBuilder中
violations.forEach(v -> {
strBuilder.append(v.getMessage()).append("; ");
});
// 返回一个ResultVO对象,包含错误信息和状态码400(表示请求参数错误)
return ResultVO.error(400, strBuilder.toString());
}
ConstraintViolationException
类是Java中的一个异常类,通常用于处理违反约束条件的情况。当在程序中对数据进行验证时,如果发现数据不符合预期的约束条件,就会抛出这个异常。例如,假设我们有一个用户实体类(User),其中包含用户名和密码两个属性。我们可以为这两个属性添加一些约束条件,例如用户名不能为空,密码长度必须大于等于8个字符等。在验证用户输入的数据时,如果发现违反了这些约束条件,就可以抛出
ConstraintViolationException
异常。
配置类
package com.beanvalidation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class SecurityConfiguration {
// 定义一个名为methodValidationPostProcessor的Bean,类型为MethodValidationPostProcessor
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
// 创建一个新的MethodValidationPostProcessor实例并返回
return new MethodValidationPostProcessor();
}
}
MethodValidationPostProcessor(org.springframework.validation.beanvalidation)类,启动方法级校验
控制类
package com.beanvalidation.controller;
import com.beanvalidation.entity.User03;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/example03/")
@Validated
public class ExampleController03 {
@GetMapping("users/{owner}")
public void getViolationException(
@Size(min = 2, max = 6, message = "用户信息错误")
@PathVariable String owner) {
}
}
@Validated注解
@Validated
注解是Spring Validation框架中的一个注解,用于开启对方法参数的验证。它可以与@Valid
注解一起使用,以确保在调用带有验证注解的方法之前,先对方法参数进行验证。
测试
### 请求地址参数校验
GET http://localhost:8080/api/example03/users/s