JAVA/SPRING 2011. 6. 29. 14:04
메인 출처 : http://ukja.tistory.com/

서브 출처 : http://blog.naver.com/ypark197?Redirect=Log&logNo=90093937410

AOP(Aspect Oriented Programming) in Java - Part I


Enterprise Java 환경에서 최근 가장 주목받는 기술 중 하나는 AOP, 즉 Aspected Oriented Programming 이다. AOP가 주목받는 이유는 Enterprise Java 시스템이 맞닥뜨리고 있는 복잡성때문이다.

(AOP의 정의에 대해서는 Wikipedia를 참조하기 바란다)

AOP는 전통적인 객체 지향의 프로그래밍(Object Oriented Programming. 이하 OOP)이 실패한 곳에서 진가를 발휘한다. OOP 언어들은 견고한 철학과 다양하고 유연한 프로그래밍 기법, 다양한 패턴들의 체계화등을 제공한다. 프로그래머들은 이로 인해 절차식 프로그래밍 언어들을 사용할 때에 비해 훨씬 직관적이고 체계적이고 모듈화된 구현을 할 수 있게 되었다. 만세!!!

하지만,불행히도 OOP 언어가 해결하지 못하는 몇가지 중요한 문제점들이 대두되었다.

가장 간단하고 직관적인 예로 로깅(Logging) 기능을 생각해 보자. OOP 언어의 대표격인 Java에서의 전통적인 Logging 구현 방법은 다음과 같다.

public class OrderManager {

public void orderItem(OrderItem oi) {
Logger log = Logger.getLogger("order");
log.info("orderItem started...");
// do something
try {
doSomething(oi);
} catch(Exception ex) {
log.info("Error : " + ex.getMessage());
} finally {
log.info("orderItem finished...");
}
}

Logger라는 객체를 이용해서 로깅 기능을 잘 모듈화했지만, 실제 이 객체를 사용하다보면 몇가지 심각한 문제를 만나게 된다.

  • 로직을 구현하는 핵심 소스에 많은 수의 로깅 관련 소스가 삽입됨으로써 소스의 가독성과 간결함을 심하게 훼손한다.
  • 로깅을 남기는 방법이나 시점, 위치 등을 바꾸려면 거의 모든 소스파일을 변경해야 한다.

위와 같이 "어떻게 하면 로깅을 남기는 기능을 핵심 로직과 무관하게 구분해서 구현할 수 있을까"라는 간단하고 명확한 요구에 대해 OOP는 적절한 대답을 하지 못한다.여기가 AOP가 개입되는 부분이다.

AOP는 객체를 프로그래밍 단위로 하지 않고, 특정 사건의 발생 양상(또는 상황/국면)을 프로그래밍의 단위로 한다. 이런 의미에서 AOP를 한글로 번역하자면상황 지향 프로그래밍이 가장 적절한 것으로 생각된다.

위의 로깅 예제에서는 다음과 같은 상황들이 펼쳐진다.

