태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'Aspect Oriented Programming'에 해당되는 글 2건

  1. 2009/04/24 개발 프레임워크 만들기 대장정 25 - AOP 적용 예제 I by dalbong2
  2. 2009/04/24 개발 프레임워크 만들기 대장정 21 - Aspect Oriented Programming 개념 I by dalbong2

■ 예제 설명

앞에서 본 샘플 프로젝트 솔루션의 구조이다.

Spring.Calculator.Web 프로젝트를 실행시켜보면 다음과 같은 결과 페이지가 보인다.

첫번째 링크는 단순한 웹 서비스 메소드를 호출하고 있다. AOP가 적용된 메소드를 호출하기 위해서는 두번째 링크를 클릭해야 한다. 이번 포스트에서는 두번째 링크에 대한 웹 서비스를 AOP 예제로 삼겠다.  두번째 링크를 클릭하면 다음과 같은 웹 서비스 테스트 화면이 나온다.

노출된 메소드중에서 Add 메소드를 클릭해서 적절히 값을 넣고 호출한다.

이 메소드를 호출하고 나서 남는 로그는 다음과 같다. 

2008-08-18 23:09:34,406 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Add'

2008-08-18 23:09:34,421 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned '2'

그러나 웹 서비스 메소드에는 로그를 남기는 코드는 없다. 별도의 advice를 사용해서 로그를 남기고 있다. 이 샘플에서는 AOP를 구현하기 위해서 개발자가 해야 할 일을 앞에서 설명한 대로 차례로 진행해보자. 


■ 타겟 객체 정의


먼저 타겟 객체에 대한 소스를 보자. 먼저 두번째 링크가 호출하는 클래스는 Spring.Calculator.Services.2005 프로젝트의 AdvancedCalculator 를 보면 다음과 같다.

public class AdvancedCalculator : Calculator, IAdvancedCalculator

{

    #region Fields

    private int memoryStore = 0;

    #endregion


    #region Constructor(s) / Destructor

    public AdvancedCalculator()

    {}


    public AdvancedCalculator(int initialMemory)

    {

        memoryStore = initialMemory;

    }

    #endregion


    #region IAdvancedCalculator Members

    public int GetMemory()

    {

        return memoryStore;

    }

    public void SetMemory(int memoryValue)

    {

        memoryStore = memoryValue;   

    }

    public void MemoryClear()

    {

        memoryStore = 0;

    }

    public void MemoryAdd(int num)

    {

        memoryStore += num;

    }


    #endregion

}

이 클래스에는 Add 메소드가 없다. 상속을 하고 있는 부모 클래스 Calculator에서 구현하고 있다. 그리고 앞 포스트에서 말한대로 타겟 객체가 되기 위해서는 현재 버전의 Spring.NET( v 1.1.2)에서는 반드시 인터페이스를 구현해야 한다고 했다. 코드를 보면 IAdvcancedCalculator를 상속해서 구현하고 있다.

public interface IAdvancedCalculator : ICalculator

{

    int GetMemory();

    void SetMemory(int memoryValue);

    void MemoryClear();

    void MemoryAdd(int num);

}

IAdvancedCalculator 인터페이스는 ICalculator를 상속해서 인터페이스 정의를 물려받고 있다. ICalculator 인터페이스와 그것을 구현하고 있는 Calculator 클래스 코드는 다음과 같다.

public interface ICalculator

{

    int Add(int n1, int n2);

    int Substract(int n1, int n2);

    DivisionResult Divide(int n1, int n2);

    int Multiply(int n1, int n2);

}

public class Calculator : ICalculator

{

    #region ICalculator Members


    public int Add(int n1, int n2)

    {

        return n1 + n2;

    }


    public int Substract(int n1, int n2)

    {

        return n1 - n2;

    }


    public DivisionResult Divide(int n1, int n2)

    {

        DivisionResult result = new DivisionResult();

        result.Quotient = n1 / n2;

        result.Rest = n1 % n2;

        return result;

    }


