Skip to content

Commit 003df29

Browse files
poi1649Jaeyoung22
andauthored
[포이] ExceptionHandler와 ControllerAdvice 초안 작성 (#760)
* docs: 리오의 DAO와 Repository 초안 작성 * docs: 리오의 DAO와 Repository 초안 * [포이] ExceptionHandler와 ControllerAdvice 초안 작성 * docs: 맞춤법수정, 레퍼런스 추가 * docs: 피드백 반영 * docs: 맞춤법 수정 * Delete 2023-04-24-DAO-And-Repository.md * docs: author 정보에서 리오 정보 삭제 * remove: 리오 사진 다 삭제 --------- Co-authored-by: ReO <[email protected]>
1 parent bae2bb5 commit 003df29

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

src/content/author.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,13 @@
308308
website: https://github.com/tonic523
309309
location: Seoul
310310
profile_image: profile/tonic.jpeg
311+
- id: 5기_포이
312+
avatar: avatars/poi.png
313+
bio: 우아한테크코스 5기 포이(김보준)입니다 :)
314+
github: bjk1649
315+
website: https://github.com/bjk1649
316+
location: Seoul
317+
profile_image: profile/poi.jpg
311318
- id: 5기_리오
312319
avatar: avatars/reo.jpeg
313320
bio: 우아한테크코스 5기 리오(오영택)입니다🎶

src/content/avatars/poi.png

307 KB
Loading

src/content/img/white-label.png

