AOP 구성 요소
AOP에는 새로운 용어가 많이 등장한다. 이 중에서 특히 AOP를 이용해서 개발하는데 필요한 중요한 다음의 구성 요소들에 대한 정의를 정확히 이해해야 한다.
JoinPoint
Crosscutting Concerns 모듈이 삽입되어 동작할 수 있는 실행 가능한 특정 위치를 말한다.
예를 들어 메소드가 호출되는 부분 또는 리턴되는 시점이 하나의 JoinPoint가 될 수 있다. 또 필드를 액세스하는 부분, 인스턴스가 만들어지는 지점,
예외가 던져지는 시점, 등이 대표적인 JoinPoint가 될 수 있다.
각각의 JoinPoint들은 그 안에 Crosscutting Concerns의 기능이 AOP에 의해 자동으로 추가되어져서 동작할 수 있는 후보지가 되는 것이다.
Pointcut
Pointcut은 어떤 클래스의 어느 JoinPoint를 사용할 것인지를 결정하는 선택 기능을 말한다.
AOP가 항상 모든 모듈의 모든 JoinPoint를 사용할 것이 아니기 때문에 필요에 따라 사용해야 할 모듈의 특정 JoinPoint를 지정할 필요가 있다.
일종의 JoinPoint 선정 룰과 같은 개념으로 다음과 같은 Pattern Matching 방법들을 이용하여 룰을 정의하게 된다.
Pattern Matching Examples
1. Basics
- set*(..) : set으로 시작하는 모든 메소드명
- * main(..) : return type이 any type이고, 0개 이상의 any type parameter를 가진 main 메소드
2. Matching Type
- java.io.* : java.io 패키지 내에 속한 모든 요소
- org.myco.myapp..* : org.myco.myapp 패키지 또는 서브 패키지 내에 속한 모든 요소
- Number+ : Number 또는 Number의 서브 type으로 Integer, Float, Double ..등이 이에 해당
- !(Number+) : Number 또는 Number의 서브 type이 아닌 모든 type
- org.xyz.myapp..* && !Serializable+ : org.xyz.myapp 패키지 또는 서브 패키지 내에 존재하면서 Serializable type이 아닌 모든 요소
- int || Integer : int 또는 Integer type
3. Matching Modifiers
- public static void main(..) : 0개 이상의 any type parameter를 가진 public static void main 메소드
- !private * *(..) : return type이 any type이고, 0개 이상의 any type parameter를 가진 모든 메소드중 modifier가 private이 아닌 메소드
- * main(..) : modifier를 별도로 명시하지 않은 경우, default modifier가 아닌 any modifier 의미
4. Matching Parameter
- * main(*) : return type이 any type이고, 1개의 any type parameter를 가진 main 메소드
- * main(*,..) : return type이 any type이고, 최소 1개의 any type parameter를 가진 main 메소드
- * main(*,..,String,*) : return type이 any type이고, 최소 3개의 any type parameter를 가지며 끝에서 두번째 parameter type이 String인 main 메소드
5. Matching Constructor
- new(..) : 0개 이상의 any type parameter를 가진 constructor
- Account.new(..) : 0개 이상의 any type parameter를 가진 Account 클래스의 constructor
이제부터 앞서 정의한 Pattern Matching 방법을 이용하여, 본격적으로 Pointcut Designator(지시자)별 Pointcut 정의 방법에 대해
살펴보기로 하자.
Pointcut Designators
1. execution 또는 call
특정 메소드나 생성자 실행을 위한 JoinPoint를 정의하는 것으로, JoinPoint의 특정 method name, parameter types,
return type, declared exceptions, declaring type, modifiers에 대한 matching이 가능하며,
단, return type pattern, method name pattern, parameter list pattern은 필수적으로 정의해야 한다.
다음은 execution, call을 이용한 pointcut 정의 예이다.
- execution(* main(..)) : return type이 any type이고, 0개 이상의 any type parameter를 가진 main 메소드 실행시
- call(Account.new(..)) : any type parameter를 가진 Account 클래스의 constructor 호출시
2. get 또는 set
특정 Field에 접근하거나 특정 Field 수정을 위한 JoinPoint를 정의한다.
- get(Collection+ org.xyz.myapp..*.*) : Collection type의 org.xyz.myapp 패키지에 속한 any field에 대한 getter 호출시
- set(!private * Account+.*) : Account type의 non-private field에 대한 setter 호출시
3. handler
Exception 핸들링을 위한 JoinPoint를 정의한다.
- handler(DataAccessException) : matches cach(DataAccessException){...} and doesn't match catch(RuntimeException)
- handler(RuntimeException+) : matches both
4. within
특정 유형에 속하는 JoinPoint를 정의하며, 주로 &&, ||, ! 등과 함께 조합된 형태로 사용된다.
- within(*) : matches any JoinPoint
- within(org.xyz.myapp..*) : org.xyz.myapp 패키지 내에 속하는 모든 요소
- within(IInterface+) : IInterface type의 모든 요소
5. withincode
해당되는 메소드 또는 constructor 내에 정의된 코드를 위한 JoinPoint를 정의한다.
- withincode(!void get*()) : return type이 void가 아니고 메소드명이 get으로 시작하며 parameter가 없는 메소드 내의 코드
6. args
입력값의 개수, type 등에 대한 JoinPoint를 정의한다.
- call(* transfer(..)) && args(DepositAccount,CheckingAccount,*) : 메소드명이 transfer이고, 입력 인자가 2개 이상이며, 1,2번째 입력 인자의 type이 DepositAccount,CheckingAccount인 메소드 호출시
7. target
JoinPoint를 가진 target object의 type을 정의한다.
- call(* *(..)) && target(Account) : Account 클래스내의 모든 메소드 호출시
Advice
Advice는 각 JoinPoint에 삽입되어져 동작할 수 있는 코드로 동작 시점은 pointcut에 Matching되는 JoinPoint 실행 전후이며
before, after, after returning, after throwing, around 중에서 선택한다.
주로 메소드 단위로 구성된 Advice는 Pointcut에 의해 결정된 모듈의 JoinPoint에서 호출되어 사용된다.
일반적으로 독립적인 클래스 등으로 구현된 Crosscutting Concerns 모듈을 JoinPoint의 정보를 참조해서 이용하는 방식으로 작성된다.
Before Advice
Matching된 JoinPoint 전에 동작하는 Advice이다.
다음은 depositMadeAspartOfATransfer()라는 Pointcut 실행 전에 동작되어져야 할 Advice 예제이다.
before(): depositMadeAspartOfATransfer(){
// do something here...
}
After Advice
After Advice는 동작 시점에 따라 after (finally), after returning, after throwing 으로 구분할 수 있다.
1. after returning
Matching된 JoinPoint가 성공적으로 return된 후에 동작하는 Advice이다.
after() returning(Float f) : interestingJoinPoint {
System.out.println(f); // return 값을 콘솔에 남긴다.
}
2. after throwing
Exception이 발생하여 Matching된 JoinPoint가 종료된 후에 동작하는 Advice이다.
after() throwing(Exception ex) : interestingJoinPoint() {
System.out.println(ex.getMessage()); // Exception 메시지를 콘솔에 남긴다.
}
3. after (finally)
Matching된 JoinPoint 종료 후에 동작하는 Advice이며 잘 사용되지는 않는다.
after(): interestingJoinPoint() {
// do something here
// but be prepared to handle normal and exceptional returns
}
Around Advice
가장 강력한 Advice로 Matching된 JoinPoint 전, 후에 동작하며 JoinPoint 실행 시점을 결정할 수 있다.
또한 다른 Advice와는 달리 입력값, target object, return 값 등에 대해 변경이 가능하다.
Object around() : interestingJoinPoint() {
Object ret;
// before joinpoint..
ret = proceed(); // execute joinpoint
// after joinpoint..
return ret;
}
Weaving 또는 CrossCutting
Pointcut에 의해서 결정된 JoinPoint에 지정된 Advice를 삽입하는 과정이 Weaving이다.
Weaving은 AOP가 기존의 Core Concerns 모듈의 코드에 전혀 영향을 주지 않으면서 필요한 Crosscutting Concerns 기능을 추가할 수 있게 해주는 핵심적인 처리 과정이다.
다른 말로 CrossCutting이라고 하기도 한다. Weaving을 처리하는 방법은 후처리기를 통한 코드생성 기술을 통한 방법부터 특별한 컴파일러 사용하는 것,
이미 생성된 클래스의 정적인 바이트코드의 변환 또는 실행 중 클래스로더를 통한 실시간 바이트코드 변환 그리고 Dynamic Proxy를 통한 방법까지 매우 다양하다.
Aspect
Aspect는 어디에서(Pointcut) 무엇을 할 것인지(Advice)를 합쳐놓은 것을 말한다. AspectJ와 같은 자바 언어를 확장한 AOP에서는 마치 자바의 클래스처럼 Aspecct를 코드로 작성할 수 있다.
다음은 모든 클래스의 main 메소드 실행(pointcut main()) 후에 "Hello from AspectJ"라는 문자열을 남기는(after returning advice) Aspect
HelloFromAspectJ 코드의 일부이다.
public aspect HelloFromAspectJ{
// define pointcut
pointcut main(): execution(public static void main(String[]));
// define advice
after() returning : main() {
System.out.println("Hello from AspectJ!");
}
}