    public int Multiply(int n1, int n2)

    {

        return n1 * n2;

    }


    #endregion

}

인터페이스들은 구현 클래스들과는 다른 프로젝트 Spring.Calculator.Contract.2005에 구현되어 있다. 만약 클라이언트 애플리케이션과 서버 애플리케이션이 분리되어 있다면 클라이언트에서는 인터페이스 어셈블리만 참조하면 된다. 물론 서버측에서는 인터페이스 어셈블리와 구현 어셈블리가 같이 참조되어야 한다.


■ advice 코딩하기


이제 타겟 객체를 호출할때 weaving될 advice 코드를 살펴본다. 샘플에서는 프로젝트 Spring.Aspects.2005에 구현되어 있다.

현재는 두개의 로깅 advice가 구현되어 있다. 이 중에서 웹 애플리케이션에서는 CommonLoggingAroundAdvice를 사용해서 로그를 남기고 있다. 이 advice 코드를 보면 다음과 같다.

public class CommonLoggingAroundAdvice : IMethodInterceptor

{

    #region Logging

    private static readonly ILog LOG = LogManager.GetLogger(typeof(CommonLoggingAroundAdvice));

    #endregion


    #region Fields

    private LogLevel _level = LogLevel.All;

    #endregion


    #region Properties

    public LogLevel Level

    {

        get { return _level; }   

        set { _level = value; }

    }

    #endregion


    #region IMethodInterceptor Members


    public object Invoke(IMethodInvocation invocation)

    {

        Log("Intercepted call : about to invoke method '{0}'", invocation.Method.Name);

        object returnValue = invocation.Proceed();

        Log("Intercepted call : returned '{0}'", returnValue);

        return returnValue;

    }


    #endregion


    #region Private Methods

    private void Log(string text, params object[] args)

    {

        switch(Level)

        {

            case LogLevel.All :

            case LogLevel.Debug :

                if (LOG.IsDebugEnabled) LOG.Debug(String.Format(text, args));

                break;

            case LogLevel.Error :

                if (LOG.IsErrorEnabled) LOG.Error(String.Format(text, args));

                break;

            case LogLevel.Fatal :

                if (LOG.IsFatalEnabled) LOG.Fatal(String.Format(text, args));

                break;

            case LogLevel.Info :

                if (LOG.IsInfoEnabled) LOG.Info(String.Format(text, args));

                break;

            case LogLevel.Warn :

                if (LOG.IsWarnEnabled) LOG.Warn(String.Format(text, args));

                break;

            case LogLevel.Off:

            default :

                break;

        }

    }

    #endregion

}

이 advice는 타겟 객체의 메소드를 호출할때 적용된다. 타겟 객체의 어디서, 어떻게 적용될지를 선택할 수 있는 방법이 바로 IMethodInterceptor 인터페이스이다. 이 인터페이스에서는 단지 object Invoke()만을 정의하고 있다.

IMethodInterceptor를 구현하고 있는 advice가 타겟 객체에 적용될때는 타겟 객체의 메소드를 호출하면 항상 IMethodInterceptor인터페이스의 Invoke()가 호출된다. 코드에서 Invocation.Proceed(); 부분이 advice가 캡쳐한 원래의 호출을 다시 타겟 객체로 전달하는 부분이다. 타겟 객체로 호출을 전달하기 전에 예제의 advice에서는 로그를 남기는 작업을 하고 있다. 로그를 남기는 Log()에 대해서는 지금 이곳에서는 중요한 부분이 아니므로 넘어가도록 한다. Proceed()를 호출하고 나서 반환값을 받고서도 클라이언트로 바로 넘기지 않는다. 반환되기 전의 순간도 캡쳐할 수 있다. 예제 advice에서는 타겟 객체에서 반환하는 값에 접근해서 그 값을 로그로 남기고 있다. 그런 다음 최종적으로 클라이언트 코드로 반환값을 넘겨주고 있다. 만약 타겟 객체가 개발자가 개발g한 비즈니스 객체이고 개발 프레임워크에서 이와 같은 advice를 개발해서 적용한다면 얼마나 유용할지 짐작이 갈 것이다.

