Skip to content

Commit 5e566a6

Browse files
authored
Create Java全局异常捕获处理 .md
1 parent 58deca5 commit 5e566a6

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed

Java/Java全局异常捕获处理 .md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
2+
3+
为了项目的正常运行中,异常捕获,记录也是非常重要的,方便我们排查问题,定位问题
4+
5+
# 定义异常
6+
7+
为了方便定位异常,自定义了几种异常类,方便我们快速定位异常。
8+
## 基类
9+
```java
10+
public class HttpException extends RuntimeException {
11+
protected String code;
12+
protected Integer httpStatusCode = 500;
13+
}
14+
15+
```
16+
## ParameterException
17+
```java
18+
public class ParameterException extends HttpException {
19+
public ParameterException(String code){
20+
this.code = code;
21+
this.httpStatusCode = 400;
22+
}
23+
}
24+
```
25+
## ServerErrorException
26+
```java
27+
public class ServerErrorException extends HttpException {
28+
public ServerErrorException(String code) {
29+
this.code = code;
30+
this.httpStatusCode = 500;
31+
}
32+
}
33+
34+
```
35+
## UnAuthenticatedException
36+
```java
37+
public class UnAuthenticatedException extends HttpException{
38+
public UnAuthenticatedException(String code){
39+
this.code = code;
40+
this.httpStatusCode = 401;
41+
}
42+
}
43+
44+
```
45+
## ForbiddenException
46+
```java
47+
public class ForbiddenException extends HttpException {
48+
public ForbiddenException(String code) {
49+
this.code = code;
50+
this.httpStatusCode = 403;
51+
}
52+
}
53+
```
54+
## NotFoundException
55+
```java
56+
public class NotFoundException extends HttpException {
57+
public NotFoundException(String code){
58+
this.httpStatusCode = 404;
59+
this.code = code;
60+
}
61+
}
62+
63+
```
64+
这里定义了我在项目中常用的几种异常,也可根据实际情况定义自己所需的异常。
65+
# 捕获异常
66+
67+
捕获异常需要用到一个注解`@ControllerAdvice`,关于它的详细解释可查看[文档](https://docs.spring.io/spring-framework/docs/5.0.0.M1/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html)
68+
69+
使用方法如下,定义一个异常捕获类
70+
71+
```java
72+
@ControllerAdvice
73+
public class GlobalExceptionAdvice {
74+
75+
}
76+
```
77+
这个类就已经实现了捕获全局异常的功能,下面在加上上面定义的几种异常
78+
```java
79+
@ControllerAdvice
80+
public class GlobalExceptionAdvice {
81+
@ExceptionHandler(UnAuthenticatedException.class)
82+
public ResponseEntity unAuthenticatedException(UnAuthenticatedException e) {
83+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getCode());
84+
}
85+
86+
87+
@ExceptionHandler(ParameterException.class)
88+
public ResponseEntity handleParameterException(ParameterException e) {
89+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getCode());
90+
}
91+
92+
@ExceptionHandler(ForbiddenException.class)
93+
public ResponseEntity handleForbiddenException(ForbiddenException e) {
94+
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getCode());
95+
}
96+
97+
@ExceptionHandler(NotFoundException.class)
98+
public ResponseEntity handleNotFoundException(NotFoundException e) {
99+
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getCode());
100+
}
101+
102+
@ExceptionHandler(RuntimeException.class)
103+
public ResponseEntity handleRunTimeException(RuntimeException e) {
104+
105+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(500);
106+
}
107+
108+
109+
}
110+
```
111+
112+
`@ExceptionHandler`注解表示该方法捕获的异常类型,就可以在不同的异常中进行不同的处理方式。
113+
# 记录异常
114+
捕获到异常之后我们要记录下来,方便我们对bug的追踪解决。
115+
116+
记录方法有多种多样的,比如记录到数据库或者`log`文件中。我使用了第二种方式。
117+
118+
## 加入依赖
119+
120+
```xml
121+
<dependency>
122+
<groupId>org.projectlombok</groupId>
123+
<artifactId>lombok</artifactId>
124+
<optional>true</optional>
125+
</dependency>
126+
<dependency>
127+
<groupId>commons-logging</groupId>
128+
<artifactId>commons-logging</artifactId>
129+
<version>1.2</version>
130+
</dependency>
131+
<dependency>
132+
<groupId>org.apache.logging.log4j</groupId>
133+
<artifactId>log4j-api</artifactId>
134+
<version>2.2</version>
135+
</dependency>
136+
<dependency>
137+
<groupId>org.apache.logging.log4j</groupId>
138+
<artifactId>log4j-core</artifactId>
139+
<version>2.2</version>
140+
</dependency>
141+
<dependency>
142+
<groupId>org.apache.logging.log4j</groupId>
143+
<artifactId>log4j-jcl</artifactId>
144+
<version>2.2</version>
145+
</dependency>
146+
```
147+
## 增加日志配置文件
148+
149+
`logback.xml`
150+
```xml
151+
<?xml version="1.0" encoding="UTF-8"?>
152+
<configuration>
153+
154+
<!-- 控制台 appender, 几乎是默认的配置 -->
155+
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
156+
<encoder charset="UTF-8">
157+
<!-- 输出的日志文本格式, 其他的 appender 与之相同 -->
158+
<pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
159+
<charset>UTF-8</charset>
160+
</encoder>
161+
</appender>
162+
163+
<!-- info 级别的 appender -->
164+
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
165+
<!-- 日志写入的文件名, 可以是相对目录, 也可以是绝对目录, 如果上级目录不存在会自动创建 -->
166+
<file>./logs/info/log-stack.log</file>
167+
<!-- 如果是 true, 日志被追加到文件结尾; 如果是 false, 清空现存文件. 默认是true -->
168+
<append>true</append>
169+
<!-- 日志级别过滤器, 只打 INFO 级别的日志-->
170+
<filter class="ch.qos.logback.classic.filter.LevelFilter">
171+
<level>INFO</level>
172+
<!-- 下面2个属性表示: 匹配 level 的接受打印, 不匹配的拒绝打印 -->
173+
<onMatch>ACCEPT</onMatch>
174+
<onMismatch>DENY</onMismatch>
175+
</filter>
176+
<!-- 最常用的滚动策略, 它根据时间来制定滚动策略, 既负责滚动也负责触发滚动 -->
177+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
178+
<!-- 设置滚动文件规则, 如果直接使用 %d, 默认格式是 yyyy-MM-dd -->
179+
<fileNamePattern>./logs/info/log-stack.%d{yyyy-MM-dd}.log</fileNamePattern>
180+
<!-- 保留14天的日志 -->
181+
<maxHistory>30</maxHistory>
182+
</rollingPolicy>
183+
<!-- 定义日志输出格式 -->
184+
<encoder charset="UTF-8">
185+
<pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
186+
<charset>UTF-8</charset>
187+
</encoder>
188+
</appender>
189+
190+
<!-- error 级别的 appender -->
191+
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
192+
<file>./logs/error/log-stack.log</file>
193+
<append>true</append>
194+
<filter class="ch.qos.logback.classic.filter.LevelFilter">
195+
<level>ERROR</level>
196+
<onMatch>ACCEPT</onMatch>
197+
<onMismatch>DENY</onMismatch>
198+
</filter>
199+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
200+
<fileNamePattern>./logs/error/log-stack.%d{yyyy-MM-dd}.log</fileNamePattern>
201+
<!-- 保留7天的日志 -->
202+
<maxHistory>30</maxHistory>
203+
</rollingPolicy>
204+
<!-- 定义日志输出格式 -->
205+
<encoder charset="UTF-8">
206+
<pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
207+
<charset>UTF-8</charset>
208+
</encoder>
209+
</appender>
210+
<!-- error 级别的 appender -->
211+
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
212+
<file>./logs/debug/log-stack.log</file>
213+
<append>true</append>
214+
<filter class="ch.qos.logback.classic.filter.LevelFilter">
215+
<level>DEBUG</level>
216+
<onMatch>ACCEPT</onMatch>
217+
<onMismatch>DENY</onMismatch>
218+
</filter>
219+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
220+
<fileNamePattern>./logs/debug/log-stack.%d{yyyy-MM-dd}.log</fileNamePattern>
221+
<!-- 保留7天的日志 -->
222+
<maxHistory>30</maxHistory>
223+
</rollingPolicy>
224+
<!-- 定义日志输出格式 -->
225+
<encoder charset="UTF-8">
226+
<pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
227+
<charset>UTF-8</charset>
228+
</encoder>
229+
</appender>
230+
<!-- 指定 com.github 下的日志打印级别, appender -->
231+
<logger name="com.github" level="debug" additivity="false">
232+
<appender-ref ref="stdout"/>
233+
<appender-ref ref="info"/>
234+
<appender-ref ref="error"/>
235+
<appender-ref ref="debug"/>
236+
237+
</logger>
238+
239+
240+
<root level="info">
241+
<appender-ref ref="stdout"/>
242+
<appender-ref ref="info"/>
243+
<appender-ref ref="error"/>
244+
</root>
245+
246+
</configuration>
247+
```
248+
## 写入日志
249+
```java
250+
@ControllerAdvice
251+
@Slf4j
252+
public class GlobalExceptionAdvice {
253+
@ExceptionHandler(ParameterException.class)
254+
public ResponseEntity handleParameterException(ParameterException e) {
255+
log.error(e.getLocalizedMessage());
256+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getCode());
257+
}
258+
}
259+
```
260+
# 完善异常信息
261+
262+
文章中的异常只定义了`code`,具体的异常信息可以写在配置文件中或者保存在数据库中,在捕获到异常之后,找到对应的描述信息返回调用者,增加友好度。
263+
264+
# 完善记录日志
265+
266+
以上如果发生了异常,在日志文件中是这样记录的
267+
268+
```
269+
10:19:32.024 [http-nio-8080-exec-2] ERROR c.g.e.d.advice.GlobalExceptionAdvice 41 - / by zero
270+
```
271+
发现记录的行号是在`GlobalExceptionAdvice`类中,并非是代码真实的位置。
272+
273+
如果要记录到代码的真实位置可以这样实现
274+
```java
275+
public String getExceptionDetail(Exception e) {
276+
277+
StringBuilder stringBuilder = new StringBuilder();
278+
stringBuilder.append(e.getClass() + System.getProperty("line.separator"));
279+
stringBuilder.append(e.getLocalizedMessage() + System.getProperty("line.separator"));
280+
StackTraceElement[] arr = e.getStackTrace();
281+
for (int i = 0; i < arr.length; i++) {
282+
stringBuilder.append(arr[i].toString() + System.getProperty("line.separator"));
283+
284+
}
285+
return stringBuilder.toString();
286+
}
287+
```
288+
```java
289+
log.error(getExceptionDetail(e));
290+
```
291+
根据实际情况选择适合自己的方式
292+
293+
# 完整代码
294+
295+
[Github](https://github.com/lizeze/exception-demo)
296+
297+
[Gitee](https://gitee.com/zeze.li/exception-demo)

0 commit comments

Comments
 (0)