⾃定义全局异常处理器(Java)
正常业务系统中,当前后端分离时,系统即使有未知异常,也要保证接⼝能返回错误提⽰,也需要根据业务规则制定相应的异常状态码和异常提⽰。所以需要⼀个全局异常处理器。相关代码:
异常
下⾯是 Java 异常继承图:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Throwable │
└───────────┘
▲
┌─────────┴─────────┐
││
┌───────────┐┌───────────┐
│ Error ││ Exception │
└───────────┘└───────────┘
▲▲
┌───────┘┌────┴──────────┐
│││
┌─────────────────┐┌─────────────────┐┌───────────┐
│OutOfMemoryError │... │RuntimeException ││IOException│...
└─────────────────┘└─────────────────┘└───────────┘
▲形容绿的四字词语
┌───────────┴─────────────┐
││
┌─────────────────────┐┌─────────────────────────┐
│NullPointerException ││IllegalArgumentException │...
└─────────────────────┘└─────────────────────────┘
根据编译时是否需要捕获,异常可以分为两类:1、写代码时,编译器规定必须捕获的异常,不捕获将报错;2、(抛出后)不必须捕获的异常,编译器对此类异常不做处理。
必须捕获的异常:Exception 以及 Exception 除去 RuntimeException 的⼦类。生日祝福语大全简短10个字
不必须捕获的异常:Error 以及 Error 的⼦类;RuntimeException 以及 RuntimeException 的⼦类。
必须捕获的异常:
@GetMapping("/testThrowIOException")
public ApiRespon<Void> testThrowIOException() {
testThrowIOException(); // 将报错
return ApiRespon.success();
}
private void throwIOException() throws IOException {
System.out.println("testThrowIOException");
throw new IOException();
}
不必须捕获的异常:
@GetMapping("/testThrowRuntimeException")
public ApiRespon<Void> testThrowRuntimeException() {
throwRuntimeException(); // 不报错
return ApiRespon.success();
}
private void throwRuntimeException() { // ⽆需 throws
System.out.println("testThrowRuntimeException");
throw new ArrayIndexOutOfBoundsException();
}
不过在运⾏时,任何异常都可以进⾏捕获处理,避免接⼝没有返回值的情况。
抛异常
常见异常处理⽅式有两种,1、捕获后处理,2、抛出。抛出也分为捕获后抛出和直接抛出。
当本⾝没有异常,却使⽤ throws 抛出异常时,此时相当于没有抛异常(将拦截不到异常)。
@GetMapping("/testThrowIOException2")
public ApiRespon<Void> testThrowIOException2() throws IOException {
throwIOException2();
return ApiRespon.success();
}
private void throwIOException2() throws IOException {
System.out.println("testThrowIOException");
}
打印异常
打印异常可以使⽤打印,其相关⽅法的使⽤: (e.getMessage(), e); 相当于下⾯这两条语句:
System.out.Message()); // 打印异常信息
e.printStackTrace(); // 打印异常调⽤栈
减少 NullPointException 的⽅式是设置默认值。
测试 Error
测试 StackOverflowError,设置栈的⼤⼩为 256K,IDEA(VM options): -Xss256k;命令⾏:java -Xss256k JavaVMStackSOF
class JavaVMStackSOF {
public int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
stack length:1693
Exception in thread "main" java.lang.StackOverflowError
at ption.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
at ption.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
...
测试 OutOfMemoryError,设置 Java 堆的⼤⼩为 128M,IDEA(VM options):-Xms10M -Xmx10M;命令⾏:java -Xms10M -Xmx10M ption.HeapOOM(如果类中包含 package 路径,需 cd 到 java ⽬录后运⾏此命令)
package ption;
import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
static class OOMObject {
}懦成语
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
趋之若鹜反义词}
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.ba/java.pyOf(Arrays.java:3720)
at java.ba/java.pyOf(Arrays.java:3689)
...
全局异常处理器
⾃定义异常
⾃定义异常从 RuntimeException 派⽣,构造⽅法使⽤ super(message); 和 super(message, cau);。添加状态码和参数属性。
public abstract class BaException extends RuntimeException {
private int code; // 状态码
private String message;
private Object[] args; // 参数
private IResponEnum responEnum;
public BaException(IResponEnum iResponEnum, Object[] args, String message) {
super(message);
this.args = args;
}
public BaException(IResponEnum iResponEnum, Object[] args, String message, Throwable cau) {
super(message, cau);
this.args = args;
}
public int getCode() {
de;
}
public String getMessage() {
ssage;
}
public Object[] getArgs() {
return this.args;
}
public IResponEnum getResponEnum() {
sponEnum;
}
}
当前服务的业务异常不⽤每个单独作为⼀个异常类,可通过 message 和 code 来做⼀个区分。
public class LoanException extends BusinessException {
public static LoanException INTERNAL_ERROR = new LoanException(ResponEnum.SERVER_ERROR);
public static LoanException REJECT = new LoanException(ResponEnum.REJECT);
public static LoanException BAND_FAIL = new LoanException(ResponEnum.BAND_FAIL);
public static LoanException FORBIDDEN = new LoanException(ResponEnum.FORBIDDEN);
public static LoanException DB_OPTIMISTIC_LOCK = new LoanException(ResponEnum.DB_OPTIMISTIC_LOCK);
public LoanException(IResponEnum responEnum) {
super(responEnum, null, Message());
}
public LoanException(IResponEnum responEnum, String message) {
super(responEnum, null, message);
}
}
@GetMapping("/testLoanException")
private ApiRespon<Void> testLoanException() {
throw LoanException.REJECT;
}
为不同的业务错误场景设置相关枚举类型(状态码、错误提⽰)。为枚举添加可断⾔判断抛出异常功能。public interface Asrt {
BaException var1);
BaException newException(Throwable var1, var2);
default void asrtNotNull(Object obj) {
if (obj == null) {
wException((Object[])null);
}
}
default void asrtNotNull(Object obj, args) {
if (obj == null) {
wException(args);
}
}
default void asrtTrue(boolean flag) {
if (!flag) {
wException((Object[])null);
}
}
default void asrtTrue(boolean flag, args) {
if (!flag) {
wException((Object[])null);
}
}
}
public interface BusinessExceptionAsrt extends IResponEnum, Asrt {
default BaException args) {
String msg = MessageFormat.Message(), args);
return new BusinessException(this, args, msg);
}
default BaException newException(Throwable t, args) {
String msg = MessageFormat.Message(), args);
return new BusinessException(this, args, msg, t);
}
}
@Getter
@AllArgsConstructor
public enum ResponEnum implements BusinessExceptionAsrt {
SUCCESS(111000,"success"),
PARAM_VALID_ERROR(111001,"param check error."),
SERVER_ERROR(111002,"rver error."),
LOGIN_ERROR(111003,"login error"),
UNAUTHORIZED(111004, "unauthorized"),
SERVICE_ERROR(111005,"rvice error."),
FORBIDDEN(114003, "forbidden"),
TIMEOUT(114000, "timeout"),
REJECT(114001, "reject"),
EMAIL_CONFLICT(114002, "email conflict"),
EMAIL_VERIFY_FAIL(114004, "email verify fail"),
DB_OPTIMISTIC_LOCK(114008, "update fail"),// 数据库乐观锁
EMAIL_SEND_FAIL(114011, "email nd fail"),
DATA_NOT_FOUND(114012, "data not found"),
LOGIN_TOKEN_VERIFY_FAIL(114014, "login token verify fail"),
;
/**
* 返回码
*/
private int code;
/**
* 返回消息
*/
private String message;
}
@GetMapping("/test")
public ApiRespon<String> test(String value) {
ResponEnum.SERVICE_ERROR.asrtNotNull(value);
return ApiRespon.success("true");
}
全局异常管理器
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* ⽣产环境
*/
private final static String ENV_PROD = "production";
/**
* 当前环境
*/
@Value("${env}")
private String profile;
/**
* 业务异常
*
* @param e 异常
* @return 异常结果
*/
@ExceptionHandler(value = BusinessException.class)
@ResponBody
public ApiRespon<String> handleBusinessException(BaException e) {
<(e.getMessage(), e);
<("BusinessException");
return ApiRespon.Code(), e.getMessage());
}
/**
* ⾮错误编码类系统异常
*
* @param e 异常
* @return 异常结果
*/
@ExceptionHandler(value = SystemException.class)
@ResponBody
public ApiRespon<String> handleBaException(SystemException e) {
return getServerErrorApiRespon(e);
}
/**
* Controller 上⼀层相关异常
*
* @param e 异常
* @return 异常结果
*/
@ExceptionHandler({NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
// BindException.class,
// MethodArgumentNotValidException.class
形容美的四字词语HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
@ResponBody
public ApiRespon<String> handleServletException(Exception e) {
return getServerErrorApiRespon(e);
}
/**
长干行四首其一
* 未定义异常。相当于全局异常捕获处理器。
*
* @param e 异常
* @return 异常结果
*/
班级对联
@ExceptionHandler(value = Exception.class)
@ResponBody
咨询服务协议public ApiRespon<String> handleException(Exception e) {
return getServerErrorApiRespon(e);
}
private ApiRespon<String> getServerErrorApiRespon(Exception e) {
int code = ResponEnum.Code();
String productShowMessage = ResponEnum.Message();
if (ENV_PROD.equals(profile)) {
return ApiRespon.fail(code, productShowMessage);
}
return ApiRespon.fail(code, e.getMessage());
}
}
使⽤ @ControllerAdvice + @ExceptionHandler 实现对指定异常的捕获。此时运⾏时异常和 Error 也能被捕获。延伸阅读