이렇게 타겟 메소드의 호출 전 후를 캡쳐할 수 있는 기회를 제공하는 advice를 "around advice"라고 한다. IMethodInterceptor는 around advcie의 Spring 프레임워크의 구현이다. 그외에도 before advice, after advice, throws advice등이 있고 이것을 각각 구현한 Spring.NET의 인터페이스들이 있다. 이것에 대해서는 뒤에서 다루기로 하고 지금은 Spring.NET 애플리케이션에서 AOP를 적용하는 전체적인 절차를 계속 알아보도록 하자.

지금까지의 내용을 보면, 인터페이스가 있었고 그리고 인터페이스를 구현한 타겟 객체가 있었다. 그리고 타겟 객체의 메소드를 호출할때 적용될 advice가 있었다. 이제 실제로 advice를 타겟 객체에 적용하기 위한 설정이 필요하다.


■  advice 적용 설정하기( ProxyFactoryObject 설정하기 )


advice를 타겟 객체에 적용하기 위한 설정은 다시 말하면 ProxyFactoryObject 객체 설정과 같은 말이다. 앞의 포스트에서 Spring.NET에서는 프락시 패턴을 이용해서 AOP를 구현하고 있다고 했다. 그리고 advice가 적용된(weaving된) 프락시를 AOP 프락시로 표현했는데, 이 AOP 프락시를 런타임시에 동적으로 생성해내는 객체가 바로 ProxyFactoryObject라고 했다. 클라이언트 코드에서는 타겟 객체에 대한 참조와 advice를 건네주고 ProxyFactoryObject객체로부터 타겟 객체에 대한 AOP 프락시를 받는다고 했다. 이 시나리오를 설정을 통해서 구현하면 다음과 같다. 이 시나리오를 코드상에서 프로그램적으로 구현할 수 있는 API도 제공하고 있다. 이 설정은 웹 프로젝트 Spring.Calculator.Web.2005 의 web.config의 부분이다.

<!-- Aspect -->


<object id="CommonLoggingAroundAdvice" type="Spring.Aspects.Logging.CommonLoggingAroundAdvice, Spring.Aspects">

  <property name="Level" value="Debug"/>

</object>


<!-- Service -->


<object id="calculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"/>

<object id="calculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">

  <property name="target" ref="calculator"/>

  <property name="interceptorNames">

    <list>

      <value>CommonLoggingAroundAdvice</value>

    </list>

  </property>

</object>

첫번째 <object/>요소에 타겟 객체에 적용될 advice가 정의되어 있다. id는 CommonLoggingAroundAdvice로 하고 있는데, 다른 <object/>에서 이 객체를 참조할때 id 값을 이용할 수 있다. advice는 Spring.Aspects.Logging 네임스페이스에 포함된 CommonLoggingAroundAdvice 클래스에 정의되어 있다는 것을 type 어트리뷰트값을 통해서 나타내고 있다.

두번째 <object/> 요소에서는 타겟 객체에 대한 정의를 표현하고 있다. id는 calculator로 하고 있고 타겟 객체의 타입은 어셈블리 Spring.Calculator.Services의 Spring.Calculator.Services 네임스페이스 아래에 있는 AdvancedCalculator 클래스에서 정의하고 있다.

