본문 바로가기
Programming/Spring

AOP(Aspect Oriented Programming)

by TinKerBellBass 2017. 8. 2.
728x90
반응형
참고 도서
스프링 퀵 스타트
국내도서
저자 : 채규태
출판 : 루비페이퍼 2016.06.30
상세보기



기본 개념 정리

OOP(Objet Oriented Programming)으로는 공통 코드를 완벽하게 분리해 내기가 힘들어 등장한 것이
관심(관점)에 중점을 두어 공통코드를 분리해 내는 AOP(Aspect Oriented Programming)이다.



그림과 같이 클라이언트의 요청을 처리하기 위해 만들어진 모듈들 사이에는

로깅, 보안, 트랜잭션 처럼 공통적으로 수행되어야 하는 부가적인 기능들이 있고,

각각의 모듈들이 실제로 클라이언트의 요청을 처리하는 핵심 기능들이 있다.


부가적인 기능에 관심을 두어 횡단관심이라 하고,

핵심 기능에 관심을 두어 핵심관심이라 한다.


횡단관심(Crosscutting Concerns)과 핵심관심(CoreConcerns)을 분리해 내서(관심 분리, Separation of Concerns) 

횡단관심을 하나의 클래스에 담아 코드의 중복을 줄이고, 공통적으로 사용하는 것이 것이 AOP의 핵심 개념이다.




조인포인트(Joinpoint)

클라이언트가 호출하는 모든 비지니스 메소드


포인트컷(Pointcut)

조인포인트 중 횡단관심을 수행해야 하는 특정 메소드를 찾아내는 것

예를 들어 등록, 수정, 삭제, 검색 기능(조인포인트)을 수행하는 메소드가 있다면,

등록, 수정, 삭제 기능에는 트랜잭션이라는 횡단관심을 수행해야 하지만

검색에는 수행하지 않아도 된다(포인트컷).


어드바이스(Advice)

횡단관심에 해당하는 공통 기능의 코드로 독립된 클래스의 메소드로 작성한다.

어드바이스가 포인트컷을 통해 수행되는 시기를 스프링 설정을 통해 지정할 수 있다.

트랜잭션이라는 어드바이스는 비지니스 로직을 수행하기 전이 아닌 수행 후에 이루어져야 한다.


위빙(Weaving)

포인트컷으로 지정한 핵심 관심이 호출될 때, 어드바이스인 횡단관심이 삽입되는 과정


애스팩트(Aspect) 또는 어드바이저(Advisor)

포인트컷과 어드바이스의 결합을 의미. 어떤 포인트컷에 대해서 어떤 어드바이스를 실행할지 결정


정리

클라이언트는 비지니스 컴포넌트의 여러 조인포인트를 호출한다. 이때 특정 포인트컷으로 지정한 메소드가

호출되는 순간 어드바이스 객체의 어드바이스 메소드가 실행된다. 어드바이스 메소드의 실행 시점은

After, After-Returning, After-Throwing, Around의 다섯 가지 방식으로 지정할 수 있으며,

포인트컷으로 지정한 메소드가 호출될 때, 어드바이스 메소드가 삽입되게 하는 설정을 애스팩트라고 한다.

그리고 애스팩트 설정에 따라 위빙이 처리된다.


1. AOP 라이브러리 추가

