📫 作者简介:「子非我鱼」,专注于研究全栈
🔥 三连支持:欢迎 ❤️关注、👍点赞、👉收藏三连,支持一下博主~
文章目录
介绍
在Java的Spring框架中,@ControllerAdvice注解提供了一种机制,使得我们能够集中处理应用程序中的异常,从而实现全局异常处理。本文将介绍如何使用@ControllerAdvice注解来统一处理Java应用程序中的异常情况。
为什么需要全局异常处理?
在开发过程中,难免会遇到各种异常情况,如空指针异常、数据库访问异常等。为了提供更友好的用户体验和更好的日志记录,我们希望能够捕获这些异常并进行统一处理,而不是让它们在应用程序中传播。
使用@ControllerAdvice
@ControllerAdvice注解是Spring框架提供的一种用于处理全局异常的注解。通过在类上使用@ControllerAdvice注解,我们可以定义全局的异常处理器,并使用@ExceptionHandler注解来处理特定类型的异常。
步骤一:导入所需依赖
<!--SpringMVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!--Swagger文档技术依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- 校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
步骤二:定义统一返回封装类
/**
* 返回结果通用封装
*/
@Data
//给set方法设置返回对象,返回对象就是自己本身
@Accessors(chain = true)
//使对象可以使用builder方式进行创建
@Builder
@ApiModel(value = "Result对象",description = "通用结果返回对象")
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "响应状态码")
private int status;
@ApiModelProperty(value = "响应信息")
private String msg;
@ApiModelProperty(value = "响应结果数据")
private T data;
public static Result ok(String msg, Object data) {
return Result.builder()
.status(ResultCode.OK)
.msg(msg)
.data(data)
.build();
}
public static Result ok(int code, String msg, Object data) {
return Result.builder()
.status(code)
.msg(msg)
.data(data)
.build();
}
public static Result error(String msg) {
return Result.builder()
.status(ResultCode.ERROR)
.msg(msg)
.build();
}
public static Result error(int code, String msg) {
return Result.builder()
.status(code)
.msg(msg)
.build();
}
}
这个根据公司统一规范进行定义,我这里只做测试使用
步骤三:定义统一返回状态码
/**
* 响应状态码
*/
public class ResultCode {
public static final int OK = 20000; //操作成功
public static final int BAD_REQUEST = 400;//请求错误
public static final int NOT_FOUND = 404;//没有查询到数据
public static final int ERROR = 500;//操作失败
}
同上,根据统一规范而定
步骤四:定义全局异常处理类
import com.myqxin.common.utils.Result;
import com.myqxin.common.utils.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Set;
@Slf4j
@ControllerAdvice
public class BaseExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(BaseExceptionHandler.class);
/**
* 系统异常
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handler(Exception e) {
//获取异常信息,获取异常堆栈的完整异常信息
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
//日志输出异常详情
log.error(sw.toString());
return Result.error(ResultCode.ERROR, "服务异常,请稍候再试");
}
/**
* 请求方式不支持
*/
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public Result handleException(HttpRequestMethodNotSupportedException e) {
log.error(e.getMessage(), e);
return Result.error("不支持' " + e.getMethod() + "'请求");
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public Result notFount(RuntimeException e) {
log.error("运行时异常:", e);
return Result.error("运行时异常:" + e.getMessage());
}
/**
* 处理校验异常,对于参数类型的数据的校验异常
*
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public Result handler(ConstraintViolationException e) {
StringBuffer sb = new StringBuffer();
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
constraintViolations.forEach(msg -> sb.append(msg.getMessage()).append(";"));
log.error("get校验方式:" + sb.toString());
return Result.error(ResultCode.BAD_REQUEST, sb.toString());
}
/**
* 处理校验异常,对于对象类型的数据的校验异常
*
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Result handler(MethodArgumentNotValidException e) {
StringBuffer sb = new StringBuffer();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
log.error("post校验方式:" + sb.toString());
return Result.error(ResultCode.BAD_REQUEST, sb.toString());
}
/**
* 校验异常
*/
@ExceptionHandler(BindException.class)
public Result validationExceptionHandler(BindException e) {
StringBuffer sb = new StringBuffer();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
return Result.error(ResultCode.BAD_REQUEST, sb.toString());
}
}
步骤五:实体类测试,Myqxin.java
@Data
public class Myqxin {
@NotEmpty(message = "用户名不能为空")
private String username;
private String password;
@Email(message = "邮箱格式有误")
private String email;
private String mobile;
private Integer age;
}
步骤六:Controller类测试
@RestController
@RequestMapping("/myqxin")
@Validated // 在类上只是校验非对象的
public class UserController {
@PostMapping("/post")
// 这种对象方式,就需要单独加上 @Validated,否则不生效
public Result<Myqxin> postTest(@Validated @RequestBody Myqxin myqxin){
System.out.println("冷言"+myqxin);
return Result.ok("",myqxin);
}
@GetMapping("/get")
public Result<String> getTest(@NotBlank(message = "ID不能为空") String id){
System.out.println("冷言"+id);
return Result.ok("",id);
}
}
测试效果
spring提供的常用校验注解
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
3.hiberate validation 注解
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@URL(protocol=,host=, port=, regexp=, flags=) 合法的url
主要区分下@NotNull @NotEmpty @NotBlank 3个注解的区别:
@NotNull 任何对象的value不能为null
@NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null
@NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0
有些时候这些注解并不能完全满足我的校验需求,这时候就需要我们自定义注解
1,新建一个统一管理校验规则的类
public class ValidUtil {
// 手机号校验正则
public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";
public static final String MOBILE_MSG = "手机号格式错误";
// 用户账号校验正则
public static final String USERNAME_REGX = "^[a-zA-z]\\w{5,19}$";
public static final String USERNAME_MSG = "账号必须是字母开头,字母,数字,下划线组成,6-20位";
}
2,定义注解
@Constraint(validatedBy = MobileValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface Mobile {
// 手机号的校验格式
String regexp() default ValidUtil.MOBILE_REGX;
// 手机号的提示信息
String message() default ValidUtil.MOBILE_MSG;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@code @Email} constraints on the same element.
*
* @see
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
Mobile[] value();
}
}
3,实现规则
// 第一个泛型是手机号校验的注解,第二个泛型是校验参数的类型
public class MobileValidator implements ConstraintValidator<Mobile,String> {
private String regexp;
// 初始化方法
@Override
public void initialize(Mobile constraintAnnotation) {
// 获取校验的正则表达式
this.regexp = constraintAnnotation.regexp();
}
// 校验,返回true 通过 返回false 失败
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null){
return true;
}
return value.matches(regexp);
}
}
实例效果:
结论
通过使用@ControllerAdvice注解,我们能够实现Java应用程序的全局异常处理,集中处理各种异常情况,为用户提供更友好的错误信息,同时也更容易进行日志记录和排查问题。这是一个在实际项目中非常有用的技术,能够提高应用程序的可维护性和用户体验。