107 KB
Loading
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
---
2+
layout: post
3+
title: 'ExceptionHandler와 ControllerAdvice를 알아보자'
4+
author: [5기_포이]
5+
tags: ['spring']
6+
date: '2023-05-02T12:00:00.000Z'
7+
draft: false
8+
image: ../teaser/cycle.png
9+
---
10+
11+
콘솔 애플리케이션을 구현할 때, 우리는 예외를 핸들링하기 위해 try / catch문을 사용했습니다.
12+
13+
그러나 웹 애플리케이션에서는 예외 처리 방법이 조금 다릅니다.
14+
15+
이번 글에서는 스프링을 사용한 웹 애플리케이션 적용할 수 있는 예외 처리 방법인 `@ExceptionHandler``@ControllerAdvice`에 대해 알아보겠습니다.
16+
17+
<!-- end -->
18+
19+
## 스프링부트의 기본적인 예외 처리
20+
21+
```java
22+
@RestController
23+
public class SimpleController {
24+
25+
@GetMapping(path = "/errorExample")
26+
public String invokeError() {
27+
throw new IllegalArgumentException();
28+
}
29+
}
30+
```
31+
32+
라는 예시에서 예외를 던지는 메서드를 호출하는 URL `/errorExample`에 요청을 보내봅시다.
33+
34+
웹으로 요청을 보낸다면 스프링부트가 제공하는 기본 에러 페이지가 표시됩니다.
35+
36+
`BasicErrorController` 는 요청의 Accept 헤더 값이 text/html일 때, 예외가 발생하면 `/error` 라는 경로로 재요청을 보내줍니다.
37+
38+
해당 페이지는 `/error` 경로에 등록된 기본 에러 페이지입니다.
39+
40+
만약 기본 에러 페이지가 아닌 스스로 작성한 커스텀 페이지를 보여주고 싶다면 뷰 템플릿 경로에 커스텀 페이지 파일을 만들어서 넣어두면 됩니다.
41+
42+
오류 페이지 파일명에 따라 표시되는 웹페이지를 다르게 설정할 수 있습니다.
43+
44+
- 4xx.html: 400대 오류 페이지
45+
- 5xx.html: 500대 오류 페이지
46+
- 404.html: 404 오류 페이지
47+
48+
만약 페이지 변경뿐 아니라 더 상세하게 예외의 내용을 응답에 담고 싶다면, `BasicErrorController`를 상속한 `@Controller` 클래스를 만들어 `errorHtml()` 메서드와 `error()` 메서드를 재정의해주면 됩니다.
49+
50+
다만 이 방법은 url만 알면 누구나 마음대로 error 페이지에 접근할 수 있다는 단점이 있습니다.
51+
52+
이러한 단점을 `@ExceptionHandler`를 사용해 해결할 수 있습니다.
53+
54+
## @ExceptionHandler
55+
56+
`@ExceptionHandler` 어노테이션을 사용하면 value로 원하는 예외를 지정하고 이를 핸들링 할 수 있습니다.
57+
58+
예외에 대한 세부적인 정보 또한 응답으로 전달해 줄 수 있습니다.
59+
60+
```java
61+
@RestController
62+
public class SimpleController {
63+
64+
// ...
65+
66+
@ExceptionHandler(value = IllegalArgumentException.class)
67+
public ResponseEntity<String> invokeError(IllegalArgumentException e) {
68+
...
69+
return new ResponseEntity<>("error Message", HttpStatus.BAD_REQUEST);
70+
}
71+
}
72+
```
73+
74+
위 처럼 컨트롤러별로 반환하고자 하는 body와 메시지를 예외별로 적절하게 선택하여 세밀한 정보를 제공할 수 있습니다.
75+
76+
`@ExceptionHandler`는 value 속성으로 지정한 예외뿐 아니라 예외의 자식 클래스도 전부 캐치해 지정된 응답을 반환하게 됩니다. (value 속성을 지정하지 않는다면 메서드의 파라미터에 있는 예외가 자동으로 지정됩니다.)
77+
78+
만약 자식 클래스는 다른 예외 처리를 적용하고 싶다면 다음과 코드를 같이 작성하여 처리할 수 있습니다.
79+
80+
```java
81+
@RestController
82+
public class SimpleController {
83+
84+
// ...
85+
86+
@ExceptionHandler(value = IllegalArgumentException.class)
87+
public ResponseEntity<String> invokeError(IllegalArgumentException e) {
88+
...
89+
return new ResponseEntity<>("부모 클래스", HttpStatus.BAD_REQUEST);
90+
}
91+
92+
@ExceptionHandler(value = IllegalArgumentExtendsException.class)
93+
public ResponseEntity<String> invokeError(IllegalArgumentException e) {
94+
...
95+
return new ResponseEntity<>("자식 클래스", HttpStatus.BAD_REQUEST);
96+
}
97+
}
98+
```
99+
100+
`@ExceptionHandler`는 코드를 작성한 컨트롤러에서만 발생하는 예외만 처리됩니다.
101+
102+
만약 같은 예외에 대해 여러 컨트롤러에서 같은 처리를 하고 싶다면 컨트롤러마다 같은 메서드를 작성해 주어야만 합니다.
103+
104+
즉, 코드의 중복이 발생할 수밖에 없습니다.
105+
106+
## ControllerAdvice
107+
108+
`@ControllerAdvice``@Component` 어노테이션의 특수한 케이스로, 스프링 부트 애플리케이션에서 전역적으로 예외를 핸들링할 수 있게 해주는 어노테이션입니다.
109+
110+
이를 통해 코드의 중복을 해결할 수 있습니다.
111+
112+
또한, 하나의 클래스 내에서 정상 동작 시 호출되는 코드와 예외를 처리하는 코드를 분리할 수 있습니다.
113+
114+
다음은 `@ControllerAdvice``@RestConrollerAdvice` 의 구현의 일부입니다.
115+
116+
```java
117+
@Target(ElementType.TYPE)
118+
@Retention(RetentionPolicy.RUNTIME)
119+
@Documented
120+
@ControllerAdvice
121+
@ResponseBody
122+
public @interface RestControllerAdvice {
123+
...
124+
}
125+
126+
@Target(ElementType.TYPE)
127+
@Retention(RetentionPolicy.RUNTIME)
128+
@Documented
129+
@Component
130+
public @interface ControllerAdvice {
131+
...
132+
}
133+
```
134+
135+
`@Component`어노테이션을 붙이면 Component Scan 과정을 거쳐 Bean으로 등록됩니다.
136+
137+
즉, 이 어노테이션을 사용하는 `ControllerAdvice` 또한 Bean으로 관리된다는 것을 알 수 있습니다.
138+
139+
또한, `@RestControllerAdvice``@ResponseBody` 어노테이션이 붙어있으므로 응답을 Json으로 처리한다는 것을 알 수 있습니다.
140+
141+
`@ControllerAdvice` 어노테이션을 통해 예외를 핸들링하는 클래스를 구현해 보았습니다.
142+
143+
```java
144+
@ControllerAdvice
145+
public class SimpleControllerAdvice {
146+
147+
@ExceptionHandler(IllegalArgumentException.class)
148+
public ResponseEntity<String> IllegalArgumentException() {
149+
return ResponseEntity.badRequest().build();
150+
}
151+
}
152+
```
153+
154+
위 처럼 코드를 작성하게 되면 애플리케이션 내의 모든 컨트롤러에서 발생하는 `IllegalArgumentException`을 해당 메서드가 처리하게 됩니다.
155+
156+
**주의점**
157+
158+
여러 `ControllerAdvice`가 있을 때 `@Order`어노테이션으로 순서를 지정하지 않는다면 Spring은 `ControllerAdvice`를 임의의 순서로 호출합니다. 즉, 사용자가 예상하지 못한 예외 처리가 발생할 수 있습니다.
159+
160+
### basePackages
161+
162+
만약 여러 `ControllerAdvice`를 세분화하고 싶다면 `basePackages` 속성을 이용할 수 있습니다.
163+
164+
작성된 패키지와 하위 패키지에서 발생하는 예외는 해당 `ControllerAdvice`에서 처리하도록 지정할 수 있습니다.
165+
166+
```java
167+
@ControllerAdvice(basePackages = {"org.woowa.tmp.pkg"}
168+
public class SimpleControllerAdvice {
169+
170+
@ExceptionHandler(IllegalArgumentException.class)
171+
public ResponseEntity<String> IllegalArgumentException() {
172+
return ResponseEntity.badRequest().build();
173+
}
174+
}
175+
```
176+
177+
`basePackages` 속성을 통해 뷰와 모델을 이용해 직접 페이지를 반환하는 Controller는 예외가 발생하면 직접 예외 페이지를 응답하게 하고, JSON 형식으로 데이터를 주고받는 Controller는 예외가 발생하면 예외에 대한 정보를 JSON 형식으로 응답하게 할 수도 있습니다.
178+
179+
### assignableTypes
180+
181+
`assignableTypes` 속성을 이용하면 클래스 단위로도 `ControllerAdvice`를 적용할 수 있습니다.
182+
183+
클래스 단위로 사용되는 만큼 `basePackages` 속성보다 조금 더 세밀하게 처리를 분리해 주고 싶을 때 사용하면 유용합니다.
184+
185+
```java
186+
@ControllerAdvice(assignableTypes = {SimpleController.class}
187+
public class SimpleControllerAdvice {
188+
189+
@ExceptionHandler(IllegalArgumentException.class)
190+
public ResponseEntity<String> IllegalArgumentException() {
191+
return ResponseEntity.badRequest().build();
192+
}
193+
}
194+
```
195+
196+
### ResponseEntityExceptionHandler
197+
198+
Spring은 스프링 예외를 미리 처리해 둔 `ResponseEntityExceptionHandler`를 추상 클래스로 제공하고 있습니다.
199+
200+
`ResponseEntityExceptionHandler`에는 스프링 예외에 대한 `ExceptionHandler`가 모두 구현되어 있습니다.
201+
202+
하지만 에러 메시지는 반환하지 않으므로 스프링 예외에 대한 에러 응답을 보내려면 이 클래스를 상속하여 아래 메서드를 오버라이딩 해야 합니다.
203+
204+
```java
205+
public abstract class ResponseEntityExceptionHandler {
206+
...
207+
208+
protected ResponseEntity<Object> handleExceptionInternal(
209+
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
210+
211+
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
212+
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
213+
}
214+
return new ResponseEntity<>(body, headers, status);
215+
}
216+
}
217+
```
218+
219+
## 마무리
220+
221+
스프링에서는 `@ExceptionHandler` `@ControllerAdvice`를 통해 편리한 예외 처리 기능들을 제공합니다.
222+
223+
또한 여러 가지 속성을 통해 직접 메서드를 오버라이딩하는 것처럼 상세하게 세부 사항들을 지정해 줄 수도 있습니다.
224+
225+
코드가 더 간결해지고, 에러 처리 코드의 위치를 사용자가 유연하게 관리할 수 있다는 장점도 있습니다.
226+
227+
228+
## 참고
229+
- https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
230+
- https://tecoble.techcourse.co.kr/post/2021-05-10-controller_advice_exception_handler/
231+
- https://www.baeldung.com/spring-controllers

src/content/profile/poi.jpg

183 KB
Loading

0 commit comments

Comments
 (0)