SpringBoot自定义异常,优雅解决业务逻辑中的错误

本文介绍了一种优雅处理Java异常的方法,包括自定义枚举错误信息、创建自定义异常类、全局异常处理策略及其实现。通过这些技术,可以显著提升用户体验,避免因技术术语直接暴露给用户而导致的负面反馈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  • 在我们开发中,总会碰到一些异常 ---------- 运行时异常(不受检异常):RuntimeException类极其子类表示JVM在运行期间可能出现的错误。编译器不会检查此类异常,并且不要求处理异常,比如用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。

  • 对于这些异常,如果返回给前端,会给用户代码很不好的体验,因为用户不知道 NullPointerException 等异常的意思,只会吐槽我们的项目,严重会导致用户流失,下面我们优雅的解决程序运行中的一场

  • 设计

    1. 自定义枚举类

    • 使用枚举统一管理我们的错误信息
    public enum ResultEnum {
    
        UNKNOWN_ERROR(-100, "未知错误"),
        NEED_LOGIN(-1, "未登录"),
        REPEAT_REGISTER(-2, "该用户已注册"),
        USER_NOT_EXIST(-3, "不存在该用户"),
        PASSWORD_ERROR(-4, "密码错误"),
        EMPTY_USERNAME(-5, "用户名为空"),
        EMPTY_PASSWORD(-6, "密码为空"),
        SUCCESS(0, "success"),
        SYSTEM_ERROR(500,"手速太快了,慢点");
    
        private Integer code;
    
        private String msg;
    
        private ResultEnum(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    }
    

2. 自定义异常

package com.yxl.exception;


import com.yxl.enums.ResultEnum;

/**
 * @Author:byteblogs
 * @Date:2018/09/27 12:52
 */
public class BusinessException extends RuntimeException {

    private int code;

    private String errMsg;

    public  BusinessException(ResultEnum resultEnum){
        this.code = resultEnum.getCode();
        this.errMsg = resultEnum.getMsg();
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }
}

3. 异常全局处理

  • 创建handler文件夹

  • 新增ExcepetionHandler类

  • 加入 @ControllerAdvice 注解

**加粗样式**

  • 对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不仅限于此。ControllerAdvice拆分开来就是Controller
    Advice,关于Advice,前面我们讲解Spring
    Aop时讲到,其是用于封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ContrllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行“切面”环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:
    结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的;

处理全局异常

  • ResultBody 是返回的项目统一格式
package com.yxl.handler;

import com.yxl.enums.ResultEnum;
import com.yxl.exception.BusinessException;
import com.yxl.po.ResponseMessage;
import com.yxl.po.ResultBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;

@ControllerAdvice
public class ExceptionHandler {

    private final static Logger logger= LoggerFactory.getLogger(ExceptionHandler.class);

    /**
     * 处理其他异常
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseMessage handel(Exception e){
        if(e instanceof BusinessException){
            BusinessException myException =(BusinessException)e;
            return ResultBody.error( myException.getCode(),myException.getMessage());
        }else {
            logger.error("[系统异常] {}",e);
            return ResultBody.error(ResultEnum.SYSTEM_ERROR);
        }
    }


    /**
     * 处理BusinessException异常
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public ResponseMessage busin(BusinessException e) {
        return ResultBody.error(e.getCode(), e.getErrMsg());
    }

    /**
     * 处理空指针的异常
     *
     * @param req
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(value = NullPointerException.class)
    @ResponseBody
    public ResponseMessage exceptionHandler(HttpServletRequest req, NullPointerException e) {
        logger.error("发生空指针异常!原因是:", e);
        return ResultBody.error(ResultEnum.SYSTEM_ERROR);
    }
    /**
     * 处理参数校验异常
     *
     * @param req
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseMessage exceptionHandler(HttpServletRequest req, MethodArgumentNotValidException  e) {

        logger.error("参数校验异常:", e);
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return ResultBody.error(ResultEnum.SYSTEM_ERROR);
    }

    /**
     * 处理参数校验异常 --Json 转换异常
     * @param req
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(value = HttpMessageNotReadableException.class)
    @ResponseBody
    public ResponseMessage exceptionHandler(HttpServletRequest req, HttpMessageNotReadableException e) {
        logger.error("参数校验异常-json转换异常:", e);
        return ResultBody.error(ResultEnum.SYSTEM_ERROR);
    }

}

4. 使用

  @GetMapping("/test2")
    public ResponseMessage test2(@ApiParam(value = "id")@RequestParam(required = false) Long id) {
        //如果等于空抛出异常
        if(Objects.isNull(id)){
            throw new BusinessException(ResultEnum.EMPTY_USERNAME);
        }
        return ResultBody.success();
    }
  • 结果 抛出了我们自定义的枚举异常
    在这里插入图片描述
### 实现 Spring Boot 自定义注解验证并处理级联属性 在 Spring Boot 应用程序中,通过自定义注解来增强数据验证功能是一种常见做法。这不仅提高了代码的可读性和维护性,还使得业务逻辑更加清晰。 #### 定义自定义注解 为了创建一个新的约束注解 `@ValidEmail` 来验证电子邮件地址的有效性: ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Constraint(validatedBy = EmailValidator.class) @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface ValidEmail { String message() default "Invalid email address"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 此部分描述了如何声明一个名为 `@ValidEmail` 的新注解[^2]。 #### 创建对应的验证器类 接着需要编写具体的验证逻辑,在这里是一个简单的正则表达式匹配: ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class EmailValidator implements ConstraintValidator<ValidEmail, String> { private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; @Override public void initialize(final ValidEmail constraintAnnotation) {} @Override public boolean isValid(String value, ConstraintValidatorContext context){ return value != null && value.matches(EMAIL_PATTERN); } } ``` 这段代码实现了针对上述自定义注解的具体检验行为。 #### 处理嵌套对象中的字段验证(即级联属性) 当涉及到复杂的数据结构时,比如包含其他实体的对象列表或集合,则可以通过启用级联验证机制来进行更深层次的检查。只需简单地标记父级 Bean 上的相关成员变量为 `@Valid` 或者 `@Validated` 即可激活这一特性。 假设有一个用户注册表单模型 UserForm.java 如下所示: ```java import org.hibernate.validator.constraints.Length; import javax.validation.Valid; import java.util.List; public class UserForm { @Length(min=3,max=50,message="Username must be between 3 and 50 characters long.") private String username; @ValidEmail(message = "Please provide valid e-mail") private String emailAddress; @Valid private Address shippingAddress; // 嵌入另一个带有自己验证规则的bean @Valid List<ContactInfo> contactInfos; // 对象列表同样适用 // getters & setters... } ``` 这里的 `shippingAddress` 和 `contactInfos` 字段都标记有 `@Valid` 注释,这意味着它们内部所含有的任何受支持类型的子组件都将被递归地加以验证[^1]。 #### 配置全局异常处理器 为了让应用程序能够优雅地响应违反约束的情况,通常会配置一个统一的错误消息返回接口。可以利用 `@ControllerAdvice` 结合 `@ExceptionHandler(MethodArgumentNotValidException.class)` 方法捕获所有来自控制器层面上发生的参数绑定失败事件,并将其转换成友好的 JSON 格式的反馈信息给前端调用方。 ```java @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) protected ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { Map<String,Object> body=new HashMap<>(); body.put("timestamp",new Date()); body.put("status",HttpStatus.BAD_REQUEST.value()); List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); body.put("errors",errors); return new ResponseEntity<>(body,status); } } ``` 以上就是关于如何在 Spring Boot 中实现自定义注解进行校验以及处理级联属性的一个完整示例教程。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值