spring项目全局异常处理机制设计

1,896 阅读6分钟

本文分享一个使用于spring boot和spring web的全局异常处理机制.

本示例是用spring boot项目测试的

1.设计思想

整个异常处理机制,是针对于controller层进行的自动异常处理,如果是rpc的接口,则不适用.

整个机制的核心,是通过spring提供的controller增强来扩展处理异常

异常处理机制可以独立成单独的jar,只需要引入以后,加入一个注册的处理类,即可使用

可以处理系统异常和业务异常,也支持处理自定义异常

2.使用的技术

2.1 @RestControllerAdvice/@ControllerAdvice

@ControllerAdvice或@RestControllerAdvice 应用在类上, 分别扩展@Controller请求和@RestController请求,

其中@InitBinder可以在所有@RequestMapping注解的方法处理之前进行初始化处理

@ModelAttribute可以把值绑定到Model中

@ExceptionHandler用来处理全局异常

示例:

@ControllerAdvice
public class MyControllerAdvice {

    @InitBinder
    public void initBinder(WebDataBinder binder) {}

    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("","");
    }
    
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Map errorHandler(Exception ex) {
        Map map = new HashMap();
        //...
        return map;
    }
}

2.2 @ExceptionHandler

@ExceptionHandler上面已经说了,可以处理全局异常,具体的用法如下:

@ExceptionHandler({Exception.class})
public JSONObject exception(Exception e){
	JSONObject object = new JSONObject();
	object.put("key",500);
	object.put("value","系统异常");
	return object;
}

3.代码实现

异常处理已一个单独的项目抽离出来,可以是spring boot项目,也可以是普通的spring web项目

3.1 项目结构

整个项目于的文件结构如下: 下面按不同的模块具体展示实现逻辑

3.2 自定义业务异常

此文件夹下主要是所有自定义的业务异常类,我示例了一个异常类,可以按照自己的需求定义一个或多个

示例代码如下:

@Data
public class BusinessException extends Exception {

    //异常码
    private Long code;
    //异常信息
    private String msg;
    //异常提醒邮件地址(可选)
    private String mailAdd = null;
    //异常提醒邮件信息(可选)
    private String mailBody;

    public BusinessException(){
        super();
        this.code = 500L;
        this.msg = "业务异常";
    }
    public BusinessException(Long code, String msg){
        super(msg);
        this.code = code;
        this.msg = msg;
    }
    public BusinessException(ExEnum exEnum){
        super(exEnum.getMsg());
        this.code = exEnum.getCode();
        this.msg = exEnum.getMsg();
    }
    public BusinessException(MainEnum mainEnum){
        super();
        this.code = 500L;
        this.msg = "业务异常";
        this.mailAdd = mainEnum.getMailAdd();
        this.mailBody = mainEnum.getMessageBody();
    }

}

异常类很简单,就是由异常码 异常信息和异常提醒邮件信息几个字段组成的; 又实现了几个常用的构造方法,已简化抛出异常时的代码.

此自定义异常有几个可优化点:

  • 字段可以扩展增删

  • 构造方法可以增删

当然,也支持在使用此异常处理机制的项目里自定义异常处理类

3.3 异常枚举

此枚举文件夹中,罗列使用到的异常类, 这里主要说明的是可以自定义异常枚举

自定义异常枚举,是项目预定义的常见异常,这样在需要排除异常类的时候,可以直接使用枚举抛出,不再需要new异常对象

这里演示了两种枚举,一种是普通的(ExEnum),一种是需要邮件提醒的(MailEnum),核心代码如下:

public enum ExEnum {

    BASE_EX(5001L,"业务异常"),
    //... 可以扩展异常类型
    ;
    private Long code;
    private String msg;
    ExEnum(Long code, String msg){
        this.code = code;
        this.msg = msg;
    }
	//...getter setter
}
public enum MailEnum {
    Business_1("zs@mail.com","系统发生异常","业务一负责人邮箱"),
    //... 可以扩展异常类型
    ;
    private String mailAdd;
    private String messageBody;
    private String msg;
    MailEnum(String mailAdd, String messageBody, String msg){
        this.mailAdd = mailAdd;
        this.messageBody = messageBody;
        this.msg = msg;
    }
	//...getter setter
}

3.4 异常处理程序

异常处理程序,是整个机制的核心,这里依据系统异常和自定义业务异常两个,分别由两个Handler处理.

核心的点就是使用@ExceptionHandler来处理不同的异常

示例代码如下:

3.4.1 系统异常处理

处理类自定义了许多default方法,处理常见的异常,也可以实现接口自己重写或新增

public interface BaseExceptionHandler {
    Logger logger = LoggerFactory.getLogger(BaseExceptionHandler.class);
    
