Exception Handling - KimGyuBek/Threadly GitHub Wiki
MVC ๋ด๋ถ ์์ธ๋
DispatcherServlet๋ด๋ถ๊น์ง ๋๋ฌํดGlobalExceptionHandler๊ฐ ์ฒ๋ฆฌํ๋ค.Security Filter์์ ๋ฐ์ํ ์ธ์ฆ ์์ธ๋
DispatcherServlet๊น์ง ๋๋ฌํ์ง ๋ชป ํ๊ณ filter chain ๋ด๋ถ์์ExceptionTranslatorFilter๊ฐ ๊ฐ๋ก์ฑ ํAuthenticationEntryPoint๊ฐ ์ฒ๋ฆฌํ๋ค.

- Controller, Service, Repository์์ ๋ฐ์ํ๋ ์์ธ๋
DispatcherServlet๊น์ง ๋๋ฌ ํ ์ฒ๋ฆฌ๋๋ค. -
@RestControllerAdvice๋ ์์ธ๋ฅผ ๊ฐ์งํดErrorCode๋ฅผErrorResponse๋ก ๋ณํ ํ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํํ๋ค. -
RuntimeException์ ์์๋ฐ๋ ์ปค์คํ ๋๋ฉ์ธ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค. (ex.UserException) - ์ด ๋ฐฉ์์ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ๊ณ์ธต์์ ๋ฐ์ํ๋ ๋ชจ๋ ์์ธ๋ฅผ ํ ๊ณณ์์ ์ผ๊ด๋๊ฒ ์๋ตํ ์ ์๋ค.
Follow ์์ฒญ ์ฒ๋ฆฌ
//FollowCommandService.java
/*Follow ์์ฒญ ์ฒ๋ฆฌ*/
@Transactional
@Override
public FollowUserApiResponse followUser(FollowUserCommand command) {
// ...
/*์๊ธฐ ์์ ์ ํ๋ก์ ํ๋ ค๋ ๊ฒฝ์ฐ ์์ธ ๋ฐ์*/
if (Objects.equal(command.userId(), command.targetUserId())) {
throw new UserException(ErrorCode.SELF_FOLLOW_REQUEST_NOT_ALLOWED);
}
// ...
}//GlobalExceptionHandler.java
/*์ ์ญ ์ ์ธ ์ฒ๋ฆฌ๊ธฐ*/
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// ...
/*User*/
@ExceptionHandler(UserException.class)
public ResponseEntity<ErrorResponse> handleUserException(UserException ex, WebRequest request) {
return ResponseEntity.status(
ex.getErrorCode().getHttpStatus()) //UserException ๊ฐ์ง ํ ErrorResponse๋ก ๋ณํ ํ ๋ฐํ
.body(new ErrorResponse(ex.getErrorCode()));
}
// ...
}
- Filter์์ ๋ฐ์ํ ์์ธ๋
DispatcherServlet๊น์ง ๋๋ฌํ์ง ๋ชป ํ๋ค. -
@RestControllerAdvice์์ ์ฒ๋ฆฌ ๋ถ๊ฐ -
SecurityFilterChain๋ด๋ถ์์ExceptionTranslationFilter๊ฐ ์์ธ๋ฅผ ๊ฐ๋ก์ฑ๊ณAuthenticationEntryPoint์ ์์๋ฐ์ ํธ๋ค๋ฌ๊ฐ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค. - ์ด ๊ณผ์ ์์ ์ฌ์ฉ๋๋ ์์ธ๋
AuthenticationException์ ์์๋ฐ์์ ์ปค์คํ ์์ธ๋ก ๊ตฌํํ๋ค. (ex.UserAuthenticationException)-
RuntimeException์ ๋์ง๊ฒ ๋๋ฉด 500 ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ - ๋๋ฉ์ธ ์์ธ๋ฅผ
AuthenticationException์ ํ์ ์์ธ๋ก ๋ณํํ์ฌ ์ฒ๋ฆฌ
-
JWT ํ ํฐ ์ธ์ฆ
//JwtAuthenticationFilter.java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final CustomAuthenticationEntryPoint authenticationEntryPoint;
// ...
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
// ...
/*๋ธ๋๋ฆฌ์คํธ์ ๋ฑ๋ก๋ ํ ํฐ์ด๋ฉด ์์ธ ๋ฐ์*/
if (authManager.isBlacklisted(token)) {
throw new TokenException(ErrorCode.TOKEN_INVALID); //๋๋ฉ์ธ CustomException ๋ฐ์
}
// ...
} catch (TokenException e) {
TokenAuthenticationException exception = new TokenAuthenticationException(
e.getErrorCode()); //๋๋ฉ์ธ ์์ธ -> AuthenticationException ํ์ ์์ธ๋ก ๋ณํ
authenticationEntryPoint.commence(request, response,
exception); //AuthenticationEntryPoint ํธ์ถํด์ ๋ณํ๋ ์์ธ๋ฅผ ์ง์ ์ ๋ฌ
return;
}
filterChain.doFilter(request, response);
}
}//CustomAuthenticationEntryPoint.java
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
//์๋ต ๊ธฐ๋ณธ ์ค์
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
//์ปค์คํ
Authentication ์์ธ ์ฒ๋ฆฌ
/*TokenAuthenticationException์ผ ๊ฒฝ์ฐ*/
if (authException instanceof TokenAuthenticationException) {
ErrorCode errorCode = ((TokenAuthenticationException) authException).getErrorCode();
response.setStatus(errorCode.getHttpStatus().value()); //ErrorCode์ ๋งคํ๋ HttpStatus ์ถ์ถ
objectMapper.writeValue(response.getWriter(), //MVC ๋ด๋ถ ์์ธ ์๋ต ํฌ๋งท๊ณผ ๋์ผํ๊ฒ ์คํจ ๋ฐ๋ ์์ฑ
ApiResponse.fail(errorCode));
return;
}
if (authException instanceof UserAuthenticationException) { //๋ค๋ฅธ ์ปค์คํ
Authentication ์์ธ ์ฒ๋ฆฌ
// ...
return;
}
// ๊ธฐ๋ณธ ์ธ์ฆ ์์ธ๋ 401๋ก ํต์ผ
ErrorCode errorCode = ErrorCode.AUTHENTICATION_ERROR;
response.setStatus(errorCode.getHttpStatus().value());
objectMapper.writeValue(response.getWriter(),
ApiResponse.fail(errorCode));
return;
}
}MVC ๋ด๋ถ ์์ธ(
DispatcherServlet๋ด๋ถ)์ Security ์์ธ(filter chain)์๋ ๋์ผํ ์๋ต ํฌ๋งท์ ์ค๋ค.
MVC ๋ด๋ถ ์์ธ ์๋ต
{
"success": false,
"code": "TLY3000",
"message": "๊ฒ์๊ธ์ ์ฐพ์ ์ ์์ต๋๋ค.",
"data": {},
"timestamp": "2025-09-26T20:07:29.398202"
}Security Filter ๋ฐ์ ์์ธ ์๋ต
{
"success": false,
"code": "TLY1001",
"message": "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.",
"data": {},
"timestamp": "2025-09-26T20:08:08.492155"
}- Security Filter ์์ธ๋
DispatcherServlet๊น์ง ๋๋ฌํ์ง ์์ผ๋ฏ๋ก@ResponseBodyAdvice๋ก ๊ณตํต ์๋ต ํฌ๋งท์ด ์ ์ฉ๋์ง ์๋๋ค.- ๋ฐ๋ผ์
AuthenticationEntryPoint์์ ์ง์ ๊ณตํต ์๋ต์ ๋ง๋ค์ด์ ์๋ตํด์ผํ๋ค.
- ๋ฐ๋ผ์
- ์ปค์คํ
ํํฐ๊ฐ
ExceptionTranslationFilter๋ณด๋ค ์์ ์๋ ๊ฒฝ์ฐ, ๋จ์ํthrow๋ง ํด์๋EntryPoint๊ฐ ํธ์ถ๋์ง ์๋๋ค.- ํํฐ ๋ด๋ถ์์ ์์ธ ๋ฐ์ ์
authenticationEntryPoint.commence()๋ฅผ ์ง์ ํธ์ถํ๊ฑฐ๋ ์ปค์คํ ํํฐ๋ฅผExceptionTranslationFilter๋ค์ ๋ฑ๋กํด์ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์์ํ๋ ๋ฐฉ์์ ์ฌ์ฉํด์ผํ๋ค.
- ํํฐ ๋ด๋ถ์์ ์์ธ ๋ฐ์ ์
์์ธ ์์ธ/ํด๊ฒฐ ๊ณผ์ ์ ํธ๋ฌ๋ธ์ํ ๋ฌธ์์ฐธ๊ณ