메이븐 기준으로 Maven Repository(https://mvnrepository.com/)에서 aspectjweaver를 검색하여
해당 dependency를 pom.xml에 추가.
bean 객체들이 생성되는 XML 설정파일의 Namespace에서 aop를 추가.

2. AOP 엘리먼트

1
2
3
4
<aop:config>
    <aop:pointcut.../>
    <aop:aspect...></aop:aspect>
</aop:config>
cs

<aop:config>
루트 엘리먼트

<aop:pointcut>
포인트 컷 지정

<aop:aspect>
핵심관심에 해당하는 포인트컷 메소드와 횡단관심에 해당하는 어드바이스 메소드를 결합

실제 동작

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package log;
 
public class LogAdvice{
    // 포인트컷으로 지정한 메소드가 호출될 때 실행되는 횡단관심
    public void printLog(){
        System.out.print("횡단관심");
    }
}
 
<!-- 횡단관심 메소드가 모여 있는 클래스에서 bean 객체를 생성 
package log; public class LogAdvice ==> class="log.LogAdvice" -->
<bean id="log" class="log.LogAdvice" />
 
<aop:config>
    <!-- 횡단관심을 실행할 포인트 컷 설정 -->
    <aop:pointcut id="allPointcut" expression="execution(* com.neverland.aop..*Impl.*(..))"/>
    <!-- 포인트 컷으로 설정한 메소드에서 횡단관심을 어떻게 실행할 것인가 설정, bean id="log" ==> ref="log" --> 
    <aop:aspect ref="log">
        <!-- allPointcut으로 설정한 포인트컷 메소드가 실행될 때, log(ref="log") 라는 어드바이스 객체의
             printLog()(method="printLog()") 메소드가 동작. 동작 시점은 포인트 컷 메소드 실행 전(aop:before) -->
        <aop:before point-cut-ref="allPointcut" method="printLog()"/>
</aop:aspect>
</aop:config>
 cs


3. 포인트컷 설정 방법

execution(* com.neverland.aop..*Impl.get*(..))
리턴타입 + 패키지 경로 + 클래스명 + 메소드명,매개변수

리턴타입
execution(* com.neverland.aop..*Impl.get*(..))

 * 

 모든 타입 허용

 void

 리턴타입이 void

 !void

 리턴타입이 void가 아닌 것


패키지 지정
execution(* com.neverland.aop..*Impl.get*(..))

 com.neverland.aop

 정확하게 com.neverland.aop 패키지만 선택

 com.neverland.aop..

 com.neverlnad.aop 패키지로 시작하는 모든 패키지 선택

 com.neverland..impl

 com.neverland 패키지로 시작하면서 마지막 패키지 이름이 impl로 끝나는 패키지 선택


클래스 지정

execution(* com.neverland.aop..*Impl.get*(..))

 InsertServiceImpl

 정확하게 InsertServiceImpl 클래스만 선택

 *Impl

 클래스 이름이 Impl로 끝나는 클래스만 선택

 InsertService+

 해당 클래스로부터 파생된 모든 자식 클래스 선택, 인터페이스 뒤에 +가 붙으면 구현한 모든 클래스 선택


메소드 지정(클래스와 메소드 사이에 . 붙는 것 주의)

execution(* com.neverland.aop..*Impl.get*(..))

 *(..) 

 모든 메소드 선택

 get*(..) 

 메소드 이름이 get으로 시작하는 모든 메소드 선택


매개변수 지정

execution(* com.neverland.aop..*Impl.get*(..))

 (..)

 매개변수의 개수와 타입에 제약 없음

 (*) 

 반드시 1개의 매개변수를 가지는 메소드만 선택

 (com.neverland.aop.UserDao)

 매개변수로 UserDao를 가지는 메소드만 선택(클래스의 패키지 경로 반드시 포함)

 (!com.neverland.aop.UserDao)

 매개변수로 UserDao를 가지지 않는 메소드만 선택

 (Integer, ..)

 한 개 이상의 매개변수를 가지며, 첫 번째 매개변수 타입이 Integer인 메소드만 선택

 (Integer, *)

 반드시 2개의 매개변수를 가지며, 첫 번째 매개변수 타입이 Integer인 메소드만 선택


4. JoinPoint 메소드

JoinPoint 인터페이스

 Signature getSignature( ) 

 클라이언트가 호출한 메소드의 시그니처 정보(리턴타입, 이름, 매개변수)를 리턴  

 Object getTarget( )

 클라이언트가 호출한 비지니스 메소드를 포함하는 비지니스 객체 리턴

 Object[ ] getArgs( )

 클라이언트가 메소드를 호출할 때 넘겨준 인자 목록을 리턴


ProceedingJoinPoint 인터페이스 (JoinPoint 인터페이스 상속, Around 작동 방식에 사용)

 Object proceed( )

클라이언트가 호출한 비지니스 메소드를 호출 


Signature 인터페이스

 String getName( )

 클라이언트가 호출한 메소드 이름 리턴 

 String toLongString( )

 클라이언트가 호출한 메소드의 리턴타입, 이름, 매개변수 리턴(패키지 경로까지 포함)

 String toShortString( )

 클라이언트가 호출한 메소드 시그니처를 요약하여 리턴


5. 어드바이스 동작 시점(XML)

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
<!-- bean 객체 생성 -->
<bean id="log" class="com.neverland.logAct.LogAct" />
<!-- 동작시점에 before / after-returning / after-throwing / after / around 의 다섯 가지 동작 시점을 기입 -->
<aop:config>  
    <aop:pointcut id="allPointcut" expression="execution(* com.neverland.aop..*Impl.*(..))"/>     
    <aop:aspect ref="log">
   <aop:동작시점 pointcut-ref="설정한 포인트 컷의 id" method="어드바이스 클래스의 메소드 이름" />
  </aop:aspect>
</aop:config> 

 
/*
개략적으로 표현한 비지니스 메소드
public UserVO getUser(UserVO vo){
    UserVO user = New UserVO();
    return user;
}
*/
 
package com.neverland.logAct;
 
public class LogAct{
 
    // <aop:before pointcut-ref="설정한 포인트 컷의 id" method="beforeLog" />
    // 비지니스 메소드가 실행되기 전에 동작
    public void beforeLog(JoinPoint jp){
        String method = jp.getSignature().getName();
        Object[] args = jp.getArgs();
 
        //getUser() 출력
        System.out.println("클라이언트가 호출한 메소드 이름: " + method); 
        //UserVO 출력(UserVO를 toString()으로 오버라이딩 해서 구현했으면, UserVO의 정보도 출력
        System.out.println("클라이언트가 호출한 메소드 인자: " + arg[0].toString());         
    }
 
 
    // <aop:after-returning pointcut-ref="설정한 포인트 컷의 id" method="afterReturningLog" returning="returnObj" />
    // 비지니스 메소드가 정상적으로 실행되고 난 후, 결과 데이터를 리턴하는 시점에 동작
    // Object returnObj 바인드 변수, 비니지스 메소드가 리턴한 결괏값을 바인딩, XML에 returning="returnObj" 설정
    public void afterReturningLog(JoinPoint jp, Object returnObj){ 
        String method = jp.getSignature().getName();
        
        if(returnObj instanceof UserVO){
            UserVO user = (UserVO) returnObj;            
        }
        
        // getUser() 출력
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);
        //비지니스 메소드 실행후 리턴(user)되는 UserVO 출력(UserVO를 toString()으로 오버라이딩 해서 구현했으면, UserVO의 정보도 출력 
        System.out.println("클라이언트가 호출한 메소드 리턴값: " + returnObj.toString());
    }
 
 
    // <aop:after-throwing pointcut-ref="설정한 포인트 컷의 id" method="afterThrowingLog" throwing="exceptObj" />
    // 비지니스 메소드가 실행되다가 예외가 발생할 때 동작
    // Exception exceptObj 바인드 변수, 비니지스 메소드에서 발생한 예외 객체 바인딩, XML에 throwing="exceptObj" 설정
    public void afterThrowingLog(JoinPoint jp, Exception exceptObj){ 
        String method = jp.getSignature().getName();
                
        // getUser() 출력
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);
        // 예외 메시지 출력 
        System.out.println("클라이언트가 호출한 메소드 실행중 발생한 예외 메시지: " + exceptObj.getMessage());
    }
 
 
    // <aop:after pointcut-ref="설정한 포인트 컷의 id" method="finallyLog" />
    // try-catch-finally 구문의 finally 블록처럼 예외 발생 여부에 상관 없이 비지니스 메소드 실행 후 무조건 동작    
    public void finallyLog(JoinPoint jp){ 
        String method = jp.getSignature().getName();
                
        // getUser() 출력
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);        
    }
 
 
    // <aop:around pointcut-ref="설정한 포인트 컷의 id" method="aroundLog" />
    // 비지니스 메소드 호출 자체를 가로채 실행 전후에 동작
    // ProceedingJoinPoint 를 매개 변수로 사용(proceed() 메소드)
    public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable{ 
        String method = pjp.getSignature().getName();
        
        System.out.println("비지니스 메소드 수행 전에 동작");
        Object returnObj = pjp.proceed();
        System.out.println("비지니스 메소드 수행 후에 동작");
                
        // getUser() 출력
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);
        
        return returnObj;
    }
 
}
 
cs


6. 어드바이스 동작 시점(어노테이션)

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
// XML 파일에 <aop:aspect-autoproxy/> 선언, 스프링 컨테이너가 AOP 관련 어노테이션을 인식하고 처리 
/*
개략적으로 표현한 비지니스 메소드
public UserVO getUser(UserVO vo){
    UserVO user = New UserVO();
    return user;
}
*/
 
package com.neverland.logAct;
 
// 공통되는 포인트컷 추출
@Aspect
public class CommonPointcut{
    @Pointcut("execution(* com.neverland.aop..*Impl.get*(..))")
    public void getPointcut(){}
}
 
 
@Service    // 객체 생성
@Aspect        // Aspect 설정, Aspect = 포인트컷 + 어드바이스
public class LogAct{
    
/*
애스펙트 선언 클래스 내에 포인트컷 설정
@Pointcut("execution(* com.neverland.aop..*Impl.get*(..))")
public void getPointcut(){}
@before("getPointcut()");
public void beforeLog(){
}
*/
 
    // 아래 메소드는 모두 외부 포인트컷 참조
 
    // 비지니스 메소드가 실행되기 전에 동작
    @Beford("CommonPointcut.getPointcut()"// 클래스명.포인트컷 메소드명
    public void beforeLog(JoinPoint jp){
        String method = jp.getSignature().getName();
        Object[] args = jp.getArgs();
 
        
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);     
        System.out.println("클라이언트가 호출한 메소드 인자: " + arg[0].toString());         
    }
 
    
    // 비지니스 메소드가 정상적으로 실행되고 난 후, 결과 데이터를 리턴하는 시점에 동작
    @AfterReturning(pointcut="CommonPointcut.getPointcut()", returning="returnObj")    
    public void afterReturningLog(JoinPoint jp, Object returnObj){ 
        String method = jp.getSignature().getName();
        
        if(returnObj instanceof UserVO){
            UserVO user = (UserVO) returnObj;            
        }        
        
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);    
        System.out.println("클라이언트가 호출한 메소드 리턴값: " + returnObj.toString());
    }
 
    
    // 비지니스 메소드가 실행되다가 예외가 발생할 때 동작
    @AfterThrowing(pointcut="CommonPointcut.getPointcut()", throwing="exceptObj")        
    public void afterThrowingLog(JoinPoint jp, Exception exceptObj){ 
        String method = jp.getSignature().getName();
                
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);    
        System.out.println("클라이언트가 호출한 메소드 실행중 발생한 예외 메시지: " + exceptObj.getMessage());
    }
 
 
    // try-catch-finally 구문의 finally 블록처럼 예외 발생 여부에 상관 없이 비지니스 메소드 실행 후 무조건 동작
    @After("CommonPointcut.getPointcut()")        
    public void finallyLog(JoinPoint jp){ 
        String method = jp.getSignature().getName();
                
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);        
    }
 
 
    // 비지니스 메소드 호출 자체를 가로채 실행 전후에 동작
    @Around("CommonPointcut.getPointcut()")        
    public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable{ 
        String method = pjp.getSignature().getName();
        
        System.out.println("비지니스 메소드 수행 전에 동작");
        Object returnObj = pjp.proceed();
        System.out.println("비지니스 메소드 수행 후에 동작");
                
        System.out.println("클라이언트가 호출한 메소드 이름: " + method);
        
        return returnObj;
    }
 
}
 
cs


728x90
반응형

'Programming > Spring' 카테고리의 다른 글

Spring Layered Architecture  (0) 2017.08.09
Spring MVC (annotation 기반)  (0) 2017.08.08
Spring MVC (XML 설정 기반)  (0) 2017.08.08
Spring JDBC (JdbcTemplate class)  (0) 2017.08.05
IoC(Inversion of Control) 컨테이너  (0) 2017.08.01

댓글