在项目中,大家通常都喜欢使用 throw new BusinessException()
来阻断错误的参数或者业务逻辑异常等已知异常,反馈给用户。比如用户未绑定手机号、用户的参数异常等情况。在大部分情况下,使用异常终止流程都不是很好的方式,但是奈何在SpringBoot 环境下,@RestControllerAdvice
实在是香,不需要定义很多返回状态码。所以被大多数人使用在项目中。
但是使用此方式是具有一定的性能损耗的,因为在基类Throwable 的fillInStackTrace()方法会不断收集各层的调用链路,而SpringMVC又是在外层的Tomcat到DispatcherServlet到应用自定义的各种intercepter层层调用,调用堆栈通常较长。所以多多少少具有一定的性能消耗,下面进行了一个基本测试。
第一个for循环是为了给JVM预热。
第二个模拟普通的异常。
第三个模拟重写了fillInStackTrace()方法的异常。
/**
* @author imyzt
* @date 2021/01/02
* @description 异常收集测试
*/
public class TestMain {
public static void main(String[] args) {
int idx = 1000022;
long t1Start = System.currentTimeMillis();
for (int i = 0; i < idx; i++) {
Test test = new Test("aa" + i);
}
long t1end = System.currentTimeMillis();
System.out.println(t1end - t1Start);
long t2Start = System.currentTimeMillis();
for (int i = 0; i < idx; i++) {
try {
throw new ThrowableTest("aa" + i);
} catch (ThrowableTest throwableTest) {
}
}
long t2end = System.currentTimeMillis();
System.out.println(t2end - t2Start);
long t3Start = System.currentTimeMillis();
for (int i = 0; i < idx; i++) {
try {
throw new ThrowableTest2("aa" + i);
} catch (ThrowableTest2 throwableTest) {
}
}
long t3end = System.currentTimeMillis();
System.out.println(t3end - t3Start);
}
public static class Test {
private String name;
public Test(String name) {
this.name = name;
}
}
public static class ThrowableTest extends RuntimeException {
private String msg;
public ThrowableTest(String msg) {
super(msg);
this.msg = msg;
}
}
public static class ThrowableTest2 extends RuntimeException {
private String msg;
public ThrowableTest2(String msg) {
super(msg);
this.msg = msg;
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
}
耗时对比:
88
1631
74
通过结果可以看出,即使在非Web环境下都有如此巨大的性能差距,web环境下冗长的调用链路差距只会更大,大家可使用web环境下进行测试。
但是不是说有性能损耗就不用, 也需要结合业务进行权衡,比如可以将类似于参数错误等无需通过调用堆栈判断的、但是出现频率有很高的的异常进行重写基类的fillInStackTrace()
方法,使其不收集堆栈,但是此举会使出现异常后无法排查,所以只能使用在无需通过堆栈判断问题的场景下用于提升性能。