  1. orderItem 메소드가 시작하는 상황에 로그를 기록하라.
  2. orderItem 메소드가 끝나는상황에 로그를 기록하라.
  3. orderItem 메소드내에서 Exception이 발생하는 상황에 로그를 기록하라.

AOP에서는 위의 상황들을 정의할 수 있고, 이런 상황이 발생했을 때 수행할 작업을 정의할 수 있다. 즉, 핵심 로직과 무관하게 핵심 로직을 수행하는 과정에서 발생하는 상황들을 적절하게 다룰 수 있다.

Java AOP의 대표적인 언어인 AspectJ를 이용하면위의 소스를 다음과 같이 변경할 수 있다.

// 놀랍게도 핵심 로직에서는 Logging 관련된 부분이 완전히 제거된다.

public class OrderManager {

public void orderItem(OrderItem oi) {
// do something
try {
doSomething(oi);
} catch(Exception ex) { }
}

// 핵심 로직에서 발생하는 상황에 대한 로직을 구현한다.

public aspect LoggingAspect {

// OrderManger내에서 발생하는 모든 Method Call에 대해

pointcut methodExec() :
call(* *.*(..)) &&within(OrderManager);

//Method Call 시작시수행할 일

before() : methodExec() {
Logger log = Logger.getLogger("order");
log.info(thisJoinPointStaticPart.getSignature() + " started...");
}

//Method Call 종료시 수행할 일
after() returning : methodExec() {
Logger log = Logger.getLogger("order");
log.info(thisJoinPointStaticPart.getSignature() + " finished...");
}

// Exception 발생시
after() throwing(RuntimeException ex) : methodExec() {
Logger log = Logger.getLogger("order");
log.info("Error : " + ex.getMessage());
}
}

위와 같이 AOP를 이용하면 로깅과 같은 Aspect라는 이름으로 비핵심 로직을 핵심 로직에서 완전히 분리할 수 있으며, 이후 로깅과 관련된 변경 사항은 핵심 로직을 담당하는 소스의 변경이 아닌 Aspect의 변경만으로 이루어진다.

지금까지 AOP의 등장 배경에 대해 간략하게 살펴보았다. 다음 글을 통해 AOP의 개념 및 AOP의 대표 구현체인 AspectJ에 대해 좀 더 상세하게 논의해보자.

 

 

 

AOP의 개념 - part2

AOP에서는 위와 같이 핵심 로직 구현에서 부가적으로 처리해야 하는 작업들을 Concern(걱정/관심)이라고 부른다. Concern의 특징은 핵심 로직을 구현하는 여러 객체들에 걸쳐져 있다는 것이다. 가령 로깅 Concern은 핵심 로직을 구현하는 모든 객체와 관련되어 있다. 이런 의미에서는 흔히 Cross-cutting Concern이라는 용어를 쓴다. 우리말로 번역하면 "횡단-절단 관심" 정도가 될 것이다.

그림으로 표현하면 아래와 같지 않을까...?


즉 AOP는 객체가 구현하는 핵심 로직에 존재하는 여러 가지 Concern(관심거리, 혹은 걱정거리)을 구현하기 위한 프로그래밍 방법론으로 이해할 수 있다. 핵심 로직을 수행하는 과정에서 발생하는 로그 기록이라는 걱정 거리, 데이터베이스와의 통신 과정에서 발생하는 트랜잭션 관리라는 걱정 거리, 핵심 로직을 수행하는 과정에서 발생하는 성능을 측정하고자 하는 걱정 거리 등... 여러 객체와 행위를 관통하는(Cross-cutting)하는 걱정거리를 보다 손쉽게 구현하고자 하는 것이 바로 AOP의 핵심인 것이다.

AOP의 용어

AOP에 대해 더 상세하게 논의하기 전에 AOP에서 사용하는 용어에 대해 간략하게 정리해보자. 용어를 정확하게 이해해야만 AOP의 기법을 이해할 수 있다.

(정확하게 말하면 아래 용어는 AOP의 용어가 아니라 AspectJ의 용어이다. 하지만 AspectJ가 AOP의 사실상의 표준이기 때문에 무방하다고 간주한다)

