[펌]AOP

ITWeb/개발일반 2008. 6. 30. 19:02
ref. http://www.ibm.com/developerworks/kr/library/mar06/pollice/index.html



Aspect-Oriented Programming: AOP에 대한 실제적 접근방식 (한글)

developerWorks
문서 옵션
이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기


제안 및 의견
피드백

난이도 : 초급

Gary Pollice, Professor of Practice, Worcester Polytechnic Institute

2006 년 4 월 24 일

Aspect-Oriented Programming에 대한 대부분의 소개서들은 신기술 측면만을 다루고 있습니다. 바로 이러한 점 때문에 AOP의 실제적인 가치를 전혀 배울 수 없었습니다 . 이 글에서 AOP 기술을 소프트웨어 개발 프로젝트에 적용하는 방법을 설명합니다.

illustration최 근에 나는 Software Engineering Research Group (SERG)에서 Aspect 지향 프로그래밍(AOP)에 대한 토론을 진행해 줄 것을 부탁 받았다. 토론회가 시작되기 몇 시간 전, 한 학생이 나에게 물었다. "Aspect가 도대체 어디에 좋은가요? 그런데 로깅 예제는 안주셔도 됩니다. 내가 Aspect에 대한 것을 읽을 때 마다 보는 것이니까요. "

그 학생의 질문으로 나는 소프트웨어 시스템에 AOP를 적용하는 효과적인 방법을 생각하게 되었다. 또한 새로운 방식을 채택할 방법과 시기를 고려해야 할 필요성도 느꼈다. AOP는 새로운 방식이다. AOP가 효과적으로 사용될 수 있는 방법에 대해 이야기 하고자 한다. 또한 최근 AOP가 어떻게 진화했는지도 볼 것이다.

나는 Aspect 지향 자바를 예제로 사용할 예정이다. 하지만 자바 외에도 다른 여러 언어들의 Aspect 구현이 있다. AspectC++과 AspectL이 그것인데 이것은 Aspect 지향 Lisp 구현이다.1

AOP 개념 리뷰

AOP가 생소하다면 관련 개요서를 참조하기 바란다. 내가 쓴 2004년 2월의 기술자료를 참조해도 좋다.2 AOP 소개서의 많은 부분이 Aspect의 개념을 설명할 때 로깅을 예제로서 사용하고 있다. (로깅은 많은 사람들이 이해하고 있는 것이고 AOP가 사용되는 방법을 보여주는 좋은 예제이다.) Aspect는 크로스커팅을 하는 영역이다. 다시 말해서, 하나의 클래스에 쉽게 캡슐화 되지 않는다. 하지만 객체 지향 패러다임을 엄격히 따라간다면 그와 같은 영역을 일관되고 관리가 가능한 방식으로 나타내야 한다. 종종 크로스커팅 책임을 개별 helper 클래스로 위임해야 하고 영역에 의해 표현되는 기능을 필요로 하는 모든 클래스에 의존하여 호출들이 적절한 장소에서 그 위임에 대한 호출을 포함시켜야 한다. 코드의 적절한 포인트에 로그인을 지속적으로 삽입하는 것을 확인하기란 어려운 일이다. Aspect는 불완전 하지만 이러한 상황을 향상시킬 수 있는 방식을 제공한다.

AOP의 논의에 참여하기 위해서는 몇 가지 개념을 알아야 한다. 핵심 개념은 조인 포인트(join point)이다. 이 개념은 프로그래머에게는 익숙한 개념이지만 다시 한번 짚고 넘어가자. 조인 포인트는 "프로그램 플로우에서 정의가 잘된 포인트"이다. 3 메소드 호출 또는 메소드 리턴 같은 많은 유형의 조인 포인트가 있다. 정상 리턴이나 예외가 될 수 있다.

AOP의 경우 한 프로그램에 조인 포인트를 규명할 방식이 필요하다. AspectJ는 포인트컷(pointcut)을 사용하여 한 개 이상의 조인 포인트를 설명한다. 포인트컷은 조인 포인트를 기술하는 식(expression)이다. 포인트컷을 조인 포인트 세트를 리턴하는 코드의 쿼리로 생각할 수 있다.