세번째 <object/>요소는 바로 앞의 advice객체와 타겟 객체를 인자로 받아들이는 AOP 인터페이스 제너레이터 ProxyFactoryObject에 대한 정의이다. ProxyFactoryObject 타입의 속성중에는 Target, InterceptorNames( 대소문자 무관)가 있는데 Target 속성을 통해서 타겟 객체에 대한 참조를 받고, InterceptorNames 속성을 통해서 around advice를 받고 있다. 타겟 객체에 대한 참조를 지정할때 <property/>요소의 ref 어트리뷰트를 사용하는데 그 값으로는 앞에서 AdvancedCalculator 객체를 정의하고 있는 <object/>의 id 어트리뷰트를 지정하고 있다. 그리고 InterceptorNames 속성은 여러개의 advice를 지정할 수 있다. 그래서 <list/>요소 내부에 <value/> 요소를 사용해서 advice를 추가하고 있는데, 다른 advice 객체가 있다면 <value/>를 더 추가할 수 있다. 여기서 <value/>의 값으로 지정된 CommonLoggingAroundAdvice는 advice를 정의하고 있는 첫번째 <object/>요소의 id값이다.

이로써 특정 advice를 특정 타겟 객체에 적용하는 작업은 끝났다.  클라이언트 코드에서는 이제 다음과 같은 방법으로 AOP 프락시에 대한 참조를 얻을 수 있다.

IApplicationContext ctx = ContextRegistry.GetContext();

IAdvancedCalculator firstCalc = (IAdvancedCalculator) ctx.GetObject("calculatorWeaved");

컨텍스트 객체의 GetObject() 메소드에 ProxyFactoryObject 객체에 대한 id값을 넘겨주면 원한는 타겟 객체에 대한 AOP 프락시를 받을 수 있다. 반환되는 객체가 ProxyFactoryObject 객체 자체가 아니라 그 타겟 객체에 대한 프락시임을 다시 한번 더 상기하자. 이제 AOP 프락시를 통해서 클라이언트측 코딩을 해 나가면 된다.

현재 웹 샘플 Spring.Calculator.Web.2005 에서는 클라이언트 코드에서 타겟 객체에 대한 참조를 이용하는 코드가 없다. 현재의 웹 샘플 코드에서는 서버측에서만 AOP를 적용하는 코드가 있다. 타겟 객체의 메소드를 호출하는 클라이언트 코드는 앞의 그림과 같은 ASP.NET에서 제공하는 테스트 페이지를 사용하고 있다. 앞에서와 유사한 코드는 프로젝트 Spring.Calculator.ClientApp.2005의 Program.cs 파일에 있다.

Spring.Calculator.Web.2005 프로젝트에 있는 web.config의 설정은 웹 서비스로 노출된 타겟 객체에 대한 설정이다. 따라서 클라이언트측 로그는 없지만 서버측 객체 호출에 대한 로그는 남는다.


■  웹 애플리케이션 실행하기


Logs폴더 하위의 log.txt 파일을 열어보면 웹 서비스 메소드가 호출될때 남겨진 로그를 볼 수 있다. 참고로 실행중에는 VS.NET의 솔루션에서 오픈하지 말고, 윈도우 탐색기에서 오픈하라. 다음은 메소드 Add()와 Divide()를 호출한 후에 남은 로그 내용이다.

2008-08-20 00:08:52,468 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Add'
2008-08-20 00:08:52,484 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned '3'
2008-08-20 00:09:06,078 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Divide'
2008-08-20 00:09:06,078 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned 'Quotient: '2'; Rest: '1''


지금까지의 설정처럼 하면 타겟 객체의 모든 메소드를 호출할때마다 CommonLoggingAroundAdvice의 내용이 적용될 것이다. 그러나 때로는 타겟 객체의 특정 메소드를 호출하는 경우에만 설정한 advice들이 적용되기를 바랄 수도 있을 것이다. 


■ pointcut 코딩하기


타겟 객체의 특정 메소드만 호출할때 로그를 남기고 싶다면 앞에서와 같은 기본적인 설정만으로는 부족하다. 해서 좀 더 특별한 설정이 필요하다. 특별한 설정이란 바로 타겟 객체의 pointcut을 지정하는 것이다. AOP의 일반적인 이론에서는 여러 joinpoint( advice가 weaving될 수 있는 포인트. 예를 들어 속성 값이 변하기 전, 후)가 있을 수 있겠지만, 현재 AOP 프락시를 이용하여 AOP를 구현하는 방법에서는 메소드만이 pointcut의 대상이 된다. 말이 점점 어려워진다 -_-;;