    @ExceptionHandler({Exception.class})
    default JSONObject exception(Exception e){
        logger.error("exception-{}",e.getMessage(),e);
        JSONObject object = new JSONObject();
        object.put("key",500);
        object.put("value","系统异常");
        return object;
    }

    @ExceptionHandler({ArithmeticException.class})
    default JSONObject arithmeticException(ArithmeticException ae){
        logger.error("arithmeticException-{}",ae.getMessage(),ae);
        JSONObject object = new JSONObject();
        object.put("key",500);
        object.put("value","算术异常" + ae.getMessage());
        return object;
    }
    
    //TODO 可以扩展其他类型的异常
}
3.4.2 自定义业务异常处理
public interface BusinessExceptionHandler {
    Logger logger = LoggerFactory.getLogger(BusinessExceptionHandler.class);

    @ExceptionHandler({BusinessException.class})
    default JSONObject businessException(BusinessException be){
        logger.error("businessException-{}",be.getMessage(),be);
        //需要执行异常邮件通知
        if(be.getMailAdd() != null){
            logger.info("模拟邮件发送动作,为{}发送邮件,内容为:{};{}", be.getMailAdd(), be.getMailBody(), be.getMessage());
        }
        JSONObject object = new JSONObject();
        object.put("key",500);
        object.put("value","异常:" + be.getMessage());
        return object;
    }

    //TODO 可以定义其他类型的异常,并进行处理
}

3.5 自定义断言

自定义断言是为了减少代码中的if判断,使得代码更加关注业务逻辑的处理,这里只示例一个,用以展示思路,可以按照自己项目的需求, 扩展

示例如下

public class ExAssert {
    /**
     * 断言对象是否为空
     * @param obj   被断言对象
     * @param ex    指定异常枚举
     * @throws BusinessException
     */
    //@SneakyThrows是为了骗过编译器,避免直接throws异常,
    //导致在使用断言的地方还需要手动抛出异常
    @SneakyThrows
    public static void isNotNull(Object obj, ExEnum ex) {
        if(obj == null){
            throw new BusinessException(ex);
        }
    }

    /**
     * 断言对象是否为空
     * @param obj   被断言对象
     * @param code  指定错误码
     * @param msg   指定错误提示信息
     * @throws BusinessException 业务异常
     */
    @SneakyThrows
    public static void isNotNull(Object obj, Long code, String msg) {
        if(obj == null){
            throw new BusinessException(code, msg);
        }
    }

    /**
     * 断言对象是否为空
     * @param obj   被断言对象
     * @param code  指定错误码
     * @param msg   指定错误提示信息
     * @throws BusinessException 业务异常
     */
    @SneakyThrows
    public static void isNotNull(Object obj, Long code, String msg, MainEnum mainEnum) {
        if(obj == null){
            throw new BusinessException(code, msg, mainEnum);
        }
    }

    //TODO 可以增加其他断言

}

4.异常处理使用示例

4.1 pom文件添加依赖

和普通的pom文件添加依赖的方法一致

<dependency>
	<groupId>jin.common</groupId>
 	<artifactId>exception</artifactId>
	<version>0.0.1-SNAPSHOT</version>
</dependency>

4.2 新增一个Handler装配异常处理程序

此类用来装配common包中提供的全局异常处理机制,需要哪几个装配哪几个

使用此Handler装配以后,此项目中所有的controller都会使用该全局异常处理机制了

@RestControllerAdvice
public class DemoBaseExceptionHandler implements BaseExceptionHandler, BusinessExceptionHandler {
    //此类默认不需要实现,如果异常处理项目已满足,则不要实现

}

4.3 自定义异常处理\覆盖异常处理

@RestControllerAdvice
public class DemoBaseExceptionHandler implements BaseExceptionHandler, BusinessExceptionHandler {
    //此类默认不需要实现,不过可以自定义异常处理机制

    //此操作会覆盖接口默认实现的处理逻辑
    @ExceptionHandler({ArithmeticException.class})
    public JSONObject arithmeticException(ArithmeticException ae) {
        JSONObject object = new JSONObject();
        object.put("key", 5008);
        object.put("value", "算术异常" + ae.getMessage());
        return object;
    }

}

4.4 使用实例代码

4.4.1 系统异常
@GetMapping("/demo")
public String demo(){
	//模拟异常
	int i = 0;
 	i = 1/i;
	return "done" + System.currentTimeMillis();
}

效果:

4.4.2 业务异常
public void demo1(Object obj) throws BusinessException {
	//模拟业务异常
    throw new BusinessException(ExEnum.BASE_EX.getCode(),ExEnum.BASE_EX.getMsg());
}

效果:

4.4.3 断言
@Override
public void demo() {
	//此代码模拟业务代码抛出异常
	ExAssert.isNotNull(null, ExEnum.BASE_EX);
}	

此机制可以处理大部分的异常,然而404等异常,无法处理,可以使用重新定向404页面的方法处理