조인 포인트 세트를 선택할 때 여기에 대한 어드바이스(advice)를 제공한다. 어드바이스는 프로그램이 실행하는 동안 조인 포인트를 만나게 될 때 실행되는 코드이다. 조인 포인트, 포인트컷, 어드바이스는 소프트웨어의 동적 속성을 나타낸다. 어드바이스는 프로그램 코드의 실행 특성을 변화시킨다.

시스템의 정적인 본질을 다루는 한 가지 개념이 있다. 바로 inter-type declaration이다. inter-type declaration은 프로그램의 정적 구조를 변화시킬 수 있다. 메소드와 변수를 추가하고, 지정된 규칙에 따라 상속 계층을 변경할 수 있다.

자바에서 클래스가 모듈의 단위이듯, Aspect는 AspectJ의 추가 모듈 단위이다. Aspect는 크로스커팅 영역을 위해 조인 포인트, 포인트컷, inter-type declaration, 어드바이스를 캡슐화 한다. AOP는 객체 지향 분석과 디자인의 대체 개념이 아니다. OO 방식이 우리가 원하는 솔루션을 제공하지 못하는 상황 속에서 새롭게 생겨난 것이다.

AOP 예제

이제 AOP 예제를 살펴보도록 하자. 제품 시스템과 제품 및 개발 상황 속에서 예제를 가져왔다. 두 개의 예제부터 시작하다.

실행 트레이싱

많은 개발자들이 프로그램의 실행을 디버깅 하거나 트레이싱 하기 위해 코드에 일종의 print 문을 포함시킨다는 것에 놀랐다. 물론 디버거는 정보를 잘 제공한다. 하지만 지금 우리는 디버거를 논할 것은 아니다. 프로그램의 텍스트 트레이스가 만들어지기를 기다리는 이유가 있다. 현재 Eclipse의 AspectJ Development Tools (AJDT)는 프로그램 실행 트레이스를 구현하는 좋은 예제이다. Eclipse 도움말에서 자세한 내용을 참조하라. AspectJ Programming Guide에도 자세한 내용이 있다.

이 예제는 원과 사각형 같은 이차원 그림을 표현하는 클래스를 가진 작은 자바 애플리케이션이다. 두 개의 원과 사각형을 만들고, 영역, 길이, 중앙 포인트 간 거리를 프린트하는 메인 메소드도 있다. 이 프로그램을 실행하면 그림 1과 같은 아웃풋이 나온다.

Figure 1: Input from the shape program

그림 1: shape 프로그램의 인풋

호출된 메소드의 실제 시퀀스를 보고 싶다면 두 가지 방법이 있다. 첫 번째 방법은 메소드의 이름과 클래스를 가진 메시지를 프린트하는 각 메소드의 시작에 코드를 삽입하는 것이다. 이를 각 메소드 마다 수행해야 한다. 두 번째 방법은 정확히 같은 일을 수행할 Aspect를 만드는 것이다. 이때에는 애플리케이션 코드를 변경할 필요가 없다.

트레이싱 예제는 Aspect를 사용하는 여러 버전의 트레이싱 솔루션들로 구성된다. 우리는 마지막 버전만 보도록 하겠다. 기타 다른 버전들은 AspectJ Programming Guide를 참조하기 바란다.

강력한 트레이싱 메커니즘에 대한 솔루션은 두 개의 파일로 구성된다. 하나는 추상 Aspect이다. 프로그래머가 파생된 클래스에서 일부 코드를 구현해야 하는 곳의 추상 클래스와 비슷하다. 이 경우는 파생된 Aspect이다. Trace라고 하는 추상 Aspect는 메소드 또는 구조체의 시작과 종료에 대한 메시지를 프린팅하고 아웃풋을 포맷팅 할 여러 가지 표준 메소드들을 갖고 있다. Aspect를 사용하고 있지 않다면 이 메소드들은 트레이스 정보를 출력할 때 사용할 헬퍼 클래스에 있을 것이다. 또한 프로그래머가 TRACELEVEL이라는 속성을 설정하여 트레이싱 유형을 설정할 수도 있다. 세 가지 레벨이 있다. 메시지 없음(no messages), 들여쓰기가 되지 않은 메시지(messages that are not indented), 중첩된 호출을 보여주도록 들여쓰기 된 메시지(messages that are indented to show nested calls).