여튼 현재의 Spring.NET 버전에서는 메소드만이 advice가 적용될 수 있고 메소드중에서 특별한 메소드만 advice가 적용될 수 있도록 하는 방법이 있다. Spring.Aop.Support네임스페이스 아래에 있는 RegularExpressionMethodPointcutAdvisor 타입을 이용해서 그런 설정을 할 수 있는데, 이 타입은 정규식을 이용하고 있다. 즉 특정 정규식에 일치하는 메소드명을 갖는 타겟 메소드에만 advice를 적용시키는 설정을 할 수 있다.

불행히도 현재 사용하고 있는 샘플 프로젝트중에는 이 설정이 없다.  설정을 조금 수정해야 한다.  시간 관게상 이 작업은 다음 포스트에서 하도록 한다. 

Posted by dalbong2

Aspect지향 프로그래밍! 프레임워크 입장에서는 아주 쓸모있고 중요한 개념이다. 개발자들의 코딩을 화~악 줄여줄 수 있고 또한 프로젝트가 진행하고 있는 도중에도 개발자들의 코드 수정없이 프레임워크단에서 갑의 요청 사항을 최대한 흡수해 줄 수 있는 완충 역할을 할 수 있는 방법이다.

그러나 얼른 와 닫지 않는 용어이다. Object Oriented Programming이라는 용어를 처음 들어을때도 이런 떨떠름한 기분이었을까 하는 생각이 든다. Object가 뭔지 정의를 정확히 내리라면 머뭇거리게 되지만, 그래도 우리는 이것에 대해 이해는 하고 있다. 문장의 주어 또는 목적어로 사용될 수 있는 "놈"들이다.  "이 녀석의 어떤 메소드를 호출하면 ..." 또는 "저 녀석의 어떤 메소드를 호출해줘야 ~ 할 수 있다"처럼 마치 이야기의 대상처럼 사용할 수 있는 것이 object이다.

그럼 aspect란? longman 사전을 찾아보면 다음처럼 정의되어 있다 : "one part of a situation, idea, plan etc that has many parts". 그리고는 다음과 같은 예문이 나와 있다 : "Dealing with people is the most important aspect of my work. 사람을 다루는 일이 내 일중의 가장 중요한 일이다". "전체중의 부분 또는 전체중의 단면"을 의미한다고 하겠다.

소프트웨어 개발에서의 aspect도 의미적으로는 이와 비슷한 개념으로 정리될 수 있을 것 같다. 비즈니스 로직을 실제로 구현하다보면 필요한 비즈니스 로직 구현외에도 기능성 코딩을 해야 하는 경우가 많다. 예를 들어 로깅이나 예외처리, 트랜잭션처리등은 비즈니스 요구와는 직접적인 상관은 없지만 계속 반복되는 기능들이다. 이런 기능들을 애플리케이션을 만들때마다 또는 하나의 애플리케이션에서 다른 비즈니스 로직을 구현할때마다 계속 반복해서 코딩하기 보다는 처리 모듈들을 "단면!"별로 분리해서 구현하자는 것인데, 이런 각각의 단면 모듈들(로깅, 예외처리, 트랜잭션처리, 보안처리등)을 aspect라 하고 있다.

비즈니스 계층의 메소드를 개발할때 다음과 같은 형식의 코딩에 대한 경험이 있을 것이다.

public void 메소드()

{

    // 메소드 시작 로깅

    // 메소드 호출

    // 메소드 종료 로깅

}

또는 다음과 같은 형식으로 트랜잭션을 처리해본 경험도 있을 것이다.

public void 메소드()

{

    // 트랜잭션 설정 및 시작

    try

    {

        // 비즈니스 로직 구현


    }

    catch

    {

        // 트랜잭션 롤백


    }

}