  • joinpoint : 프로그램 수행 과정에서의 특정 지점. 생성자의 호출, 메소드의 호출, 오브젝트 필드에 대한 접근 등의 대표적인 joinpoint들이다.
  • pointcut : joinpoint와매칭하고자 하는 조건과 그 상황에서의 값들
  • advice : pointcut에 의해매칭된 joinpoint에서 실행할 작업
  • aspect : pointcut과 advice의 집합체. 즉 특정 상황(pointcut)과 그 상황에서 수행할 작업(advice)의 집합
  • weaving : aspect과 핵심 로직을 섞는(weave) 것을 의미

이를 그림으로 표현하면 다음과 같다.




Weaving(직조)은 Aspect와 Object, 또는 Concern과 Logic을, 또는 횡과 종을 합친다는 의미이다. 실을 횡과 종으로 이어서 천을 짜는 것을 생각하면 된다. 가령 AspectJ는컴파일시 Weaving과 로딩타임시 Weaving을 제공한다.
  • 컴파일시 Weaving : Java/AspectA 소스파일을 컴파일하는 시점에 Weaving을 수행
  • 로딩타임시 Weaving: Java Class 파일을 ClassLoader에 의해 로딩하는 시점에 Weaving을 수행

다음으로 계속...

 

 

 

AspectJ - part3

AspectJ는 Java에서의 AOP 언어의 사실상의 표준이다. Spring AOP, JBoss AOP 등 새로운 AOP 컴포넌트들이 있지만, 모두 AspectJ의 서브셋으로 보아도 무방할 것이다.

AspectJ의 특징과 역사는 Wikipedia에 잘 기술되어 있다. (고맙게도 ...)

현재 AspectJ은 Eclipse Project의 서브 프로젝트로 관리되고 있으며, IBM 영국 연구소의 개발자들이 핵심 멤버로 활동하고 있다. 따라서 Eclipse가 계속 존재하는 한, AspectJ 또한 계속 유지보수가 이루어질 것을 기대할 수 있다. ^^

AspectJ는 1.5 이후에 큰 변화를 겪었는데, 바로 AspectWerkz라는 이름의 AOP 프로젝트를 흡수한 것이다. 이 흡수로 인해 AspectJ는 1) 로드타임시 Weaving(Load Time Weaving. LTW) 지원, 2) Java 5 스타일의 Annotation 지원이라는 새롭고 강력한 특징을 가지게 되었다.

이 중, 특히 LTW 기능에 주목할 필요가 있다. 이전 버전의 AspectJ에서는 반드시 AspectJ가 제공하는 컴파일러를 이용해 사용자가 작성한 Class 파일이나 Jar 파일에 대해 Weaving 작업을 수행해야 했다. 한 프로젝트에서 모든 소스를 다 스스로 작성하는 경우는 모르겠지만, 다른 써드 파티가 제공하는 라이브러리를 사용하는 경우에는 확실히 번거로운 점이 있다.

LTW 기능을 이용하면 사용자의 Class 파일을 로딩하는 시점에(즉 실행시) Aspect를 Class와Weaving할 수 있다. 따라서 별도의번거로운 컴파일 과정을 거치지 않아도된다.

하지만!!! 속도 면에서는 컴파일시Weaving이 더유리할 수 있다는 점만은 기억해두자.특히 프로그램 실행 초기에 클래스가 로딩되는 속도가 눈에띄게느려질 수 있다는 점은 기억해둘 필요가 있다.

AspectJ 다운받고 사용하기

AspectJ는 http://www.eclipse.org/aspectj/index.php에서 다운받을 수 있다. 다음 두가지를 다운받아야 한다.

  • AspectJ 1.5.3 - AspectJ 메인 릴리즈
  • AJDT - AspectJ Development Tools. Eclipse Project내에서 AspectJ를 사용할 수 있는 플러그인을 제공

AJDT를 사용하면 Eclipse의 풍부한 기능과 함께 비주얼하게 AspectJ를 사용할 수 있으므로 보다 손쉽게 AspectJ에 접근할 수 있다.

AJDT를 설치하고 나면, 아래 그림과 같이 AspectJ를 기본으로 하는 프로젝트와 Aspect를 손쉽게 생성할 수 있다.

<< AspectJ 프로젝트 생성>>

<< Aspect 생성>>

다음 파트에서 AJDT를 이용해 간단한 AOP 샘플을 구현할텐데, 다음과 같은 Concern을 해결하는 것을 목표로 한다.

" 현재 운영 중인 시스템에서 코드상의 오류로 인해 Exception이 계속해서 발생한다. Exception이 발생할 때마다 발생한 Exception을 파악하고, Exceptioon의 발생 시점, 발생 이유, 발생 시의 Stack Trace 등을 기록하고자 한다."

