0
点赞
收藏
分享

微信扫一扫

互联网大厂是如何处理全局异常的?



目录


  • 一、为何要处理全局异常?
  • 二、开发环境
  • 三、添加依赖
  • 四、自定义异常错误类
  • 4.1 自定义异常基础接口类
  • 4.2 通用异常信息枚举类
  • 4.3 业务异常信息枚举类
  • 4.4 自定义业务异常类
  • 五、接口返回统一格式
  • 六、全局异常处理
  • 七、测试
  • 7.1 辅助类
  • 7.2 测试结果


一、为何要处理全局异常?


在平常项目开发过程中,程序难免会出现运行时异常,或者业务异常。难道要针对每一处可能出现的异常进行编写代码进行处理?或者直接不处理异常,将一大屏堆满英文的异常信息显示给用户?那用户体验性是何等极差。

所以,当程序抛异常时,为了​​日志的可读性​​,​​排查 Bug简单​​,以及​​更好的用户体验性​​,所以我们要对全局异常进行处理。


二、开发环境



  1. JDK 1.8 或者1.8以上
  2. Springboot (此演示版本为 Springboot 2.1.18.RELEASE)
  3. Gradle (当然也可用Maven,其实目的都是为构建项目,管理依赖等)


三、添加依赖

plugins {
id "org.springframework.boot" version "2.1.18.RELEASE"
id "io.spring.dependency-management" version "1.0.10.RELEASE"
id "java"
}

group = 'com.nobody'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenLocal()
maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// 添加lombok,主要为程序中通过注解,不用编写getter和setter等代码
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
}

四、自定义异常错误类


在我们项目开发中,肯定会有跟业务相关的异常,例如添加用户的业务,系统要求用户名不能为空,但是添加用户的请求接口,用户名值为空,这时我们程序要报​​用户名不能为空​​​的异常错误;或者查询用户信息的接口,可能会报​​用户不存在​​的错误异常等等。


4.1 自定义异常基础接口类


因为要做成通用性,所以我们定义一个异常基础接口类,自定义的异常枚举类需实现该接口。


package com.nobody.exception;

/**
* @Description 自定义异常基础接口类,自定义的异常信息枚举类需实现该接口。
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
public interface BaseErrorInfo {

/**
* 获取错误码
*
* @return 错误码
*/
String getErrorCode();

/**
* 获取错误信息
*
* @return 错误信息
*/
String getErrorMsg();

}

4.2 通用异常信息枚举类


通用异常信息枚举类,这里定义的所有异常信息是整个程序通用的。


package com.nobody.exception;

import lombok.Getter;

/**
* @Description 自定义通用异常信息枚举类
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@Getter
public enum CommonErrorEnum implements BaseErrorInfo {

/**
* 成功
*/
SUCCESS("200", "成功!"),
/**
* 请求的数据格式不符!
*/
BODY_NOT_MATCH("400", "请求的数据格式不符!"),
/**
* 未找到该资源!
*/
NOT_FOUND("404", "未找到该资源!"),
/**
* 服务器内部错误!
*/
INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
/**
* 服务器正忙,请稍后再试!
*/
SERVER_BUSY("503", "服务器正忙,请稍后再试!");

private String errorCode;
private String errorMsg;

CommonErrorEnum(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}

4.3 业务异常信息枚举类


如果程序中异常信息太多,可以针对每个模块功能定义业务异常枚举类,方便维护,例如和用户相关的异常信息枚举类如下。


package com.nobody.exception;

import lombok.Getter;

/**
* @Description 自定义用户相关异常信息枚举类
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@Getter
public enum UserErrorEnum implements BaseErrorInfo {

/**
* 用户不存在
*/
USER_NOT_FOUND("1001", "用户不存在!");

private String errorCode;
private String errorMsg;

UserErrorEnum(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}

4.4 自定义业务异常类


业务异常类,主要用于业务错误,或者异常时手动抛出的异常。


package com.nobody.exception;

import lombok.Getter;
import lombok.Setter;
import org.slf4j.MDC;

