본문 바로가기
프로그래밍/Java

AOP로 세션 확인하기 - @Around 어노테이션, 메서드 리턴 값과 일치시키기

by 동네로봇 2019. 11. 20.

AOP (Aspect Oriented Programming) 

관점 지향 프로그래밍.

말이 어렵지만 함수를 구현했을 때 함수 위쪽부터 아래쪽으로 진행되는 방향의 관점에서 벗어나 중간중간에 반복되는 메서드를 뽑아내는 가로 방향(횡단)의 관점으로 바라보는 프로그래밍을 말한다. (더 어려운가..)

 

여러가지 기능을 하는 서비스가 있을 때 반복되는 메서드를 모듈화함으로써 더 간단하고 보기 쉬운 코드를 만들 수 있다. 

 

예를 들면 메서드 실행 전후에 무조건 로그를 찍는다던가, 메서드 실행 전 세션을 확인한다던가 하는 공통된 작업을 따로 구분하여 분리해놓으면 반복 작업을 줄일 수 있어 조금 더 간편하게 코드를 작성할 수 있다. 

 


 

세션 확인을 위한 aop를 구현하면서 내가 필요했던 기능 리스트들이다.

 

  1.  모든 기능 실행 전 세션 확인하기 (로그인, 회원가입 기능 제외)
  2.  같은 컨트롤러 내에서 Login, SignUp 메서드만 제외하기
  3.  POST, GET 메서드 구분하기 (세션키를 전달해주는 객체가 다르기 때문)
  4.  에러 발생 시 Exception 처리가 아닌 JSON 으로 fail data 전달하기

 

위의 기능들을 구현하면서 부딪혔던 문제들과 해결방법들.

 

  1. controller 내에서 일부 메서드만 제외할 수 없음. (이건 결국 방법을 못 찾았다...ㅠㅠ)
  2. 메서드 실행 전 세션을 체크하는 일이라서 @Before 어노테이션을 사용해야 될 것이라 생각했다.
    •  하지만 메서드 전에 세션 확인을 하고 결과 값을 리턴하기 때문에 @Around 를 사용해야 했다.
  3. 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

메서드 리턴타입 일치시키기.