JAVA 예외처리 구현
AS-IS
MyCustomRuntimeException.java
public class MyCustomRuntimeException extends RuntimeException {
public MyCustomRuntimeException(String message) {
super(message);
}
public MyCustomRuntimeException(String message, Throwable cause) {
super(message, cause);
}
}
MyController.java
@RestController
public class MyController {
private static final Logger logger = LoggerFactory.getLogger(MyController.class);
@Autowired
private MyService myService;
@RequestMapping("/example")
public Map<String, Object> exampleHandler() {
Map<String, Object> result = new HashMap<>();
try {
myService.doSomething();
result.put("status", "Success");
} catch (Exception e) {
logger.error("Exception occurred: " + e.getMessage(), e);
result.put("status", "Error");
result.put("message", "An error occurred");
}
return result;
}
@RequestMapping("/another")
public Map<String, Object> anotherHandler() {
Map<String, Object> result = new HashMap<>();
try {
myService.doAnotherThing();
result.put("status", "Success");
} catch (Exception e) {
logger.error("Exception occurred: " + e.getMessage(), e);
result.put("status", "Error");
result.put("message", "An error occurred");
}
return result;
}
}
❗중복
💡 중복 코드: 여러 컨트롤러 메소드에서 [에러처리코드]가 반복되어 중복이다.
💡 가독성: [에러처리코드]가 각 메소드에 직접 삽입되어 있어 가독성이 떨어어진다.
💡 확장성: 새로운 에러 상황이 추가될 때마다 모든 메소드에 동일한 코드를 추가 필요함. 이로 인해 코드의 확장성이 떨어진다.
💡 컨트롤러 역할의 혼란: 컨트롤러는 주로 HTTP 요청을 처리하고 응답을 생성하는 역할을 수행해야 한다.. 하지만 에러 처리 로직이 섞여 있으면 컨트롤러의 역할이 혼란스러워질 수 있다.
💡 단일 책임 원칙 위반: 각 메소드에서 예외를 처리하는 코드가 컨트롤러에 포함되어 있으므로, 단일 책임 원칙(Single Responsibility Principle)을 위반할 수 있다.
TO-BE(레벨1): 기존 + 에러관리 클래스 만들기
MyCustomRuntimeException.java
/******************************
에러관리 클래스
******************************/
public class MyCustomRuntimeException extends RuntimeException {
public MyCustomRuntimeException(String message) {
super(message);
}
public MyCustomRuntimeException(String message, Throwable cause) {
super(message, cause);
}
}
MyService.java
/******************************
서비스 클래스
******************************/
@Service
public class MyService {
public void myServiceLogic() {
// 서비스 로직 수행
// 예외 상황이 발생할 경우 MyCustomRuntimeException을 던짐
if (someCondition) {
throw new MyCustomRuntimeException("Custom exception occurred in MyService");
}
// 나머지 로직 수행
}
}
MyController.java
/******************************
컨트롤러
******************************/
@RestController
@RequestMapping("/myController")
public class MyController {
private final MyService myService;
@Autowired
public MyController(MyService myService) {
this.myService = myService;
}
@PostMapping("/myEndpoint")
public Map<String, Object> handleRequest(@RequestBody Map<String, Object> requestBody) {
Map<String, Object> result = new HashMap<>();
try {
myService.myServiceLogic(); // 서비스 호출
// 성공 시 처리
result.put("status", "success");
result.put("message", "Request processed successfully");
} catch (MyCustomRuntimeException e) {
// 서비스에서 발생한 예외를 MyCustomRuntimeException으로 잡아서 처리
result.put("status", "error");
result.put("message", "Custom exception occurred: " + e.getMessage());
} catch (Exception e) {
// 기타 예외 처리
result.put("status", "error");
result.put("message", "An error occurred: " + e.getMessage());
}
return result;
}
}
❗요약
💡 기존에는 컨트롤러, 서비스 모든곳에서 예외처리를 구현했다.
💡 현재는 별도 에러클래스를 만들어서 구현했다.❗서비스 클래스에서 예외 처리
💡 기존 코드에서는 서비스 클래스 내에서 예외를 처리하지 않고 컨트롤러에서만 처리하고 있었다. (즉,컨트롤러에서 예외 처리를 각각 구현해야 했다.)
💡 변경된 코드에서는 서비스 클래스에서 예외를 처리하고 특정 예외(RunTimeException)가 발생할 경우, 이를 특화된 예외(MyCustomRuntimeException)로 감싸서 던지도록 변경했다.❗컨트롤러 클래스에서 중복된 예외 처리
💡 기존 코드에서는 모든 컨트롤러에서 각자 예외를 처리하는 구조로 되어 있었다. 따라서 예외 처리 로직이 중복되고 있었다.
💡 변경된 코드에서는 MyCustomRuntimeException을 통합처리 한다. 이 예외가 발생할 경우, 특정 응답을 생성하여 클라이언트에게 반환하고 있다.
TO-BE(레벨2): 기존 + 에러관리 + 에러코드관리 클래스 만들기
AdmController.java
/******************************
컨트롤러
******************************/
@Controller
public class AdmController {
private static final Logger logger = LoggerFactory.getLogger(AdmController.class);
@ResponseBody
@RequestMapping(value="/app/test1",method = RequestMethod.POST)
public Map<String, Object> test1(@RequestBody Map<String, Object> params, HttpServletRequest request) {
logger.info("AdmController test1 start >>");
Map<String, Object> resultJSON = new HashMap<String, Object>();
try {
//사용자 조회서비스
TBDBDW002DTO inputW002 = new TBDBDW002DTO();
inputW002.setSlfMmsn(slfMmsn);
TBDBDW002DTO tBDBDW002DTO = tBDBDW002Service.getSelectOne(inputW002);
} catch(Exception e) {
//1. 통합된 에러처리: 커스텀익셉션에 1번 던져서 [커스텀메세지]와 [오리지날익셉션]내용을 모두 출력.
//2. 통합된 에러코드: 명확히 어떤 에러인지 개발자가 [파악]하고 [관리]가 가능함.
throw new SysException(SysErrorCode.INVALID_INPUT_VALUE);
} finally {
logger.debug("test1 finally : {}", "OK");
}
return resultJSON;
}
}
SysErrorCode.java
/******************************
에러코드 관리 클래스
******************************/
public enum SysErrorCode {
//Common
INVALID_INPUT_VALUE(400, "C001", "입력 값이 유효하지 않습니다."), //Bad Request
METHOD_NOT_ALLOWED(405, "C002", "허용되지 않은 메소드입니다"),
ENTITY_NOT_FOUND(400, "C003", "해당 엔터티를 찾을 수 없습니다."),
INTERNAL_SERVER_ERROR(500, "C004", "서버 오류"),
INVALID_TYPE_VALUE(400, "C005", "유효하지 않은 유형 값"),
HANDLE_ACCESS_DENIED(403, "C006", "접근이 거부되었습니다."),
//Member
EMAIL_DUPLICATION(400, "M001", "중복된 이메일 주소입니다"),
LOGIN_INPUT_INVALID(400, "M002", "유효하지 않은 로그인 입력입니다."),
//Coupon
COUPON_ALREADY_USE(400, "C0001", "쿠폰은 이미 사용되었습니다."),
COUPON_EXPIRE(400, "C0002", "쿠폰은 이미 만료되었습니다.");
private final String code;
private final String message;
private int status;
SysErrorCode(final int status, final String code, final String message) {
this.status = status;
this.message = message;
this.code = code;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public int getStatus() {
return status;
}
}
SysException.java
/******************************
에러 통합 클래스(실행예외)
******************************/
package com.blang.bck.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SysException extends RuntimeException
{
private static final Logger logger = LoggerFactory.getLogger(SysException.class);
//private static final long serialVerisonUID = -750907108710432318L;
//Enum열거형상수 에러코드 클래스
private final SysErrorCode errorCode;
//Constructor1: (커스텀에러세메지만)
public SysException(SysErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
//Constructor2: (커스텀에러세메지 + 오리지널에러메세지)
public SysException(SysErrorCode errorCode, Throwable e) {
super(errorCode.getMessage(), e); //부모(런타임익셉션)으로 [커스텀메세지]와 [에러객체]전달.
this.errorCode = errorCode;
logOriginalException(e);
}
//[커스텀메세지]와 [오리지날익셉션]내용을 모두 출력.
private void logOriginalException(Throwable e) {
logger.info("SysException logOriginalException start >>");
/** Logger기능 메소드(로그레벨지정해서 출력으로 권장함. 하지만 콘솔창에 색깔로 표현이 안되네....) **/
logger.error("[custom Exception SysErrorCode.getMessage()]: {}", errorCode.getMessage()); //커스텀 내용
logger.error("[org Exception name]: {}", e.getClass().getName()); //(동일)e.printStackTrace();
logger.error("[org Exception message]: {}", e.getMessage()); //오리지날 익셉션 내용
}
public SysErrorCode getErrorCode() {
return errorCode;
}
}
❗요약
/* 에러 코드 관리: SysErrorCode 열거형 클래스를 통해 에러 코드를 관리하고 있습니다. 이를 통해 에러 코드의 일관성을 유지하고, 에러 발생 시 특정 예외를 빠르게 식별할 수 있습니다. 에러 통합 클래스: SysException 클래스를 사용하여 특정 예외를 래핑하고, 예외 발생 시 추가 정보를 로깅하는 통합된 방식을 사용하고 있습니다. 이는 코드 중복을 방지하고, 에러 처리를 일관성 있게 유지할 수 있도록 도와줍니다. 예외 처리 및 로깅: 컨트롤러에서 발생하는 예외를 SysException을 통해 처리하고, 로깅하는 방식을 사용하고 있습니다. 이는 예외에 대한 추가 정보를 제공하고, 로깅을 통해 문제를 빠르게 파악할 수 있도록 합니다. RESTful API 구현: @Controller, @RequestMapping, @ResponseBody 어노테이션을 사용하여 RESTful API를 구현하고 있습니다. 이는 클라이언트에게 JSON 형태로 데이터를 반환하고, 요청과 응답을 명시적으로 처리할 수 있도록 도와줍니다. 코드 가독성: 코드에 주석을 포함하여 각 부분의 역할을 설명하고 있습니다. 또한, 각 변수 및 메서드의 명명법을 통해 코드의 가독성을 높이고 있습니다. 예외 처리 방식: 특정 예외가 발생할 경우 SysException을 던지고 있으며, 해당 예외에 대한 처리를 통일하고 있습니다. 이는 예외 처리를 통일된 방식으로 관리하고, 코드 중복을 최소화합니다. 파라미터 사용: 컨트롤러 메서드에서 @RequestBody 어노테이션을 사용하여 JSON 형태의 요청을 매핑하고, HttpServletRequest를 통해 추가적인 파라미터를 처리하고 있습니다. 트라이-캐치 구문 사용: 예외가 발생할 수 있는 코드를 try-catch 구문으로 감싸고, 특정 예외가 발생할 경우 SysException으로 래핑하여 통일된 방식으로 처리하고 있습니다. */
❗총괄적으로 보면, 위의 코드는 에러 처리 및 예외 관리에 있어서 일관성 있고 효율적인 방식을 채택하고 있습니다.
Leave a comment