Trace는 세 개의 포인트 컷을 정의한다. 이중 두 개는 구체적인 것이고 하나는 추상적이다. (그림 2) 추상 포인트컷인 myClass는 Trace를 확장하는 Aspect에서 제공된다. 포인트컷의 목표는 어드바이싱 될 조인 포인트를 포함하고 있는 객체용 클래스를 선택하는 것이다. 따라서 개발자는 트레이스 아웃풋에 어떤 클래스를 삽입할 지를 결정할 수 있다.

Figure 2: Pointcuts in the trace aspect

그림 2: 트레이스 Aspect의 포인트컷

myConstructor 포인트컷은 myClass가 선택한 클래스에 있는 객체용 구조체의 시작 부분에서 조인 포인트를 선택한다. 조인 포인트는 실제 구조체 바디이다. myMethod는 myConstructor와 비슷하지만, 이것은 선택된 클래스에서 메소드의 실행을 선택한다. 어드바이스에서 사용되는 toString 메소드의 실행은 생략했다는 것에 주목하라.

이 Aspect에서 제공하는 어드바이스는 매우 단순하다. 각 조인 포인트 전에 삽입되는 어드바이스와 조인 포인트 후에 실행되는 어드바이스가 있다. (그림 3)

Figure 3: Advice in the Trace aspect

그림 3: Trace aspect의 어드바이스

Trace Aspect를 사용하려면 이것을 확장하여 추상 포인트컷에 구체적 구현을 제공해야 한다. 그림 4는 예제 프로그램의 TraceMyClasses Aspect의 바디 모습이다. 포인트컷은 TwoDShape, Circle, Square의 인스턴스인 객체들만 선택할 것을 명령한다. 메인 메소드는 TRCELEVEL을 설정하고 트레이스 스트림을 초기화 하며 예제의 메인 메소드를 실행한다.

Figure 4: Concrete tracing aspect

그림 4: 구체적인 트레이싱 객체

그림 5는 아웃풋의 일부이다. 이 아웃풋은 각 객체에 대한 정보를 출력하고 있다. 이것은 각 메소드의 toString 메소드의 일부이다. myClasses 포인트컷이 객체를 어드바이스에 퍼블리시 하기 때문에 어드바이스는 객체에 대한 정보를 쉽게 추가할 수 있다.

Figure 5: Example trace output

그림 5: 트레이스 아웃풋

트레이스 코드를 직접 삽입하는 것 보다 AOP를 사용하는 것이 더 좋은 이유는? 여러 가지가 있다.

  • 한 장소(두 개의 Aspect)에 트레이싱 영역에 속해있는 모든 소스 코드를 둘 수 있다.
  • 트레이싱 코드를 삽입 및 제거하기가 쉽다. 구현 설정에서 Aspect를 제거하면 된다.
  • 트레이스 코드는 원하는 곳 어디에나 있다. 심지어 새로운 메소드를 목표 클래스에 추가하더라도 말이다. 따라서 오류를 줄일 수 있다. 또한 모든 트레이스 코드가 제거된다는 것을 알 수 있고, 구현 설정에서 Aspect를 제거할 때 어떤 것도 간과하지 않게 된다.
  • 적용 및 강화될 수 있는 재사용 가능한 Aspect를 가진다.

Design by Contract 또는 방어적 프로그래밍