순수한 비즈니스 로직 구현 코드와 로깅, 트랜잭션 처리 코드가 섞여 있고 이런 부가적인 코드는 메소드마다 복사되어서 사용되었다. 이런 로깅 그리고 트랜잭션 처리 코드는 특정 비즈니스 로직에서만 사용되는 것이 아니다. 아래 그림에서처럼 계좌이체 모듈, 입출금모듈, 이자계산 모듈 등 여러 관심 모듈에 걸쳐서(cross) 공통적으로 필요한 모듈들이다. 따라서 aspect를 cross concerns이라는 용어로도 표현한다. 다음 그림에서는 로깅, 보안, 트랜잭션과 같은 cross concerns 구현하고자 하는 비즈니스 관심 모듈의 관계를 개념적으로 표현하고 있다.

(객체 지향을 넘어서 관점 지향으로 AOP. http://www.zdnet.co.kr/builder/dev/java/0,39031622,39147106,00.htm )

객체 지향에서는 객체를 분리해내고 그것을 설계하는 것이 중요하듯이 aspect 지향에서는 앞에서와 같은 cross concerns을 정의하고 분리해서 설계하는 것이 중요하다. 객체 지향으로 설계된 객체들을 구현하는 툴로서 C++, C#이 있듯이 aspect 지향으로 설계된 aspect들을 메인 로직과 혼합하는 작업을 가능하도록 하는 툴들이 있다. .NET계열에서는 Spring.NET이 그 대표적인 예라 하겠다. 그러나 aspect 자체를 구현한 코드는 객체 지향 언어를 사용해서 구현한다.

비즈니스 로직과 그것을 구현하기 위한 핵심 클래스 및 메소드는 객체 지향 설계(OOA)로 도출될 수 있다. 그러나 이런 aspect들은 이런 객체 지향 방법론으로 도출할 수 없었다. 그러니 계속해서 같은 목적( 로깅, 예외처리, 트랜잭션 처리)을 갖는 코드가 조금씩 변경되어서 copy&paste 방식으로 이곳 저곳에서 반복되어서 삽입될 수 밖에 없었지만, aspect지향의 컨셉과 그것을 구현할 수 있는 툴들의 제공으로 이제는 코드가 좀 더 깔끔하게, 좀 더 비즈니스 중심으로 될 수 있게 된것이다.

이쯤되면, AOP란 OOP를 대신하는 프로그래밍 기법이 아님을 인식할 수 있었으리라 본다. 오히려 OOP를 기본으로 하되 그것이 처리할 수 없는 부분을 보충해주는 프로그래밍 방법이라 하겠다.

"AOP를 구현한다"는 것은 "분리된 cross concerns을 실제로 코드로 구현하고 그 코드를 필요한 비즈니스 관심 모듈의 적절한 위치에 삽입하는 작업"이라고 할 수 있겠다. 이런 AOP 구현을 이해하기 위해서는 이해해야하는 하위 개념들이 있다. 이런 개념들은 조금은 낯선 용어들로 표현되고 있다.

advice(또는 interceptor)
advice가 바로 앞에서 말한 "단면"을 구현한 코드이다. 즉 로깅, 트랜잭션등을 구현한 코드를 말한다. 이것을 interceptor라고도 한다. 두 표현 모두 옆에서 치고 들어오는 것들 표현하고 있다. advice 즉 충고 또는 훈수라는 것도 옆에서 갑작스레 치고 들어오는 것은 마찬가지다. 메인 비즈니스 로직에 추가되어 부가적인 훈수를 두는 코드를 말한다.

joinpoint

advice가 치고 들어올 수 있는 포인트들이다. cross concerns 모듈의 메인 비즈니스 로직에 삽입이 가능한 후보 위치를 말한다. 비즈니스 로직을 구현한 메소드가 호출되기 전 또는 후, 반환값이 반환되기 전 , 예외가 던져지는 지점, 클래스가 초기화되는 곳, 필드를 액세스하는 부분등이 모두 advice가 삽입될 수 있는 후보 포인트들이다. 그러나 모든 jointpoint가 실제로 advice가 삽입되는 곳은 아니다.

pointcut

joinpoint중에서 실제로 advice가 적용될 위치를 나타낸다. joinpoint가 개념적인 것이라면 툴마다 실제로 구현하고 있는 pointcut은 다를 수 있다. 뒤에서 보게 되겠지만 특정 pointcut를 나타내는 타입들이 Spring.NET에도 이미 구현되어 있다.

advisor

pointcut + advice를 말한다. 즉 어디서(where, pointcut) 무슨 일(what, advice)이 일이 일어날지를 정의한다. advisor가 바로 aspect의 실제 구현된 모습이라고 할 수 있다.

advised object /advised method

문서를 보다 보면 advised된 객체 또는 메소드라는 말을 보게 된다. advice 코드가 삽입된, 적용된 객체 또는 메소드라는 의미이다. advisor에 의해 훈수를 받은 객체 또는 메소드라는 것이다.

AOP는 일반적인 프로그래밍 방법이다. 즉 Spring.NET만의 개념은 아니다.  Spring.NET에서는 이런 AOP 개념들을 모두 구현하기 위한 방법을 제공하고 있지만, Spring.NET의 IoC컨테이너는 이 AOP 기술에 의존하고 있지는 않다. 즉 Spring.NET 사용자는 원한다면 AOP를 사용하지 않아도 된다는 것이다.

그러나 반복되는 코드를 단지 어트리뷰트를 사용해서 선언적(declarative)인 방식으로 해결할 수 있다면 코드가 깔끔해질 수 있을 것이고 유지, 보수에도 효과적인 방법이 될 수 있을 것이다. Spring.NET에서는 AOP를 구현할 수 있는 모든 준비를 갖춰놓고 있다. 사용자는 이제 HOW-TO만 배우면 되는 것이다.

나중에 알게 되겠지만, Spring.NET에서 AOP 개념은 프락시를 이용하고 있다. 그리고 프락시에 대한 소유권은 프레임워크에서 가지게 된다. 개발자가 타겟 객체를 요구할때 프레임워크에서는 그 객체에 대한 프락시를 반환하는 패턴을 이용하게 된다. 이 프락시를 잘 이용하면 개발자의 일명 삽질이 상당히 줄어들 수 있다.

프락시 코드를 프레임워크단이 가진다는 의미는 타겟 객체의 메소드 호출을 프레임워크단에서 모두 catch할 수 있다는 것인다. 즉 실제로 타겟 객체의 메소드 호출을 수행하기 전에 그리고 메소드 호출을 수행하고 나서의 순간들을 모두 프레임워크에서 포착할 수 있게 되어 필요한 작업을 할 수 있다. 필요한 작업이란 예를 들어 타겟 메소드를 호출하기 전에 타이머를 실행시켜 놓은 다음 타겟 메소드의 호출이 종료된 후 타이머의 시간을 재서 메소드의 실행 시간을 체크할 수도 있다는 것이다. 또 다른 예로 프락시를 통해서 타겟 메소드에 대한 정보를 얻어서 적절한 로그를 남기는 작업을 프레임워크단에서 처리할 수도 있다. 이런 작업들이 개발자들의 코드 수정없이 프레임워크단에서 일괄적으로 처리될 수 있다는 것이다. 즉 프락시 패턴을 이용하게 되면 프로젝트 진행 도중에 비즈니스 로직과 상관없는 추가 요구 사항은 최대한 프레임워크단에서 커버할 수 있는 구조가 된다는 것이다.

다음 포스트에서는 AOP 관련 샘플을 통해서 Spring.NET이 지원하는 방법을 알아보겠다. 앞에서 보이지 못한 IoC 예제 코드 즉 객체를 등록하고 설정하는 방법에 대한 것도 이 예제에서 함께 설명하도록 하겠다.

Posted by dalbong2