/**
* @Description 自定义业务异常类
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@Getter
@Setter
public class BizException extends RuntimeException {

private static final long serialVersionUID = 5564446583860234738L;

// 错误码
private String errorCode;
// 错误信息
private String errorMsg;
// 日志追踪ID
private String traceId = MDC.get("traceId");

public BizException(BaseErrorInfo errorInfo) {
super(errorInfo.getErrorMsg());
this.errorCode = errorInfo.getErrorCode();
this.errorMsg = errorInfo.getErrorMsg();
}

public BizException(BaseErrorInfo errorInfo, String errorMsg) {
super(errorMsg);
this.errorCode = errorInfo.getErrorCode();
this.errorMsg = errorMsg;
}

public BizException(BaseErrorInfo errorInfo, Throwable cause) {
super(errorInfo.getErrorMsg(), cause);
this.errorCode = errorInfo.getErrorCode();
this.errorMsg = errorInfo.getErrorMsg();
}

public BizException(String errorCode, String errorMsg) {
super(errorMsg);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}

public BizException(String errorCode, String errorMsg, Throwable cause) {
super(errorMsg, cause);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}

五、接口返回统一格式


为方便前端对接口返回的数据进行处理,也是规范问题,所以我们要定义接口返回统一格式。


package com.nobody.pojo.vo;

import lombok.Getter;
import lombok.Setter;

/**
* @Description 接口返回统一格式
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@Getter
@Setter
public class GeneralResult<T> {

private boolean success;
private String errorCode;
private String message;
private T data;
private String traceId;

private GeneralResult(boolean success, T data, String message, String errorCode) {
this.success = success;
this.data = data;
this.message = message;
this.errorCode = errorCode;
}

public static <T> GeneralResult<T> genResult(boolean success, T data, String message) {
return genResult(success, data, message, null);
}

public static <T> GeneralResult<T> genSuccessResult(T data) {
return genResult(true, data, null, null);
}

public static <T> GeneralResult<T> genErrorResult(String message) {
return genResult(false, null, message, null);
}

public static <T> GeneralResult<T> genSuccessResult() {
return genResult(true, null, null, null);
}

public static <T> GeneralResult<T> genErrorResult(String message, String errorCode) {
return genResult(false, null, message, errorCode);
}

public static <T> GeneralResult<T> genResult(boolean success, T data, String message,
String errorCode) {
return new GeneralResult<>(success, data, message, errorCode);
}

public static <T> GeneralResult<T> genErrorResult(String message, String errorCode,
String traceId) {
GeneralResult<T> result = genResult(false, null, message, errorCode);
result.setTraceId(traceId);
return result;
}

}

六、全局异常处理


此类是对全局异常的处理,根据自己情况,是否对不同种类的异常进行处理。例如以下是单独对业务异常,接口参数异常,以及剩余的所有异常进行处理,并生成接口统一格式信息,返回给调用接口的客户端,进行展示。

首先我们需要在处理全局异常的类上面,加上 ​​@ControllerAdvice​​ 或者 ​​@RestControllerAdvice​​注解。@ControllerAdvice 注解能处理 ​​@Controller​​ 和 ​​@RestController​​ 类型的接口调用时产生的异常,而 @RestControllerAdvice 注解只能处理 ​​@RestController​​ 类型接口调用时产生的异常。​​我们一般用 @ControllerAdvice 注解。​
​@ExceptionHandler​​ 只能注解在方法上,表示这是一个处理异常的方法,​​value​​ 属性可以填写需要处理的异常类,可以是数组。

​@ResponseBody​​ 注解表示我们返回的信息是响应体数据。


package com.nobody.exception;

import javax.servlet.http.HttpServletRequest;

import com.nobody.pojo.vo.GeneralResult;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* @Description 统一异常处理
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

// 处理自定义的业务异常
@ExceptionHandler(value = BizException.class)
@ResponseBody
public GeneralResult<Object> restErrorHandler(HttpServletRequest request, BizException e) {
String err = "requestURI:" + request.getRequestURI() + ",errorCode:" + e.getErrorCode()
+ ",errorMsg:" + e.getErrorMsg();
log.error(err, e);
return GeneralResult.genErrorResult(e.getMessage(), e.getErrorCode(), e.getTraceId());
}

// 处理接口参数数据格式错误异常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public GeneralResult<Object> errorHandler(HttpServletRequest request,
MethodArgumentNotValidException e) {
StringBuilder message = new StringBuilder();
String err = null;
e.getBindingResult().getAllErrors()
.forEach(error -> message.append(error.getDefaultMessage()).append(";"));
String des = message.toString();
if (!StringUtils.isEmpty(des)) {
err = des.substring(0, des.length() - 1);
}
log.error(err + ",requestURI:" + request.getRequestURI(), e);
return GeneralResult.genErrorResult(CommonErrorEnum.BODY_NOT_MATCH.getErrorMsg(),
CommonErrorEnum.BODY_NOT_MATCH.getErrorCode(), MDC.get("traceId"));
}

// 处理其他异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
public GeneralResult<Object> errorHandler(HttpServletRequest request, Exception e) {
log.error("internal server error,requestURI:" + request.getRequestURI(), e);
return GeneralResult.genErrorResult(CommonErrorEnum.INTERNAL_SERVER_ERROR.getErrorMsg(),
CommonErrorEnum.INTERNAL_SERVER_ERROR.getErrorCode(), MDC.get("traceId"));
}
}

七、测试

7.1 辅助类


测试会针对不同情况进行验证,以下是一些测试需要用到的类。


package com.nobody.pojo.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
* @Description 用户实体类
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@AllArgsConstructor
@Getter
@Setter
public class UserEntity implements Serializable {

private static final long serialVersionUID = 5564446583860234738L;

private String id;
private String name;
private int age;

}
package com.nobody.pojo.dto;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;

/**
* @Description 添加用户时参数类
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@Getter
@Setter
public class UserDTO {
@NotEmpty(message = "用户名不能为空")
private String name;
@Min(value = 0, message = "年龄最小不能低于0")
private int age;
}


以下简单模拟 User 相关业务,然后产生不同的异常。


package com.nobody.service;

import com.nobody.pojo.dto.UserDTO;
import com.nobody.pojo.entity.UserEntity;

/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
public interface UserService {
UserEntity add(UserDTO userDTO);

UserEntity getById(String id);

void marry(String age);
}
package com.nobody.service.impl;

import com.nobody.exception.BizException;
import com.nobody.exception.UserErrorEnum;
import com.nobody.pojo.dto.UserDTO;
import com.nobody.pojo.entity.UserEntity;
import com.nobody.service.UserService;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.UUID;

/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@Service
public class UserServiceImpl implements UserService {
@Override
public UserEntity add(UserDTO userDTO) {
String userId = UUID.randomUUID().toString();
return new UserEntity(userId, userDTO.getName(), userDTO.getAge());
}

@Override
public UserEntity getById(String id) {
// 模拟业务异常
if (Objects.equals(id, "000")) {
throw new BizException(UserErrorEnum.USER_NOT_FOUND);
}
return new UserEntity(id, "Mr.nobody", 18);
}

@Override
public void marry(String age) {
// 当age不是数字字符串时,抛出异常
Integer integerAge = Integer.valueOf(age);
System.out.println(integerAge);
}
}


接口类定义,根据不同参数调用接口,可产生不同的异常错误。


package com.nobody.controller;

import com.nobody.pojo.dto.UserDTO;
import com.nobody.pojo.entity.UserEntity;
import com.nobody.pojo.vo.GeneralResult;
import com.nobody.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@RestController
@RequestMapping("user")
public class UserController {

private UserService userService;

public UserController(final UserService userService) {
this.userService = userService;
}

@PostMapping("add")
public GeneralResult<UserEntity> add(@RequestBody @Valid UserDTO userDTO) {
UserEntity user = userService.add(userDTO);
return GeneralResult.genSuccessResult(user);
}

@GetMapping("find/{userId}")
public GeneralResult<UserEntity> find(@PathVariable String userId) {
UserEntity user = userService.getById(userId);
return GeneralResult.genSuccessResult(user);
}

@GetMapping("marry/{age}")
public GeneralResult<UserEntity> marry(@PathVariable String age) {
userService.marry(age);
return GeneralResult.genSuccessResult();
}

}

7.2 测试结果


启动服务,进行接口调用,本此演示用的 IDEA 自带的 ​​HTTP Client​​​ 工具进行调用,当然你也可以使用 ​​Postman​​ 进行调用。


互联网大厂是如何处理全局异常的?_java


首先演示正常的接口调用,服务没有报错,接口也返回正常数据。


互联网大厂是如何处理全局异常的?_java_02


还是调用查询用户接口,演示用户不存在情况,服务报错打印日志,接口也返回错误信息。


互联网大厂是如何处理全局异常的?_全局异常处理_03

互联网大厂是如何处理全局异常的?_java_04


再演示添加用户操作,用户名不填值,程序报错打印日志,接口也返回错误信息。


互联网大厂是如何处理全局异常的?_异常信息_05

互联网大厂是如何处理全局异常的?_spring_06

互联网大厂是如何处理全局异常的?_全局异常处理_07


再演示其他异常情况,例如解析数字出错。


互联网大厂是如何处理全局异常的?_异常信息_08

互联网大厂是如何处理全局异常的?_java_09


此演示项目已上传到Github,如有需要可自行下载,欢迎 ​​Star​​ 。

https://github.com/LucioChn/springboot-global-exception-handler




举报

相关推荐

0 条评论