Exception 클래스의 구현과 AOP 설정을 이용한 예외처리
API 서버 개발을 하며 케이스별 에러코드와 에러메시지를 response 값으로 던져주어야 했다.
각각의 API(Controller)에 try catch 로 exception 을 잡아 상황별 에러코드 및 에러메시지를 던지기엔 API 갯수가 너무 많았고, 비효율적(노가다)이라 생각하여
enum과 customized Exception class, AOP 설정을 통해 해결했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public static enum APIResult {
SUCCESS (200, "success"),
FAILURE (999, "fail"), //기타 에러
E_PARAM_NULL(101, "input parameter is null"), // 필수값 누락 에러
E_FORMAT(102, "input parameter data format error"), // 파싱 에러
E_NETWORK(104, "network error"), //네트워크 에러
private int code;
private String message;
private APIResult(int _code, String _message) {
this.code = _code;
this.message = _message;
}
public static APIResult search(int code) {
for (APIResult status : APIResult.values()) {
if(status.getCode() == code) {
return status;
}
}
return null;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
|
cs |
2. customized Exception class 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package ;
public class APIException extends RuntimeException {
private int ERR_CODE = APIResult.FAILURE.getCode();
public APIException() {
super();
}
public APIException(String errDesc) {
super(errDesc);
}
public APIException(String errDesc, int code) {
super(errDesc);
this.ERR_CODE = code;
}
public APIException(String errDesc, ConstantsAPI.APIResult e){
super(errDesc);
this.ERR_CODE = e.getCode();
}
public APIException(ConstantsAPI.APIResult e){
super(e.getMessage());
this.ERR_CODE = e.getCode();
}
public String getErrorDesc() {
return this.getMessage();
}
public int getErrCode() {
return this.ERR_CODE;
}
}
|
cs |
RuntimeException 을 상속받는 Exception 클래스를 작성한다.
생성자 오버로딩을 하여 Exception Message 및 Exception Code를 받을 수 있게 한다.
* Enum 클래스를 통으로 받는 생성자 또한 만들어준다.
ex)
APIException(ConstantsAPI.APIResult e){
//생략
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@RequestMapping(value = "/sample", method={RequestMethod.POST}, produces = "application/json; charset=UTF-8")
public ModelAndView sample(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> inParam) throws Exception {
Map<String, Object> result = new HashMap<String, Object>();
ModelAndView mav = new ModelAndView("jsonView");
try{
// inParam 유효성 체크
CommUtil.paramVaildate(new String[] {"tokenId"}, inParam);
sampleService.sampleMethod(inParam);
result.put("result_cd", APIResult.SUCCESS.getCode());
result.put("result_msg", "");
} catch (APIException ae) {
result.put("result_cd", ae.getErrCode());
result.put("result_msg", ae.getMessage());
} catch (Exception e){
result.put("result_cd", APIResult.FAILURE.getCode());
result.put("result_msg", e.getMessage());
}
mav.addAllObjects(result);
return mav;
}
|
cs |
* input parameter 유효성 검사
향상된 for문을 적절히 사용.
Map에 키가 담겨있지 않거나, 빈 값인 경우 구현해 놓았던 Custom Exception 클래스를 throw.
이때, 위에 작성해 놓은 enum을 활용.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public static boolean paramValidate(String[] params, Map<String, Object> inParam) {
boolean _result = true;
try {
for (String item : params) {
if(!inParam.containsKey(item)) {
throw new APIException(String.format("%s not exist", item), APIResult.E_PARAM_NULL);
}
if(String.valueOf(inParam.get(item)).isEmpty) {
throw new APIException(String.format("%s is null", item), APIResult.E_PARAM_NULL);
}
}
} catch(APIException e) {
throw e;
} catch(Exception e) {
throw new APIException(APIResult.FAILURE);
}
return _result;
}
}
|
cs |
여기까지만 해도 괜찮지만 Controller 마다 아래의 코드가 들어가는게 마음에 들지 않았다..
1
2
3
4
5
6
7
8
9
|
try{
} catch (APIException ae) {
result.put("result_cd", ae.getErrCode());
result.put("result_msg", ae.getMessage());
} catch (Exception e){
result.put("result_cd", APIResult.FAILURE.getCode());
result.put("result_msg", e.getMessage());
}
|
cs |
6줄 남짓의 코드를 지우기 위해
Exception을 공통(일괄?)처리 할 수 있는 @ControllerAdvice 및 @Exceptionhandler 를 사용하여 Exception 처리를 해보았으나..
Exception이 발생하는 경우, log를 찍기 위해 구현해 놓았던 interceptor 가 제대로 동작하지 않는 문제점이 발생.
구글링을 해보니, Exception 발생시 interceptor의 postHandle은 타지 않는단다..
아래는 구글링으로 찾은 내용..
From the javadoc of
Arguably the Actually the >> postHandle은 정상적인 메소드 실행에서만 작동. 예외시 preHandle, afterCompletion 만 호출됨.
(출처 : https://stackoverflow.com/questions/20551888/controlleradvice-methods-seem-to-bypass-interceptors) |
그래서 AOP를 사용해보았다.
4. AOP 사용
4-1) pom.xml 에 aop 사용을 위한 설정 추가
property에 아래의 내용 추가
1
|
<org.aspectj-version>1.6.10</org.aspectj-version>
|
cs |
dependency 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
|
cs |
4-2) SampleAOP 작성
@Around(pointCut)을 선언
@Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.*Controller.*(..))")
설명 >> public 접근제한자이면서, org.springframework.web.servlet.ModelAndView 리턴타입이면서,
com.sample. 밑의 Controller로 끝나는 모든 Class 밑의 모든 메소드(인자값 상관없이)가 실행될 때, 해당 함수(around(..))를 전, 후로 실행...
Controller(pjp.proceed()) 에서 예외 발생시 AOP에서 error code 및 error message를 넣어 return
* advice : @Around, @Before, @After 등.. 해당 어노테이션이 붙은 코드의 실행 시점을 의미
* pointCut : 정규식을 사용하여 어떤 joinPoint 를 사용할지 지정
* joinPoint : advice가 적용될 수 있는 메소드(함수)
* xml 빈 등록 잘못 할 경우 aop 를 두번 타는 문제가 발생할 수 있으니 주의.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package ;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
@Component
@Aspect
public class SampleAOP {
@Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.*Controller.*(..))")
public ModelAndView around(ProceedingJoinPoint pjp) {
ModelAndView mav = null;
try {
mav = (ModelAndView) pjp.proceed();
} catch(APIException e){
mav = new ModelAndView("jsonView");
e.printStackTrace();
Map<String, Object> result = new HashMap<String, Object>();
result.put("result_cd", e.getErrCode());
result.put("result_msg", e.getMessage());
mav.addAllObjects(result);
} catch (Throwable e) {
mav = new ModelAndView("jsonView");
e.printStackTrace();
Map<String, Object> result = new HashMap<String, Object>();
result.put("result_cd", 0);
result.put("result_msg", e.getMessage());
mav.addAllObjects(result);
}
return mav;
}
}
|
cs |
5. Controller 에서 try catch 처리 수정
예외처리를 AOP에서 일괄적으로 관리하니 기존의 try catch 를 지워준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@RequestMapping(value = "/sample", method={RequestMethod.POST}, produces = "application/json; charset=UTF-8")
public ModelAndView sample(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> inParam) throws Exception {
Map<String, Object> result = new HashMap<String, Object>();
ModelAndView mav = new ModelAndView("jsonView");
// inParam 유효성 체크
CommUtil.paramVaildate(new String[] {"tokenId"}, inParam);
sampleService.sampleMethod(inParam);
result.put("result_cd", APIResult.SUCCESS.getCode());
result.put("result_msg", "");
mav.addAllObjects(result);
return mav;
}
|
cs |
AOP 를 어설프게나마 사용해 보았다는 걸로 만족...
AOP 는 추후 더 공부해서 개념, 설정, 구현 부분을 따로 포스팅 해보겠다.
* 제 경우 기존 프로젝트에 aop 설정이 부분적으로 되어있었습니다.
위의 설정 및 소스를 그대로 따라할 경우 aop 동작이 제대로 안될 수도 있습니다.
'back > Spring Framework' 카테고리의 다른 글
[Spring Fw] POI(Excel lib) 를 사용하여 다운로드하기 (7) | 2019.07.15 |
---|---|
스프링프레임워크 log4j2 설정 : Spring Framework + log4j 2 + slf4j + jboss/wildfly(ver 9) (0) | 2019.05.10 |
jar(lib) 동적 빌드 및 배포(profiles 사용) (0) | 2019.04.26 |
스프링프레임워크 AOP를 사용한 로그 출력 : Spring + AOP + logger (0) | 2019.04.19 |
스프링 property 동적 설정(환경변수 사용) (0) | 2018.12.05 |