1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, “Callback object must not be null”);
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn’t been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
// 完成受检查异常到运行时异常的转化
throw translateException(“StatementCallback”, sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
2. 自定义异常
不必局限于Java提供的异常类型。我们可以自定义异常类来表示程序中可能会遇到的特定问题。
要自定义异常类,必须从已有异常类继承,最好的方式是选择意思相近的异常类继承。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 业务异常
class BizException extends RuntimeException{
public BizException() {
super();
}
public BizException(String message) {
super(message);
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
public BizException(Throwable cause) {
super(cause);
}
}
异常一般是用名称代表发生的问题,并且异常的名称应该可以望文知意。
2.1. 异常继承体系
异常本身也是类,存在一个完整的继承体系。
2.2. Throwable
Throwable被用来表示任何可以作为异常被抛出的类。
Throwable对象可以分为俩种类型(从Throwable继承而来):
-
Error 用于表示编译时和系统错误,出特殊情况外,开发人员不必关系
-
Exception 表示可以被抛出的基础类型。在Java类库、用户方法以及运行时故障都可能抛出Exception异常。这是开发人员最关系的异常。
throwable主要是对异常栈进行维护,核心方法如下:
方法 | 含义
—|—
printStackTrace | 打印调用栈信息,输出到标准错误输出(System.error)
printStackTrace(PrintStream) | 指定Stream打印调用栈信息
printStackTrace(PrintWriter) | 指定Print打印调用栈信息
getStackTrace() | 获取调用栈序列信息
fillInStackTrace() | 记录栈帧的当前状态
异常栈记录了”把你带到异常抛出点”的方法调用序列,是问题排查的主要信息之一。
1 |
2
3
4
5
6
7
8
9
10
11
public static void main(String… arg){
try {
// 正常处理流程,正确执行过程做什么事
Path path = Paths.get(“var”, “error”);
List<String> lines = Files.readAllLines(path, Charset.defaultCharset());
System.out.println(lines);
} catch (IOException e) {
// 异常处理流程,出了问题怎么办
e.printStackTrace();
}
}
运行程序,获得结果,异常栈如下:
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java.nio.file.NoSuchFileException: var/error
at sun.nio.fs.UnixException.translateToIOException(UnixException.java:86)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:214)
at java.nio.file.Files.newByteChannel(Files.java:361)
at java.nio.file.Files.newByteChannel(Files.java:407)
at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384)
at java.nio.file.Files.newInputStream(Files.java:152)
at java.nio.file.Files.newBufferedReader(Files.java:2781)
at java.nio.file.Files.readAllLines(Files.java:3199)
at com.geekhalo.exception.Demo.main(Demo.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
2.3. Exception
Exception是与编程有关的所有异常类的基类。
| 方法 | 含义 |
| — | — |
| getMessage | 获取详细信息 |
| getLocaliedMessage | 获取本地语言表示的详细信息 |
2.4. RuntimeException
从RuntimeException派生出来的异常成为“不受检查异常”。这种异常会自动被Java虚拟机抛出,所以不必在方法的异常说明中列出来。
1 |
2
3
private void throwRuntimeException(){
throw new RuntimeException();
}
RuntimeException 及其子类 无需在方法中进行声明。
3. 常见异常处理策略
完成自定义异常后,下一个关键点便是如何处理异常。
3.1. 异常恢复
异常处理程序的目的就是处理所发生的异常。因此,第一个异常处理策略便是,处理异常,进行异常恢复。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
private void recoveryException(String filePath){
try {
List<String> lines = readFromFile(filePath);
lines.forEach(System.out::println);
}catch (IOException e){
// 打印日志,从异常中恢复程序
LOGGER.error(“failed to read from file {}”, filePath, e);
}
}
private List<String> readFromFile(String filePath) throws IOException {
Path path = Paths.get(filePath);
return Files.readAllLines(path, Charset.defaultCharset());
}
3.2. 重新抛出异常
当你无法得到足够信息,从而对异常进行恢复时。可以把刚刚捕获的异常重新抛出。在catch子句中已经获得了对当前异常对象的引用,可以直接将其抛出。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void printFile(String filePath) throws IOException{
try {
List<String> lines = readFromFile(filePath);
lines.forEach(System.out::println);
}catch (IOException e){
// 重新抛出异常
throw e;
}
}
// 方法异常说明
private List<String> readFromFile(String filePath) throws IOException {
Path path = Paths.get(filePath);
return Files.readAllLines(path, Charset.defaultCharset());
}
重抛异常会把异常抛给上一级调用,同一try后的catch子句被忽略。如果只是把当前异常抛出,那么printStackTrace显示的是原来异常抛出点的调用链信息,而非重新抛出点的信息。如果想要更新调用信息,可以调用fillInStackTrace方法,返回另一个Throwable对象,它将当前调用栈信息填入原来的异常对象。
3.3. 异常链
如果想要在捕获一个异常后抛出另一个新异常,并希望把原始异常信息保留下来,这成为异常连。
Throwable的子类在构造器中都可以接受一个cause对象,用于表示原始异常,这样把原始异常传递给新异常,使得当前位置创建并抛出的新异常,通过异常链追踪到异常最初发生的位置。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void printFile2(String filePath){
try {
List<String> lines = readFromFile(filePath);
lines.forEach(System.out::println);
}catch (IOException e){
// 异常链
throw new BizException(e);
}
}
// 方法异常说明
private List<String> readFromFile(String filePath) throws IOException {
Path path = Paths.get(filePath);
return Files.readAllLines(path, Charset.defaultCharset());
}
Throwable子类中,只有Error、Exception、RuntimeException在构造函数中提供了cause参数,如果要把其他异常链起来,可以使用initCause方法。
4. 异常实战
异常是框架设计不可遗漏的点。
框架中的异常处理,同样遵循固定的操作流程:
-
根据需求自定义异常;
-
提供异常处理器,统一对异常进行处理;
4.1. Spring MVC 统一异常处理
Spring MVC 是最常见的 Web 框架,上手简单,开发迅速。
遵循正常流程与异常处理分离策略。研发人员只需关心正常逻辑,由框架对异常流程进行统一处理。那应该怎么操作呢?
4.1.1. 定义业务异常
首先,需要定义自己的业务异常。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class BusinessException extends RuntimeException{
/**
- 异常处理码
*/
private final int code;
/**
- 异常消息
*/
private final String msg;
private final String timestamp = String.valueOf(System.currentTimeMillis());
protected BusinessException(int code, String msg){
this.code = code;
this.msg = msg;
}
protected BusinessException(int code, String msg, Exception e) {
super(e);
this.code = code;
this.msg = msg;
}
}
4.1.2. 异常处理
可以使用 HandlerExceptionResolver 扩展,对异常进行定制。
RestHandlerExceptionResolver 对 Rest 请求的服务异常进行处理。将异常统一转化为 JSON 返回给用户。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Component
public class RestHandlerExceptionResolver implements HandlerExceptionResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(RestHandlerExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 是 Rest 请求 并且能够处理
if(isRestRequest(handler) && isAcceptException(ex)){
// 将异常传化为 RestResVo 对象
RestResVo<Void> restResVo = RestResVo.error((BusinessException)ex);
try {
// 以 Json 格式进行写回
response.getWriter().println(JSON.toJSONString(restResVo));
}catch (Exception e){
LOGGER.error(“failed to write json {}”, restResVo, e);
}
// empty ModelAndView说明已经处理
return new ModelAndView();
}
return null;
}
private boolean isRestRequest(Object handler) {
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
return AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResponseBody.class) !=null ||
AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), ResponseBody.class) != null;
}
return false;
}
private boolean isAcceptException(Exception ex) {
return ex instanceof BusinessException;
}
}
PageHandlerExceptionResolver 对页面请求的异常进行处理。将异常统一转发到 error 视图。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class PageHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 是页面请求并且能够处理当前异常
if(isPageRequest(handler) && isAcceptException(ex)){
// 返回 error 视图
ModelAndView mv = new ModelAndView(“error”);
mv.addObject(“error”, ex);
return mv;
}
return null;
}
private boolean isPageRequest(Object handler) {
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
return AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResponseBody.class) == null
&& AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), ResponseBody.class) == null;
}
return true;
}
private boolean isAcceptException(Exception ex) {
return ex instanceof BusinessException;
}
}
4.2. Spring Cloud 异常穿透
在使用 Spring Cloud 进行微服务时,如果 Server 端发生异常,客户端会收到一个 5xx 错误,从而中断当前正常请求逻辑。但,异常中所含有的业务信息也一并丢失了,如何最大限度的保持异常信息呢?
4.2.1. 定义业务异常
首先,仍旧是定义自己的业务异常类。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Data
public class CodeBasedException extends RuntimeException {
private Integer code;
private String msg;
private Object data;
public CodeBasedException(){
super();
}
public CodeBasedException(String msg) {
super(msg);
this.msg = msg;
}
public CodeBasedException(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public CodeBasedException(String message, Integer code, String msg, Object data) {
super(message);
this.code = code;
this.msg = msg;
this.data = data;
}
}
4.2.2. Server 端处理
在 Server 端,捕获业务异常,并将信息通过 Header 进行写回。
HandlerInterceptorBasedExceptionBinder 在业务处理完成后,捕获 CodeBasedException 异常,并将异常信息通过 Response 对象回写到 Header 中。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HandlerInterceptorBasedExceptionBinder implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(HandlerInterceptorBasedExceptionBinder.class);
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex == null){
return;
}
if (ex instanceo
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
f CodeBasedException){
CodeBasedException codeBasedException = (CodeBasedException) ex;
response.addHeader(SoaConstants.HEADER_ERROR_CODE, String.valueOf(codeBasedException.getCode()));
response.addHeader(SoaConstants.HEADER_ERROR_MSG, encode(codeBasedException.getMsg()));
response.addHeader(SoaConstants.HEADER_ERROR_EXCEPTION_MSG, encode(codeBasedException.getMessage()));
return;
}
response.setHeader(SoaConstants.HEADER_ERROR_CODE, “500”);
response.setHeader(SoaConstants.HEADER_ERROR_MSG, encode(ex.getMessage()));
response.setHeader(SoaConstants.HEADER_ERROR_EXCEPTION_MSG, encode(String.valueOf(ex.getStackTrace())));
LOGGER.error(“failed to handle request.”, ex);
}
}
如果是 Spring Boot 项目,我们需要完成 HandlerInterceptorBasedExceptionBinder 的注册。
1 |
2
3
4
5
6
7
@Configuration
public class SoaWebMvcConfigurer implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptorBasedExceptionBinder()).addPathPatterns("/**");
}
}
4.2.3. Client 端处理
客户端在获取请求结果后,从 Header 中提取异常信息,并重新组装并抛出异常。
FeignErrorDecoderBasedExceptionConverter 从 Header 中提取异常信息,并重新组装并抛出 SoaRemoteCallException。
1 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class FeignErrorDecoderBasedExceptionConverter implements ErrorDecoder {
private static final Logger LOGGER = LoggerFactory.getLogger(FeignErrorDecoderBasedExceptionConverter.class);
public FeignErrorDecoderBasedExceptionConverter() {
}
@Override
public Exception decode(String methodKey, Response response) {