Bertrand Meyer는 Design by Contract개념을 도입했다.4 이 원리는 클래스의 디자이너와 클래스의 사용자가 클래스 구현에 대한 생각을 공유한다는 것이다. 이 콘트랙트에는 불변 값, 전제 조건, 사후조건이 포함되어 있다. Design by Contract로 인해 클래스 디자이너는 인자의 유효성을 신경 쓰지 않고 클래스 기능을 구현하는 로직에만 집중할 수 있다. 물론 이것은 콘트랙트가 인자에 대한 사전 조건을 명시할 경우이다. Design by Contract는 클래스의 모든 클라이언트가 콘트랙트를 지킨다면 한 과잉 코드를 방지하고 퍼포먼스를 높인다.

광범위하게 사용할 라이브러리를 구현 할 때 메소드로 전달되는 인자의 유효성에 대해 잘 모를 경우가 있다. 각 메소드의 로직으로 처리하기 전에 인자를 검사해야 한다. 이것이 디펜스 프로그래밍(defensive programming)이다. 잘못될 가능성이 있는 것을 예견하고 이를 효과적으로 처리하는 것이다.

간단한 형상 프로그램을 공개적으로 사용한다고 해보자. 첫 번째 Euclidean 쿼드란트에 모든 좌표가 있어야 한다. 다시 말해서 x와 y 좌표는 비음수 값이다. 이는 포인트가 윈도우의 좌표에 나타난다면 유효한 제약조건이다. 대부분의 윈도우 시스템들은 좌측 상단 점을 (0,0)으로 시작하고 x 좌표를 오른쪽으로 늘리고 y 좌표를 아래쪽으로 옮긴다. 내부적인 필요에 의해서 Design by Contract를 사용해야 한다. 이 클래스를 사용할 개발자들에 대한 제어권이 있기 때문이다. 이것을 외부 클라이언트로 퍼블리시 할 때 인자를 검사하고 인자가 무효할 경우 예외를 던져야 한다. Aspect는 필요한 것을 구현할 수 있는 좋은 방법을 제공한다.

퍼블릭 메소드에 모든 인자를 검사할 Aspect를 구현해야 한다. 첫 번째로 해야 할 일은 포인트컷을 구현하는 일이다. 이전 예제에서 myClass 포인트컷을 사용하고, 인자 체킹을 필요로 하는 구조체를 선택하기 위해 포인트컷을 추가한다. 그림 6은 우리에게 필요한 포인트컷 세트이다. 두 번째 포인트컷은 포인트컷의 목표가 TwoDShape의 인스턴스라는 것을 지정한다. 이 같은 객체에 있는 거리 메소드로의 유일한 호출은 이 포인트컷에 의해 선택된다는 것을 의미한다.

Figure 6: Pointcuts for argument checking

그림 6: 인자 검사를 위한 포인트컷

마지막으로 적당한 어드바이스를 추가한다. 간단히 하기 위해 무효 인자를 만났을 때 메시지를 프린트 하고 실제 값을 0으로 바꾸고 무효 값이 전달될 때 거리에 대한 호출을 무시한다. 그림 7은 두 개의 어드바이스 아이템이다.

Figure 7: Argument checking advice

그림 7: 인자 검사 어드바이스

다음을 실행하면,

Circle c3 = new Circle(3.0,2.0,-2.0);

c1.distance(null>);

우리 프로그램에서는 다음과 같은 아웃풋을 얻게 된다.

Negative argument encountered at: execution(tracing.Circle(double, double, 
	double)) All arguments changed to 0.0
Null value given to distance at: 
	call(double tracing.Circle.distance(TwoDShape))

정확한 라인 번호와 소스 파일 이름을 보여줄 수 있지만 이 예제에서는 기본적인 기술만 보도록 한다.

많은 클래스가 있고 여러 인터페이스를 노출하는 큰 프로젝트에서 인자 검사를 구현하는 Aspect를 위해 개별 디렉토리를 구성해야 한다. 쉽게 구분할 수 있고 관리할 수 있도록 Aspect를 구성하는 여러 가지 방법들이 떠오른다. 내부적으로 사용할 시스템을 구현할 때 내부 구현 설정을 사용하고, 외부적으로 사용할 것을 구현할 때에는 Aspect가 포함된 설정을 사용한다. EclipseAJDT를 사용하면 새로운 구현 설정이 간단해 진다.

