Skip to content

异常深度剖析

项目参考地址:https://github.com/zhaobao1830/misszb

分类

异常分类Throwable分为Error与Exception

Error:一般系统级别,我们无法处理

Exception:一般应用层面,我们可以处理

Exception分为:CheckException与RuntimeException

CheckException:必须处理,如果不处理,在编译阶段就会报错

处理异常的时候,可以在当前位置进行处理,比如记录日志;也可以通过throws Exception往外抛异常,但总有一个地方要对异常进行处理

RuntimeException:运行时异常,不一定能在编译阶段发现,也不要求强制进行处理

备注

只要没有继承RuntimeException,那就是CheckException

如果异常在编译阶段就要处理,那就定义成CheckException

全局异常处理

全局异常类

GlobalExceptionAdvice.java

java
package com.zb.misszb.core;

import com.zb.misszb.core.configuration.ExceptionCodeConfiguration;
import com.zb.misszb.exception.http.HttpException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
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 org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.List;

@ControllerAdvice
public class GlobalExceptionAdvice {

    @Autowired
    private ExceptionCodeConfiguration codeConfiguration;

    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public UnifyResponse handleException(HttpServletRequest req, Exception e) {
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();
        System.out.println(e);
        System.out.println("系统出现错误!");
        UnifyResponse message = new UnifyResponse(9999, "服务器异常", method + " "+ requestUrl);
        return message;
    }

    @ExceptionHandler(HttpException.class)
    public ResponseEntity<UnifyResponse> handleHttpException(HttpServletRequest req, HttpException e) {
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();
        UnifyResponse message = new UnifyResponse(e.getCode(), codeConfiguration.getMessage(e.getCode()), method + " " + requestUrl);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpStatus httpStatus = HttpStatus.resolve(e.getHttpStatusCode());

        ResponseEntity<UnifyResponse> r = new ResponseEntity<>(message, headers, httpStatus);
        return r;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public UnifyResponse handleBeanValidation(HttpServletRequest req, MethodArgumentNotValidException e) {
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();

        // 有可能多个校验都报错,所以这里的异常用数组保存
        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        String message = this.formatAllErrorMessages(errors);

        return new UnifyResponse(10001, message,method + " " + requestUrl);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ResponseBody
    public UnifyResponse handleConstraintException(HttpServletRequest req, ConstraintViolationException e){
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();
        String message = e.getMessage();

        return new UnifyResponse(10001, message, method + " " + requestUrl);
    }

    // 拼接多个异常信息
    private String formatAllErrorMessages(List<ObjectError> errors) {
        StringBuffer errorMsg = new StringBuffer();
        errors.forEach(error ->
                errorMsg.append(error.getDefaultMessage()).append(';')
        );
        return errorMsg.toString();
    }
}

备注

在全局异常类里,定义处理Exception、HttpException、MethodArgumentNotValidException、ConstraintViolationException四种异常

备注

在全局异常配置类里,一定要把MethodArgumentNotValidException和ConstraintViolationException俩种异常都处理

MethodArgumentNotValidException 和ConstraintViolationException 都是用于处理参数校验异常的异常类

俩个异常类的区别,网上找的资料看不懂

备注

为什么处理HttpException和其他异常时,信息的返回操作不一样?

其他的异常处理,直接返回对象就行

但HttpException异常还有多个子异常,需要自定义HttpStatus、ContentType,这时就需要用到ResponseEntity

本项目里,我们自定义了ForbiddenException、NotFoundException等异常类,都是HttpException异常类的子类,是按照返回的httpStatusCode码来定义的,具体查看项目

使用的时候,直接throw ForbiddenException("错误码")就行

@ControllerAdvice、@ExceptionHandler、@ResponseStatus

@ControllerAdvice用来注册全局异常类

@ExceptionHandler用来在方法上指定监听哪种异常,比如@ExceptionHandler(HttpException.class),监听的就是HttpException异常,

只要请求的方法里报HttpException类以及子类的异常,就会被这个方法捕获

使用@ResponseStatus注解指定返回的错误码,比如系统错误,就返回500,不要统一都是200

为什么要把接口返回的信息都变成异常来处理?

有的项目里,会把返回信息(比如没有找到、查询数据为空等信息)和异常分开处理,缺点是:需要区分什么是异常,什么是正常的返回信息

还有一种处理方式,就是所有的返回都按异常处理,已知异常就返回message信息和code码,未知异常除了返回信息,还会将错误保存在日志文件里

备注

在我找到的开源项目里,返回前端的结果有俩种写法:

第一种、封装一个响应对象,里面包含status、msg、data属性,代码里传入相应的值,返回给前端

第二种、定义响应对象和异常类,正常的结果通过响应对象返回,如果没有结果、参数有问题、异常等情况,统一用封装的异常类处理

我现在也不知道这俩种方法哪种更好

配置文件

在开发项目中,建议将异常信息按照不同的业务单独放到不同的配置文件.properties中;通过类来获取配置文件里的信息

为什么推荐把异常信息写在配置文件?

一、异常信息散落在不同的代码文件里,不好管理

二、不好进行国际化处理,国际化针对的就是文本信息中的不同语言

使用类来获取配置文件里的信息

ExceptionCodeConfiguration.java

java
package com.lin.missyou.core.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;


@ConfigurationProperties(prefix = "lin")
@PropertySource(value = "classpath:config/exception-code.properties")
@Component
public class ExceptionCodeConfiguration {

    private Map<Integer, String> codes = new HashMap<>();

    public Map<Integer, String> getCodes() {
        return codes;
    }

    public void setCodes(Map<Integer, String> codes) {
        this.codes = codes;
    }


    public String getMessage(int code){
        String message = codes.get(code);
        return message;
    }
}

使用

java
@Autowired
private ExceptionCodeConfiguration codeConfiguration;

codeConfiguration.getMessage(code);

说明:

一、使用@PropertySource注解将类和配置文件关联起来

java
@PropertySource(value = "classpath:config/exception-code.properties")

Image text

传入的参数是配置文件exception-code.properties所在的目录

Image text

这样就可以把类和配置文件关联起来,当传入自定义的codes值,会返回对应的文字

比如传入10000,就会返回通用异常

二、使用@ConfigurationProperties定义前缀

lin.codes[10000] = 通用异常

@ConfigurationProperties(prefix = "lin")

配置文件里,codes前面的前缀是lin,在获取配置信息的类上要加注解@ConfigurationProperties(prefix = "lin")

第三、配置文件里的信息不用加引号

lin.codes[10000] = 通用异常

通用异常不用加引号,不然会报错

如有转载或 CV 的请标注本站原文地址