Обратите внимание на публичный аккаунт WeChat【
Java之言
】,Более干货文章
а также学习资料
, чтобы помочь вам отказаться от пути программирования!
1. Зачем обрабатывать глобальные исключения?
В обычном процессе разработки проекта программа неизбежно будет иметь исключения во время выполнения или бизнес-исключения. Нужно ли вам писать код для обработки всех возможных исключений? Или просто не обрабатывать исключение и отображать пользователю большой экран с информацией об исключении на английском языке? Насколько плох пользовательский опыт.
Итак, когда программа выдает исключение, чтобы日志的可读性
,排查 Bug简单
,так же как更好的用户体验性
, поэтому нам нужно обработать глобальное исключение.
2. Среда разработки
- JDK 1.8 или выше
- Springboot (данная демонстрационная версия Springboot 2.1.18.RELEASE)
- Gradle (конечно, вы также можете использовать Maven, на самом деле цель состоит в том, чтобы создавать проекты, управлять зависимостями и т. д.)
3. Добавьте зависимости
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;
}
}
5. Интерфейс возвращает единый формат
Чтобы облегчить интерфейсу обработку данных, возвращаемых интерфейсом, это также вопрос спецификации, поэтому нам нужно определить унифицированный формат для возврата интерфейса.
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;
}
}
6. Глобальная обработка исключений
Этот класс обрабатывает глобальные исключения, в зависимости от вашей ситуации, следует ли обрабатывать различные типы исключений. Например, в следующем обрабатывать бизнес исключений, исключения параметров интерфейса, и все остальные исключения отдельно и генерируют интерфейс единой информации формата, которая возвращается к клиенту, вызывающему интерфейс для отображения.
Во-первых, нам нужно добавить класс, который обрабатывает глобальные исключения.@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. Тест
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;
}
Следующее просто имитирует бизнес, связанный с пользователем, а затем генерирует различные исключения.
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
позвонить.
Сначала демонстрируется нормальный вызов интерфейса, сервис не сообщает об ошибке, а интерфейс возвращает нормальные данные.
Или вызовите пользовательский интерфейс запроса, чтобы продемонстрировать, что пользователь не существует, служба сообщает об ошибке и распечатывает журнал, а интерфейс также возвращает сообщение об ошибке.
Затем добавьте демонстрационную работу пользователя, имя пользователя не заполняет значение, программа печатает журнал ошибок, интерфейс также возвращает сообщение об ошибке.
Продемонстрируйте другие исключения, такие как ошибки при синтаксическом анализе чисел.
Этот демонстрационный проект был загружен на Github, вы можете скачать его самостоятельно, если вам это нужно, добро пожаловать
Star
.
GitHub.com/Lucio C неподвижно/билеты…
Обратите внимание на публичный аккаунт WeChat【
Java之言
】,Более干货文章
а также学习资料
, чтобы помочь вам отказаться от пути программирования!