使用@ControllerAdvice实现全局异常处理

41 篇文章 0 订阅
11 篇文章 2 订阅

在这里插入图片描述

📫 作者简介:「子非我鱼」,专注于研究全栈
🔥 三连支持:欢迎 ❤️关注、👍点赞、👉收藏三连,支持一下博主~

介绍

在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应用程序的全局异常处理,集中处理各种异常情况,为用户提供更友好的错误信息,同时也更容易进行日志记录和排查问题。这是一个在实际项目中非常有用的技术,能够提高应用程序的可维护性和用户体验。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
如果不想使用@ControllerAdvice注解,我们也可以通过实现HandlerExceptionResolver接口来实现全局异常处理。具体步骤如下: 1. 创建一个类,实现HandlerExceptionResolver接口。 2. 在类中实现resolveException方法,该方法会在全局异常发生时被执行。 3. 在resolveException方法中,我们可以根据不同的异常类型进行不同的处理,比如返回自定义错误信息或者跳转到指定页面。 4. 最后,我们需要将该类注册到Spring框架中,可以通过在配置文件中进行配置或者使用注解的方式进行注册。 以下是一个简单的示例: ```java public class GlobalExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 返回自定义错误信息 ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMsg", "系统错误,请稍后重试!"); modelAndView.setViewName("error"); return modelAndView; } } ``` 在上述代码中,我们实现了HandlerExceptionResolver接口并重写了resolveException方法。在该方法中,我们将错误信息存入ModelAndView中,并将viewName设置为"error",表示跳转到error页面显示错误信息。 最后,我们需要将该类注册到Spring框架中。可以通过在配置文件中进行配置或者使用注解的方式进行注册。例如,在Spring配置文件中添加如下配置: ```xml <bean id="globalExceptionHandler" class="com.example.GlobalExceptionHandler"/> ``` 在上述配置中,我们将GlobalExceptionHandler类注册为Spring的一个bean,并使用该类处理全局异常

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非我鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值