AOP (Aspect Oriented Programming)
관점 지향 프로그래밍.
말이 어렵지만 함수를 구현했을 때 함수 위쪽부터 아래쪽으로 진행되는 방향의 관점에서 벗어나 중간중간에 반복되는 메서드를 뽑아내는 가로 방향(횡단)의 관점으로 바라보는 프로그래밍을 말한다. (더 어려운가..)
여러가지 기능을 하는 서비스가 있을 때 반복되는 메서드를 모듈화함으로써 더 간단하고 보기 쉬운 코드를 만들 수 있다.
예를 들면 메서드 실행 전후에 무조건 로그를 찍는다던가, 메서드 실행 전 세션을 확인한다던가 하는 공통된 작업을 따로 구분하여 분리해놓으면 반복 작업을 줄일 수 있어 조금 더 간편하게 코드를 작성할 수 있다.
세션 확인을 위한 aop를 구현하면서 내가 필요했던 기능 리스트들이다.
- 모든 기능 실행 전 세션 확인하기 (로그인, 회원가입 기능 제외)
- 같은 컨트롤러 내에서 Login, SignUp 메서드만 제외하기
- POST, GET 메서드 구분하기 (세션키를 전달해주는 객체가 다르기 때문)
- 에러 발생 시 Exception 처리가 아닌 JSON 으로 fail data 전달하기
위의 기능들을 구현하면서 부딪혔던 문제들과 해결방법들.
- controller 내에서 일부 메서드만 제외할 수 없음. (이건 결국 방법을 못 찾았다...ㅠㅠ)
- 메서드 실행 전 세션을 체크하는 일이라서 @Before 어노테이션을 사용해야 될 것이라 생각했다.
- 하지만 메서드 전에 세션 확인을 하고 결과 값을 리턴하기 때문에 @Around 를 사용해야 했다.
- Exception 처리를 하지 않기 때문에 어쨌든 return 값을 반환하게 되는데 exception이 발생할 때마다 자꾸 에러가났다.
- 기존 메서드와 리턴 타입을 동일하게 맞춰주지 않았기 때문에 발생하는 문제였다. aop 로 들어오는 메서드의 리턴타입이 string 이라면 aop에서 error가 발생하여 fail data를 보내더라도 반드시 string타입으로 전달해야 했다.
1번 문제에서는 같은 controller 패키지 안에 있는 메서드 중에서 Login, SignUp 메서드만 제외하려고 pointcut을 만들었다.
@Around"execution(* com.example.controller..*.*(..)) && !LoginPointCut" 표현식으로 만들어보았으나 적용되지 않았다..ㅠㅠ
결국 controller2 패키지를 만들어 로그인 메서드를 옮겼더니 aop에서 제외되었다.
특히 고생했던 건 3번 문제... 들어오는 메서드의 리턴타입을 파악하여 aop exception 리턴 값을 변경해주어야 했다.
ProceedingJoinPoint 객체를 사용하여 메서드가 포함된 클래스를 구한 후, 클래스를 이용하여 method name 과 method return type을 구해야 했다. (더 좋은 방법이 있는지는 모르겠다.)
(아래 코드에서 method process 주석 아래 부분 참고.)
다행히 controller 패키지 안에 있는 메서드의 리턴타입이 json 아니면 string 이었기 때문에, 예외처리 시 json 으로 fail data 를 만든 후 리턴타입이 string 이면 json을 string으로 변환해주기만 하면 되었다.
하지만 만약에 모든 메서드의 리턴타입이 다르다면.. ...... . .. .. 끔찍하다. 같은 에러를 리턴타입 별로 설정해주어야 한다.
더 좋은 방법은 없을까?
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
@Aspect
public class SessionAspect {
private static final Logger log = LoggerFactory.getLogger(This.class);
@Around("execution(* com.example.controller..*.*(..)) ")
public Object sessionCheck( ProceedingJoinPoint jp ) throws NoSuchMethodException, SecurityException, ClassNotFoundException {
/**
* POST GET 구분
*
*/
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
boolean post = request.getMethod().equals("POST");
String token = null;
JsonNode resultJson = Json.newObject();
if( post ) {
//HTTP POST
resultJson = Json.toJson(jp.getArgs()[0]);
System.out.println("aop resultJson:" + resultJson);
token = resultJson.get("session-key").toString().replace("\"", "");
} else {
//HTTP GET
token = request.getParameter("session-key");
}
System.out.println("aop token : " + token);
/**
* method process
*
*/
String className = jp.getSignature().getDeclaringTypeName();
System.out.println(className);
//aop 로 들어온 메서드가 포함된 class
Class<?> cls = Class.forName(className);
//aop 로 들어온 메서드 이름
String methodName = jp.getStaticPart().getSignature().getName();
//class 에 포함된 모든 메서드
Method[] methods = cls.getMethods();
Method method = null;
for(Method m : methods) {
if( m.getName().equals(methodName) ) {
method = m;
}
}
String fullReturnType = method.getReturnType().getName();
int idx = fullReturnType.lastIndexOf(".");
String returnType = fullReturnType.substring( idx + 1 );
System.out.println("method returnType : " + returnType);
/**
* session check
*
*/
Object obj = null;
ReturnJson json = new ReturnJson();
if( SessionData.FindSession(token) ) {
// get tokenData
TokenData tokenData = SessionData.webSession.get(token);
// update session
SessionData.UpdateSession(token, tokenData);
try {
System.out.println("============================== 세션 확인 완료.");
//기존 메서드 실행
obj = jp.proceed();
} catch (Throwable e) {
//3 : 메서드 에러
obj = json.makeResult("3", "fail-메서드 에러.", null);
log.info("메서드 에러.", e.getMessage());
e.printStackTrace();
}
}else {
//2 : 세션 키값 불일치 or 세션 만료
obj = json.makeResult("2", "fail-세션 확인.", null);
log.info("세션 확인.", new Exception("세션 확인.") );
}
//메서드 리턴타입과 일치시키기 위한 조건문
if(returnType.equals("String")) {
obj = obj.toString();
}
return obj;
}
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
|
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs |
이번 aop를 구현하면서 내가 배운 것은
Http Request 에서 POST, GET 방식 구분하기.
session Check
메서드 리턴타입 일치시키기.
'프로그래밍 > Java' 카테고리의 다른 글
[java] request.getParameter 의 Content-Type (0) | 2020.03.29 |
---|---|
logback 설정하기 (properties 를 통한 외부설정) (0) | 2019.11.13 |
Spring Boot로 Json 데이터 처리하기 (@ResponseBody, @RequestBody 어노테이션) (1) | 2019.11.03 |