즉, Exception 처리라는 걱정 거리를 좀 더 효과적으로 처리할려고 한다. 언뜻 어려워 보이는 이 걱정거리가 AOP에서는 얼마나 쉽게 해결가능한지 살펴보게 될 것이다.

 

 

AspectJ에서 Concern 구현 하기 - part4

아래에 심플한(?) 비지니스 로직을 구현하는 객체가 있다. 우리의 걱정 거리는 비지니스 로직에서 Exception이 발생할 때마다 상세한 발생 상황을 기록하는 것이다.

우리의 비지니스 로직은 다음과 같다.

이 비지니스 로직에 대한 우리의 Concern을 처리해야 하는 상황은 다음과 같다.

  • ExceptionGenerator의 로직을 수행하는 과정에서 Exception이 발생하면 이것을 캡쳐해서 기록하고 싶다.
  • 이 때 어떤 메소드를 호출하다가 Exception이 발생했는지, Exception의 종류는 무엇인지 등의 정보가 종합적으로 기록하고 싶다.

이 상황을 AOP 없이 처리하려면 제 아무리 자바의 고수라고 하더라도 다음과 같은 방식으로 일일이 소스를 변경해야 한다.

try { doSomething1() } catch(Exception ex) {

logger.log("Error " + ex + " occurred when executing ExceptionGenerator.doSomething1()...");

}

비록 Java Logging API나 Log4j 같은 라이브러리들이 이러한 작업을 처리하는데 상당히 도움이 되지만, 핵심 로직안에 우리의 Concern을 처리하는 로직을 넣어야 한다는 기본적인 사실에는 전혀 변화가 없다.

하지만, AspectJ를 사용하면... ? 핵심 로직에는 Exception Handling에 관련된 소스를 전혀 추가할 필요없이 다음과 같은 형태의 Aspect만을 만들어주면 된다.

Exception Aspect Version 1

매우 심플한 Aspect지만 두 가지의 핵심적인 정보를 담고 있는 완전한 형태의 Aspect이다.

  • pointcut : 모든객체의 메소드콜을 횡단으로 매치시키는 call (* *.*(..)) 이라는 pointcut이 callpoint라는 이름으로 정의되어 있다.
  • after advice : callpoint pointcut에서 Exceptoin이 발생한 이후(after + throwing) 수행할 advice가 정의되어 있다.

AspectJ에서는 대부분의 Concern이 pointcut과 advice의 조합으로 이루어진다. 즉 어떤 지점(pointcut)에서 어떤 일(advice)를 수행할 지가 바로 AspectJ가 구현하는 Concern에 대한 해결책이 된다.

위의 ExceptionAspect와 ExceptionGenerator를 Weaving해서 수행하면 다음과 같은 결과가 나온다.

결과1


이제 ExceptionAspect를 좀 더 다듬어서 보다 완전한 형태의 정보를 얻을 수 있도록 해보자.

Exception Aspect Version 1


더욱 세련된 모양의 Aspect가 구현되었음을 확인할 수 있다. Version 1에 비해 다음과 같은 특징들이 추가되었다.

  • After advice에서 Exception발생시 Exception 객체를 받는다. 이렇게 받은 객체를 이용해서 필요한 정보를 추출한다.
  • thisJoinPointStaticPart (또는 thisJoinPoint.getStaticPart())를 이용해 어떤 지점에서 발생한 Exception인지를 알아낸다.

위의 Aspect를 보고 "아... 정말 내가 원하던 방법론이다"라고 감탄을 했다면 이미 일류 프로그래머이거나 일류 프로그래머가 될 잠재력을 가지고 있는 사람일 것이다.

AspectJ 혹은 AOP를 현재 프로젝트에 사용하고 싶은 욕구가 이는가...!!!

 

 

 