Aspect와 디자인 패턴

AOP는 기존 패턴을 향상시키고 새로운 것을 발견할 수 있도록 해준다. 사실, 크로스커팅 영역으로의 코드 투입도 하나의 패턴이다. 현재, 일부 연구원들은 AOP 방식을 사용한 디자인 패턴의 구현을 평가하고 있다. University of British Columbia의 Jan Hannemann은 그의 박사 논문에서 이것을 연구한다. http://www.cs.ubc.ca/~jan/AODPs/를 참조하라.5 Nicholas Lesiecki 역시 IBM developerWorks에 Aspect와 디자인 패턴에 대한 글을 기고하고 있다. 6

AspectJ에서 표준 디자인 패턴인 Adapter 패턴을 구현하는 방법을 알아보자.

그림 8은 Adapter 패턴에 대한 Unified Modeling Language (UML) 다이어그램이다. 이 패턴을 보면, 클라이언트는 서비스를 필요로 하고 서비스 요청을 한다. 많은 서비스 공급자들이 있고 이들 각각은 다양한 서비스 이름이나 서비스 요청자가 지켜야 할 비 표준 요구 사항들을 갖고 있다. 객체 지향 디자인에서는 목표 인터페이스에서 서비스에 대한 요청을 캡슐화하고, 인터페이스에 대해 프로그래밍 하며, 클라이언트와 서비스 간 중재자로서 작동할 어댑터(이 다이어그램의 경우 Adaptee)를 구현하도록 하고 있다.

Figure 8: Adapter pattern

그림 8: Adapter 패턴

이 방식은 매우 합리적이다. 하지만 어댑터 같은 패턴으로 설계되지 않았던 레거시 시스템이 있다면? 또한 서비스로의 호출이 애플리케이션 전체로 퍼져나갈 수 있다. 새롭고 향상된 서비스를 어떻게 구현할까? Aspect가 없다면 시스템을 리팩토링 하고, 모든 호출을 서비스에 배치시키고, 어댑터를 설계하고, Adapter 패턴을 구현해야 할 것이다. 이는 매우 힘든 일이다. 코드 향상을 위한 리팩토링은 가치 있는 목표이다. 하지만 언제나 그럴 수는 없다.

이 경우 Adapter 패턴의 AOP 버전을 구현하여 문제를 해결하도록 한다.

그림 9에서 서비스를 사용하는 클라이언트를 보자. 이 서비스는 한 지점에서 현재의 Client 객체로 거리를 리턴할 것이다.

Figure 9: Simple client

그림 9: 클라이언트

이 서비스용 인터페이스는 하나의 메소드를 기술하고 있다.


double useService(Client clt, double x, double y);

새로운 서비스 공급자는 다른 이름이 붙여진 메소드를 갖고 있다. 매개변수도 다르다. 이것의 인터페이스는 아래와 같다.


double useNewService(double x0, double x1, double y0, double y1);

오래된 서비스를 호출하고, 새로운 서비스에 대한 알맞은 인자를 얻고, 새로운 서비스를 호출하고, 그 결과를 클라이언트에 리턴하는 어댑터를 구현하고 싶다. 클라이언트를 변경하지 않고 말이다. 우리의 Aspect가 바로 이러한 일을 한다. (그림 10)

Figure 10: Adapter aspect to invoke the new service

그림 10: 새로운 서비스를 호출하는 Adapter aspect

너무 간단하다. 포인트컷을 선언하여 원래 서비스의 useService 메소드에 대한 모든 호출을 구분하고 있다. 그런 다음 around 어드바이스를 사용하여 새로운 서비스에 대한 호출로 대체한다.

이 방식에도 장단점은 있다. 우선 하나의 단순한 Aspect를 사용하여 애플리케이션에 있는 모든 코드를 변경할 수 있다. 또한 서비스를 호출하는 영역을 한 장소에 캡슐화 할 수 있다. 새로운 서비스가 더 새로운 서비스로 대체된다면 Aspect의 코드만 변경하면 된다. 시스템이 더욱 강해질 것은 자명하다.

사용되지 않는 오래된 클라이언트가 여전히 새로운 시스템에 있다는 것이 큰 단점이다. 시간이 충분하다면 표준 Adapter 패턴을 사용하도록 시스템을 리팩토링 할 수도 있다. 하지만 현재로서는 원래 서비스의 useService 메소드를 0.0 같은 더미 값을 리턴하는 코드를 사용하여 변경할 수 있다.

산업 강화 사용

지금까지, 매우 제한되었지만 유용한 AOP 적용 예제를 보았다. 기술력을 향상시킬 수 있는 방법이 궁금할 것이다. 여기에서는 간단한 예제 한 가지를 설명하겠다.

Spring Framework을 다들 알 것이다.7 Spring Framework은 엔터프라이즈 애플리케이션의 개발을 지원하는 애플리케이션 프레임웍이다. 복잡한 엔터프라이즈 애플리케이션을 구현할 수 있는 레이어드 J2EE 프레임웍을 제공한다. Spring의 기반이 되는 기술 중 하나가 AOP이다. Spring은 런타임 시 크로스커팅 로직이 코드에 적용될 수 있도록 하는 자체적인 Aspect 구현을 개발했다. AspectJ를 Spring Framework으로 쉽게 통합할 수 있는 통합 기능도 제공한다.

Spring은 현재 많은 기업들이 사용하고 있고 더 낳은 애플리케이션을 구현하는데 사용되고 있다. 개발 시간이 적게 들고 설계도 잘 되어 있다. 실제 사례들에 Aspect를 적용할 수 있는 좋은 예제라 할 수 있다.8

전망

앞으로 AOP는 소프트웨어 개발자 툴킷의 일부가 될 것이다. AOP가 주요 디자인 메커니즘으로 간주되는 시스템을 구현하는 것 까지는 아니더라도 OO 디자인을 향상시킬 수 있다고 생각한다. 이를 위해서는 몇 가지 조건이 필요하다.

첫째, 안정적인 표준의 AOP 구현이 필요하다. 이 작업은 이미 시작되었다. EJB Version 5는 두 개의 대표적인 자바 AOP 언어인 AspectJ와 AspectWerkz를 합친 것이다. C++ 같은 기타 언어들의 표준 구현 역시 도움이 될 것이다.

둘째, AOP를 특정 시스템에 적용했을 때의 효용성을 입증할 메트릭스를 개발해야 한다. AOP를 사용하여 디자인 패턴을 재구현 한다면 과연 이것이 OO 패턴 보다 더 나을까? 더 나은 점은 무엇이고, 그렇지 않은 부분은 무엇인가? 이러한 메트릭스를 개발하기 위해 우리가 해야 할 일은 무엇일까? 우리는 경험적 증거에 입각하여 구현 및 디자인 결정을 내려야 한다. 9

세 번째, AOP를 지원하는 툴을 지속적으로 개발해야 한다. Eclipse용 AJDT는 훌륭한 툴이라고 감히 말하고 싶다. 이 툴은 우리가 필요로 하는 지원 부분을 계속 향상시키고 있다.

Aspect는 여기서 멈추지 않는다. 주류 애플리케이션의 일부가 되려면 멀었지만 더 가까이 다가선 것 만은 틀림없다.

1 http://www.aspectc.org/, http://common-lisp.net/project/closer/aspectl.html

2 The Rational Edge: http://www.ibm.com/developerworks/rational/library/2782.html

3 AspectJ Programming Guide: http://www.eclipse.org/aspectj/doc/released/progguide/index.html

4 Bertrand Meyer, Object-Oriented Software Construction, 2d ed. Prentice Hall 1998.

5 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides의 Design Patterns

6 http://www-128.ibm.com/developerworks/java/library/j-aopwork5/

7 http://www.springframework.org/

8 Pro Spring, Rob Harrop and Jan Machacek, Apress 2005.

9 AOP 메트릭스 개발

기사의 원문보기

: