Skip to content

Commit bc0f556

Browse files
committed
实际项目中我们是这样做异常处理的
1 parent 12a9489 commit bc0f556

File tree

8 files changed

+257
-15
lines changed

8 files changed

+257
-15
lines changed

Diff for: README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@
2626
1. **[开发 RestFul Web 服务](./docs/basis/sringboot-restful-web-service.md)**
2727
2. **[RestController VS Controller](./docs/basis/RestControllerVSController.md)**
2828
3. **[Spring Boot 异常处理](./docs/advanced/springboot-handle-exception.md)**
29-
4. [使用 spring-boot-devtools 进行热部署](./docs/basis/spring-boot-devtools.md)
30-
5. **[ Spring Boot JPA 基础:常见操作解析](./docs/basis/springboot-jpa.md)**
31-
6. **[JPA 中非常重要的连表查询就是这么简单](./docs/basis/springboot-jpa-lianbiao.md)**
32-
7. [SpringBoot 实现过滤器](./docs/basis/springboot-filter.md)
33-
8. [SpringBoot 实现拦截器](./docs/basis/springboot-interceptor.md)
34-
9. [整合 SpringBoot+Mybatis](./docs/basis/springboot-mybatis.md)[SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](./docs/basis/springboot-mybatis-mutipledatasource.md)
29+
4. **[实际项目中我们是这样做异常处理的](./docs/advanced/springboot-handle-exception-plus)**
30+
5. [使用 spring-boot-devtools 进行热部署](./docs/basis/spring-boot-devtools.md)
31+
6. **[ Spring Boot JPA 基础:常见操作解析](./docs/basis/springboot-jpa.md)**
32+
7. **[JPA 中非常重要的连表查询就是这么简单](./docs/basis/springboot-jpa-lianbiao.md)**
33+
8. [SpringBoot 实现过滤器](./docs/basis/springboot-filter.md)
34+
9. [SpringBoot 实现拦截器](./docs/basis/springboot-interceptor.md)
35+
10. [整合 SpringBoot+Mybatis](./docs/basis/springboot-mybatis.md)[SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](./docs/basis/springboot-mybatis-mutipledatasource.md)
3536

3637
### 进阶
3738

Diff for: docs/advanced/springboot-handle-exception-plus.md

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
在上一篇文章中我介绍了:
2+
3+
1. 使用 `@ControllerAdvice``@ExceptionHandler` 处理全局异常
4+
2. `@ExceptionHandler` 处理 Controller 级别的异常
5+
3. `ResponseStatusException`
6+
7+
通过这篇文章,可以搞懂如何在 Spring Boot 中进行异常处理。但是,光是会用了还不行,我们还要思考如何把异常处理这部分的代码写的稍微优雅一点。下面我会以我在工作中学到的一点实际项目中异常处理的方式,来说说我觉得稍微优雅点的异常处理解决方案。
8+
9+
下面仅仅是我作为一个我个人的角度来看的,如果各位读者有更好的解决方案或者觉得本文提出的方案还有优化的余地的话,欢迎在评论区评论。
10+
11+
## 最终效果展示
12+
13+
下面先来展示一下完成后的效果,当我们定义的异常被系统捕捉后返回给客户端的信息是这样的:
14+
15+
![效果展示](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/异常处理plus-效果展示.jpg)
16+
17+
返回的信息包含了异常下面 5 部分内容:
18+
19+
1. 唯一标示异常的 code
20+
2. HTTP状态码
21+
3. 错误路径
22+
4. 发生错误的时间戳
23+
5. 错误的具体信息
24+
25+
这样返回异常信息,更利于我们前端根据异常信息做出相应的表现。
26+
27+
## 异常处理核心代码
28+
29+
`ErrorCode.java` (此枚举类中包含了异常的唯一标识、HTTP状态码以及错误信息)
30+
31+
```java
32+
import org.springframework.http.HttpStatus;
33+
34+
35+
public enum ErrorCode {
36+
RESOURCE_NOT_FOUND(1001, HttpStatus.NOT_FOUND, "未找到该资源"),
37+
REQUEST_VALIDATION_FAILED(1002, HttpStatus.BAD_REQUEST, "请求数据格式验证失败");
38+
private final int code;
39+
40+
private final HttpStatus status;
41+
42+
private final String message;
43+
44+
ErrorCode(int code, HttpStatus status, String message) {
45+
this.code = code;
46+
this.status = status;
47+
this.message = message;
48+
}
49+
50+
public int getCode() {
51+
return code;
52+
}
53+
54+
public HttpStatus getStatus() {
55+
return status;
56+
}
57+
58+
public String getMessage() {
59+
return message;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return "ErrorCode{" +
65+
"code=" + code +
66+
", status=" + status +
67+
", message='" + message + '\'' +
68+
'}';
69+
}
70+
}
71+
```
72+
73+
**`ErrorReponse.java`(返回给客户端具体的异常对象)**
74+
75+
```java
76+
import org.springframework.util.ObjectUtils;
77+
78+
import java.time.Instant;
79+
import java.util.HashMap;
80+
import java.util.Map;
81+
82+
public class ErrorReponse {
83+
private int code;
84+
private int status;
85+
private String message;
86+
private String path;
87+
private Instant timestamp;
88+
private HashMap<String, Object> data = new HashMap<String, Object>();
89+
90+
public ErrorReponse() {
91+
}
92+
93+
public ErrorReponse(BaseException ex, String path) {
94+
this(ex.getError().getCode(), ex.getError().getStatus().value(), ex.getError().getMessage(), path, ex.getData());
95+
}
96+
97+
public ErrorReponse(int code, int status, String message, String path, Map<String, Object> data) {
98+
this.code = code;
99+
this.status = status;
100+
this.message = message;
101+
this.path = path;
102+
this.timestamp = Instant.now();
103+
if (!ObjectUtils.isEmpty(data)) {
104+
this.data.putAll(data);
105+
}
106+
}
107+
108+
// 省略 getter/setter 方法
109+
110+
@Override
111+
public String toString() {
112+
return "ErrorReponse{" +
113+
"code=" + code +
114+
", status=" + status +
115+
", message='" + message + '\'' +
116+
", path='" + path + '\'' +
117+
", timestamp=" + timestamp +
118+
", data=" + data +
119+
'}';
120+
}
121+
}
122+
123+
```
124+
125+
**`BaseException.java`(继承自 `RuntimeException` 的抽象类,可以看做系统中其他异常类的父类)**
126+
127+
```java
128+
import org.springframework.util.ObjectUtils;
129+
130+
import java.util.HashMap;
131+
import java.util.Map;
132+
133+
/**
134+
* @author shuang.kou
135+
*/
136+
public abstract class BaseException extends RuntimeException {
137+
private final ErrorCode error;
138+
private final HashMap<String, Object> data = new HashMap<>();
139+
140+
public BaseException(ErrorCode error, Map<String, Object> data) {
141+
super(format(error.getCode(), error.getMessage(), data));
142+
this.error = error;
143+
if (!ObjectUtils.isEmpty(data)) {
144+
this.data.putAll(data);
145+
}
146+
}
147+
148+
protected BaseException(ErrorCode error, Map<String, Object> data, Throwable cause) {
149+
super(format(error.getCode(), error.getMessage(), data), cause);
150+
this.error = error;
151+
if (!ObjectUtils.isEmpty(data)) {
152+
this.data.putAll(data);
153+
}
154+
}
155+
156+
private static String format(Integer code, String message, Map<String, Object> data) {
157+
return String.format("[%d]%s:%s.", code, message, ObjectUtils.isEmpty(data) ? "" : data.toString());
158+
}
159+
160+
public ErrorCode getError() {
161+
return error;
162+
}
163+
164+
public Map<String, Object> getData() {
165+
return data;
166+
}
167+
168+
}
169+
```
170+
171+
**`ResourceNotFoundException.java` (自定义异常)**
172+
173+
可以看出通过继承 `BaseException` 类我们自定义异常会变的非常简单!
174+
175+
```java
176+
import java.util.Map;
177+
178+
public class ResourceNotFoundException extends BaseException {
179+
180+
public ResourceNotFoundException(Map<String, Object> data) {
181+
super(ErrorCode.RESOURCE_NOT_FOUND, data);
182+
}
183+
}
184+
```
185+
186+
**`GlobalExceptionHandler.java`(全局异常捕获)**
187+
188+
`@ExceptionHandler` 捕获异常的过程中,会优先找到最匹配的。
189+
190+
```java
191+
/**
192+
* @author shuang.kou
193+
*/
194+
@ControllerAdvice(assignableTypes = {ExceptionController.class})
195+
@ResponseBody
196+
public class GlobalExceptionHandler {
197+
198+
@ExceptionHandler(BaseException.class)
199+
public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
200+
String path = request.getRequestURI();
201+
ErrorReponse representation = new ErrorReponse(ex, path);
202+
return new ResponseEntity<>(representation, new HttpHeaders(), ex.getError().getStatus());
203+
}
204+
205+
206+
@ExceptionHandler(value = ResourceNotFoundException.class)
207+
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
208+
ErrorReponse errorReponse = new ErrorReponse(ex, request.getRequestURI());
209+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorReponse);
210+
}
211+
}
212+
213+
```
214+
215+
## 写一个抛出异常的类测试
216+
217+
**`Person.java`**
218+
219+
```java
220+
public class Person {
221+
private Long id;
222+
private String name;
223+
224+
// 省略 getter/setter 方法
225+
}
226+
```
227+
228+
**`ExceptionController.java`(抛出一场的类)**
229+
230+
```java
231+
@RestController
232+
@RequestMapping("/api")
233+
public class ExceptionController {
234+
235+
@GetMapping("/resourceNotFound")
236+
public void throwException() {
237+
Person p=new Person(1L,"SnailClimb");
238+
throw new ResourceNotFoundException(ImmutableMap.of("person id:", p.getId()));
239+
}
240+
241+
}
242+
```
243+

Diff for: docs/advanced/springboot-handle-exception.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,8 @@ public class ResponseStatusExceptionController {
270270

271271
本文主要讲了 3 种捕获处理异常的方式:
272272

273-
1. 使用 **`@ControllerAdvice``**@ExceptionHandler`处理全局异常
273+
1. 使用 `@ControllerAdvice``@ExceptionHandler` 处理全局异常
274274
2. `@ExceptionHandler` 处理 Controller 级别的异常
275275
3. `ResponseStatusException`
276276

277-
278-
279277
代码地址:https://github.com/Snailclimb/springboot-guide/tree/master/source-code/basis/springboot-handle-exception

Diff for: source-code/basis/springboot-handle-exception-improved/src/main/java/com/twuc/webApp/exception/ErrorReponse.java

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.twuc.webApp.exception;
22

3-
import org.springframework.http.HttpStatus;
43
import org.springframework.util.ObjectUtils;
54

65
import java.time.Instant;

Diff for: source-code/basis/springboot-handle-exception-improved/src/main/java/com/twuc/webApp/exception/GlobalExceptionHandler.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest
2727

2828

2929
@ExceptionHandler(value = ResourceNotFoundException.class)
30-
public ResponseEntity<ErrorReponse> exceptionHandler(ResourceNotFoundException ex, HttpServletRequest request) {
30+
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
3131
ErrorReponse errorReponse = new ErrorReponse(ex, request.getRequestURI());
32-
System.out.println(errorReponse.toString());
3332
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorReponse);
3433
}
3534
}

Diff for: source-code/basis/springboot-handle-exception-improved/src/main/java/com/twuc/webApp/web/ExceptionController.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.twuc.webApp.web;
22

33
import com.google.common.collect.ImmutableMap;
4+
import com.twuc.webApp.entity.Person;
45
import com.twuc.webApp.exception.ResourceNotFoundException;
56
import org.springframework.web.bind.annotation.GetMapping;
67
import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,7 +13,8 @@ public class ExceptionController {
1213

1314
@GetMapping("/resourceNotFound")
1415
public void throwException() {
15-
throw new ResourceNotFoundException(ImmutableMap.of("person id:", 1L));
16+
Person p=new Person(1L,"SnailClimb");
17+
throw new ResourceNotFoundException(ImmutableMap.of("person id:", p.getId()));
1618
}
1719

1820
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11

2-
server.port=8333
2+
server.port=8333

Diff for: source-code/basis/springboot-handle-exception-improved/src/test/java/com/twuc/webApp/web/ExceptionTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class ExceptionTest {
2121

2222
@Test
2323
void should_return_400_if_param_not_valid() throws Exception {
24-
mockMvc.perform(get("/api/illegalArgumentException"))
24+
mockMvc.perform(get("/api/resourceNotFound"))
2525
.andExpect(status().is(400))
2626
.andExpect(jsonPath("$.message").value("参数错误!"));
2727
}

0 commit comments

Comments
 (0)