AspectJ의 Load Time Weaving - part5

ApsectJ 1.5는 AspectWerkz라는 신흥 AOP 컴포넌트를 흡수하면서 Load Time Weaving 기능을 크게 향상시켰다. Load Time Weaving이란 말 그대로 클래스가 로드되는 시점에 Weaving 작업을 수행하는 것을 의미한다.

전통적으로 AspectJ에서는 ajc(AspectJ Compiler)라는 컴파일러를 사용해서 사용자가 작성한 Class 파일이나 Jar 파일을 컴파일 시간에 Weaving하는 방식을 지원했다. 비록 이 방법이 아직까지도 가장 보편적이고 또 편리한 방법이긴 하지만, 컴파일시 Weaving은 역시 불편한 방법이다. 하지만!!! 성능 면에서는 가장 유리한 방법이라는 것은 다시 한번 염두에 두자

AspectJ에서 Load Time Weaving이 적용되는 방식은 아래 그림과 같다.

aop.xml

aop.xml 파일은 LTW의 룰을 지정하는 역할을 한다. 즉 어떤 Aspect를 어떻게 Weaving 할 것인지 지정할 수 있다.

아래에 aop.xml의 간단한 예제가 있다.



위의 aop.xml 파일은[aop.ltw.SimpleLTWAspect]라는 이름의 Aspect를 사용하며, 이 Aspect를 이용해서 Weaving을 수행할 것을 지정한다.

AspectJ의 LTW 모듈은 [클래스패스(Classpath)/META-INF]에 있는 모든 aop.xml 파일을 불러와서 Weaving 작업을 수행한다.

aop.xml이 제공하는 문법은 매우 다양하고 강력하다. 어떤 Aspect를 어떤 타겟(비지니스 로직)에 대해 어떤 조건(pointcut)으로 사용할지를 자유롭게 지정할 수 있다. 예를 들어 Abstract Aspect를 만든 후 aop.xml에서pointcut을 정의할 수도 있다. aop.xml을 사용하는 상세한 방법은 AspectJ Manual을 참조한다.

LTW의 간단한 예제

아래에 간단한 Aspect가 있다.


이 Aspect의 역할은 Method의 시작과 끝을 잡아서 수행 시간을 측정하는 것이다. 어플리케이션 성능 측정을 위한 가장 기본적인 기능을 구현할 것이라고 볼 수 있다.

이 Aspect를 다음과 같이 ajc를 이용해서 컴파일한다.

c:aspectj1.5binajc -1.5 -cp ../..;c:aspectj1.5libaspectjrt.jar SimpleLTWAspect.aj

컴파일에 성공하면 SimpleLTWApsect.class 파일이 생긴다. 이 Aspect 파일과 위에서 샘플로 사용한 aop.xml 파일을 이용해서 LTW을 수행하는 명령어는 다음과 같다.

(SimpleLTW 객체는 몇 개의 메소드를 반복적으로 호출하는 단순한 객체이다)

java -javaagent:aspectjweaver.jar -cp ../.. aop.ltw.SimpleLTW

아래 결과를 보면 우리가 원하는 대로 각 메소드를 실행하는데 걸린 시간이 계산되어 나오는 것을 알 수 있다.


위에서 본 간단한예제만으로도 AspectJ에서 제공하는 LTW의 유연함과 강력함을 느낄 수 있으리라 믿는다.

PS)

Java 5 (JDK 1.5)부터는 java.lang.instrument 패키지를 이용해서 Byte Code Instrumentation(BCI)을 직접적으로 지원한다. 더 이상 BCI가 어둠의 자식이 아니라는 것을 의미한다.오히려 BCI가 Sun에서도 인정하는 보편적인 방법론임을 의미한다.

자연스럽게, AspectJ 1.5의 LTW도 이 기능을 이용한다. 위의 예에서 "-javaagent:aspectjweaver.jar" JVM 옵션이 java.lang.instrument 패키지를 이용한다는 것을 의미한다. 즉, aspectjweaver.jar 내에 클래스 로드 타임시 실시간으로 클래스를 Weaving하는 기능을 제공하는 Class Transformer가 존재한다.

JDK 1.4에서는 VM 레벨에서 BCI가 지원되지 않는다.JDK 1.4라면 아래와 같은 형식으로 사용가능하다.

java -classpath[aspectjweaver.jar]

-Djava.system.class.loader=org.aspectj.weaver.loadtime.WeavingURLClassLoader

-Daj.class.path=. -Daj.aspect.path=.[YourClass]

또는 JRockit에서는 다음과 같은 JVM옵션을 사용할 수 있다.

-Xmanagement:class=org.aspectj.weaver.loadtime.JRockitAgent

 

 

AOP(Aspect Oriented Programming) in Java - Part 6

AOP와 AspectJ를 이용해서 할 수 있는 일은 실로 무궁구진하다.

비지니스 로직을 처리하는 과정에서 발생하는 관심거리나 걱정거리를 좀 더 효율적이고 체계적으로 처리하고자 한다면 AOP가 바로 대답이다.

AOP를 이용해서 구현 가능한 몇 가지 사례를 끝으로 AOP에 관한 블로깅을 끝맺기로 한다.

  • 로깅 : 각 조인포인트마다 적절한 로그를 남길 수 있다.
  • 프로파일링 : 각 조인포인트마다 수행되는 메소드 정보를 기록해서 프로파일링을 구현할 수 있다. 가령 자바 어플리케이션에서 수행되는 모든 메소드에 대해 수행 시간을 측정하고자 한다면 AOP의 before/after/around advice를 이용해서 손쉽게 구현할 수 있다.
  • 트랜잭션 관리 : 트랜잭션 관리에 관련된 모는 기능을 비지니스 로직에서 제거하고 AOP로 구현할 수 있다. EJB는 AOP의 개념을 사용하지 않고 EJB 컨테이너를 이용해서 트랜잭션을 관리하는 기능을 제공하는 것으로 이해할 수 있다. 반면 Spring과 같은 경량 프레임웍들을 EJB 컨테이너와 같은 무거운 방법대신 AOP를 사용해서 사용자가 작성한 비지니스 로직에 트랜잭션 관리에 필요한 Bytecode를 직접 삽입하는 방식을 사용한다.
  • 코딩컨벤션 관리:클 래스의 필드명을 항상 m_ 로 시작하게끔 규칙을 부여한다고 하자. 여러 명의 개발자들이 개발을 진행할 경우 이 규칙이 지켜지기는 거의 불가능하다. 하지만 AOP의 필드 조인트포인트를 이용하면 컴파일 시점에 이러한 오류들을 모두 걸러낼 수 있다. AOP를 이용하면 매우 복잡한 코딩 컨벤션 관리를 거의 자동화 할 수 있다.
  • 기타 등등... 오직 우리의 상상력에 의해서만 제약을 받는다!!!

국내 개발 프로젝트에서 AOP를 많이 사용하지 않는다고 해서 AOP가 무용하거나 현실과 동떨어진 것이라고 생각한다면 큰 오산이라는 것을 다시 한번 명심하자.

Spring 프레임웍이 내부적으로 트랜잭션 관리를 위해 AOP를 사용하고 있다. 따라서 여러분이 만일 Spring을 사용하고 있다면 이미 AOP를 사용하고 있는 것이다. 그 외에도 대부분의 Application Server가 AOP를 이용해 사용자가 작성한 클래스 파일에 특정 기능을 부여하는 기능을 곳곳에서 사용하고 있다.

앞서 다섯 편의 글을 통해 AOP의 강력함과 편리함을 조금이라도 느낄 수 있었기를 바라며, 국내의 개발 프로젝트에서 AOP를 사용하는 실제적인 사례를 목격하게 되기를 바래 본다.

posted by 나는너의힘
: