태터데스크 관리자

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

태터데스크 메시지

저장하였습니다.

■ advice 종류

▶ around advice

앞에서 알아본 CommonLoggingAroundAdvice 타입은 around advce중의 하나였다. 즉 인터페이스 IMethodInterceptor를 상속해서 Invoke() 메소드를 구현하고 있다. 아래는 IMethodInterceptor의 정의이다.

namespace AopAlliance.Intercept

{

    public interface IMethodInterceptor : ...

    {

           object Invoke(IMethodInvocation invocation);

    }

}

이 메소드의 인자로 넘어오는 invocation은 인터셉트된 타겟 객체에 대한 호출을 나타낸다. 이 인자의 Proceed() 메소드를 호출하면 인터셉트되어서 중단된 타겟 메소드 호출이 계속 진행된다.. Invoke()의 간단한 구현 코드이다.

...

public object Invoke(IMethodInvocation invocation)

{

    Console.WriteLine("Before invocation");

    object returnValue = invocation.Proceed();

    Console.WriteLine("After invocation and before return");

    return returnValue;

}

...

Proceed()가 호출되기 전에 원하는 작업을 할 수 있고, 호출된 후 그리고 반환값이 클라이언트 코드로 넘어가기 전에 또한 원하는 작업을 할 수 있다. 이곳에서 리턴되는 반환값을 조작해서 추가 정보를 넣거나 또는 제거할 수도 있다. 이처럼 타겟 메소드 호출 전, 후에 원하는 작업을 할 수 있는 advice를 around advice라고 한다.

참고로 이 advice가 설정되는 xml을 다시 보면 아래와 같다.

<!-- Aspect -->


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

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

</object>


<!--타겟객체-->


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

<!-- Applies AOP on the contact service. -->

<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>

이렇게 설정해 놓으면 타겟 객체( calculator)의 메소드가 호출이 될때 commonLoggingAroundAdvice의 Invoke()에 의해서 인터셉트된다는 것이다. 가릿?

▶ before advice

before, after advice는 이름에서 예상되는 것처럼 호출전에 또는 호출된 후( 반환값을 반환하기전에)에만 끼어들기가 가능한 좀 더 간단한 advice이다.  before advice에서는 타겟 객체를 호출하기 위해서 Proceed()를 호출할 필요가 없다.  before advice를 구현하려면 인터페이스 IMethodBeforeAdvice를 상속해서 메소드 Before()를 구현해야 한다. 

public interface IMethodBeforeAdvice : ...

{

    void Before(MethodInfo method, object[] args, object target);

}

Before() 메소드는 타겟 메소드가 호출되기 전에 Spring.NET 프레워크에 의해서 호출된다. 이곳에서 필요한 사용자 정의 작업을 할 수 있다. method는 현재 인터셉트된 메소드에 대한 정보이고 args는 그 메소드를 호출할때 넘겨준 인자들에 대한 정보를 가지고 있다. 그리고 target은 타겟 객체에 대한 참조를 가지고 있다.

CommonLoggingAroundAdvice와 유사하게 CommonLoggingBeforeAdvice같은 advice를 구현해 놓고 앞에서처럼 설정을 하면 타겟 객체의 메소드들이 호출되기 전에 Before()가 호출된다는 것이다. 가릿? 가릿!

Before()를 수행하다 예외가 발생하면 이 예외는 이 메소드를 호출한 호출자로 전달된다.

▶ after advice

after advice 객체를 구현해서 설정해놓으면 타겟 메소드가 호출된 후에 클라이언트 코드로 반환되기 전에 호출될 수 있는데, 호출되는 메소드는 IAfterReturningAdvice 인터페이스를 구현한 객체의 AfterReturning()이다.

public interface IAfterReturningAdvice : ...

{

    void AfterReturning(object returnValue, MethodInfo method, object[] args, object target);

}

AfterReturning() 메소드에 전달되는 인자를 통해서 반환값, 타겟 메소드등에 대한 정보에 접근할 수 있다.

before advice에서는 예외가 발생하면 실행 경로를 역으로 진행해서 호출한 호출자에게 예외를 전달했다. 그러나 after advice는 실행 경로를 계속 유지한다. 그러나 리턴값을 반환하지 않고 예외를 반환한다.  실행 경로(excution path)란 advice 체인이 실행되는 순서를 말하는데 아래의 advice 체인에서 보여주는 그림을 참조하라.

▶ throws advice

throws advice 객체는 예상대로 타겟 객체에서 예외가 발생할때 호출되는 객체이다. 정확히는 throws advice가 적용된 후의 실행 경로상에서 예외가 발생할때 호출된다. 자세한 내용은 뒤의 "advice 체인"을 설명하는 곳을 참조한다.  이 녀석도 다른 녀석들처럼 throws advice가 되기 위해서는 상속해야 하는 인터페이스가 있다.

public interface IThrowsAdvice : IAdvice

{

}

근데 이 녀석은 다른 녀석들과는 다르게 구현해야 하는 메소드는 없다. 단지 구현되는 타입이 throws advice임을 나타내기만 하는 마커 인터페이스(marker interface 또는 태그 인터페이스 tag interface라고도 한다)이다. 그럼 예외가 발생했을때 Spring 프레임워크는 구현체의 어떤 메소드를 호출하게 될까. 구현체의 AfterThrowing() 메소드를 호출한다. Spring 프레임워크에 하드 코딩되어 있다고 볼 수 있겠다. AfterThrowing() 메소드에 전달되는 인자도 다음 두 유형중의 하나여야 한다.

void AfterThrowing(Exception ex)

void AfterThrowing( MethodInfo method, Object[] args, Object target, Exception ex)

다음은 실제 구현체에 대한 간단한 예제이다.

public class ConsoleLoggingThrowsAdvice : IThrowsAdvice

{

    public void AfterThrowing(Exception ex) // 실제로 이렇게 두 메소드이 예외 타입이 동일하게 구현하면 에러난다. 이유는 조금 아래에 있다.

    {

        // 예외정보로 필요한 예외 처리를 한다.

    }


    public void AfterThrowing(MethodInfo method, Object[] args, Object target, Exception ex)

    {

        // 메소드, 호출 인자, 타겟 객체에 대한 정보, 예외 정보로 필요한 예외 처리를 한다.

    }

}

그럼 왜 다른 advice처럼 아래와 유사한 형식으로 인터페이스를 정의하지 않았을까.

public interface IThrowsAdvice : IAdvice

{

    void AfterThrowing(Exception ex); // 왜 이와 유사한 메소드를 정의하지 않았을까?

    void AfterThrowing(MethodInfo method, Object[] args, Object target, Exception ex)

}

이렇게 하지 못하는(아니 하지 않는) 이유는 Spring 프레임워크에서 AfterThrowing() 메소드로 전달되는 예외 타입별로 핸들링을 할 수 있는 구조를 제공하기 위한 것이다. 다음과 같은 예외 처리 구조에 대해서는 익히 알고 있을 것이다.

//사용자 정의 예외 객체

public class MyException : Exception

{

}

...

public void method()

{

    try

    {

     // 작업...

    }

    catch (SqlException ex1)

    {

        // SqlException  예외를 처리한다.

    }

    catch (MyException ex2)

    {

        // MyException 예외를 처리한다.

    }

    catch (Exception ex3)

    {

        //Exception 예외를 처리한다.

    }

}

예외별로  다른 처리를 하고 싶다면 이런 구조적인 예외 메커니즘을 이용한다.

Spring.NET에서도 이런 유사한 구조를 제공하고자 한다. 해서 예외가 발생하면 Spring 프레임워크는 그 예외의 타입을 인식해서 적용된 throws advice에 구현되어 있는 AfterThrowing() 메소드들의 마지막 인자 즉 예외 객체의 타입과 비교를 한다. 그래서 만약 일치하는 예외 타입의 인자를 갖거나 또는 일치하는 예외 타입이 없다면 호환될 수 있는 예외 타입을 가지고 있는 AfterThrowing()을 호출한다. 만약 정의된 메소드중에서 발생한 예외의 타입과 호환되는 예외 타입의 인자를 갖는 예외 핸들링 메소드가 없다면 발생한 예외는 상위의 호출자로 버블링된다.

만약 AfterThrows() 메소드들중에서 발생한 예외 객체의 타입과 동일한 타입의 예외 인자를 갖는 메소드가 두개이상이라면? 런타임시 예외가 발생한다.

첫줄을 보면 하나의 메소드( AfterThrowing())안에 동일한 예외 타입의 인자를 갖는 메소드가 동시에 정의될 수 없다는 내용이다. 앞에서 예로 든 ConsoleLoggingThrowsAdvice 코드는 따라서 잘못된 것이다. 앞의 코드는 다음처럼 수정해서, AfterThrowing()의 마지막 인자인 예외 타입은 서로 달라야 한다.

public class ConsoleLoggingThrowsAdvice : IThrowsAdvice

{

    public void AfterThrowing(Exception ex)

    {

        Console.Out.WriteLine("Exception handler applied");

    }

    public void AfterThrowing(MyException ex)

    {

        Console.Out.WriteLine("MyException handler applied");

    }

    public void AfterThrowing(MethodInfo method, Object[] args, Object target, SqlException ex)

    {

        Console.Out.WriteLine("SqlException handler applied");

    }

}

얘기가 길어졌다. 이제 앞에서 던진 질문, throws advice에서 IThrowsAdvice에 AfterThrowing() 메소드를 포함하고 있지 않은 이유를 생각해보자. 간단하다. AfterThrowing() 메소드의 마지막 인자 즉 예외 객체의 타입을 미리 알 수 없다는 것이다. 사용자 정의 예외 타입을 사용한다면 어떻게 미리 알 수 있어서 인터페이스 메소드로 포함시키겠는가.

public interface IThrowsAdvice : IAdvice

{

    void AfterThrowing(MyException ex);//??

}

IThrowsAdvice를 상속하는 모든 구현체는 이 예외 타입의 메소드를 구현해야 한다는 얘기다. 해서 Spring에서는 이 방법을 버리고 런타임시, 실제 발생한 예외의 타입과 구현되어 있는 예외 타입을 비교해서 어떤 AfterThrowing()을 호출할지를 결정하는 방법을 선택했을 것이라는 순전히 개인적인 추측이다. 다른 이유가 있는지는 모르겠다.


■  advice 체인과 advice 실행 순서


앞에서 계속 미뤘던 advice 체인 개념을 알아보자. 타겟 객체의 하나의 pointcut에 대해서 하나만 advice를 적용할 수 있는 것은 아니다. 하나의 pointcut에 대해 앞에서 설명한 여러 종류의 advice 객체들을 여러개 적용할 수 있다. 타겟 객체 앞에 여러개의 advice가 체인처럼 연결되어 놓여져 있다. 타겟 메소드를 호출하면 체인처럼 설정되어 있는 모든 advice들을 호출에 적용하고 나서 최종적으로 타겟 메소드가 호출되는 것이다.  다음은 여러개의 advice들을 적용한 설정 예제이다.

<objects xmlns="http://www.springframework.net">


  <object id="beforeAdvice1"

          type="Spring.AopQuickStart.Aspects.ConsoleLoggingBeforeAdvice1, Spring.AopQuickStart.Common" />


  <object id="beforeAdvice2"

          type="Spring.AopQuickStart.Aspects.ConsoleLoggingBeforeAdvice2, Spring.AopQuickStart.Common" />


  <object id="afterAdvice1"

    type="Spring.AopQuickStart.Aspects.ConsoleLoggingAfterAdvice, Spring.AopQuickStart.Common" />


  <object id="aroundAdvice1"

          type="Spring.AopQuickStart.Aspects.ConsoleLoggingAroundAdvice, Spring.AopQuickStart.Common" />


  <object id="throwsAdvice1"

          type="Spring.AopQuickStart.Aspects.ConsoleLoggingThrowsAdvice, Spring.AopQuickStart.Common" />


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

    <property name="Target">

      <object type="Spring.AopQuickStart.Commands.ServiceCommand, Spring.AopQuickStart.Common" />

    </property>

    <property name="InterceptorNames">

      <list>

        <value>throwsAdvice1</value>       

        <value>beforeAdvice1</value>

        <value>aroundAdvice1</value>

        <value>afterAdvice1</value>

        <value>beforeAdvice2</value>

      </list>

    </property>

  </object>


</objects>

before, around, after, throws advice들이 모두 적용되었고 그리고 before advice는 ConsoleLoggingBeforeAdvice1, ConsoleLoggingBeforeAdvice2 두 개가 적용되었다. 다음과 같은 advice 체인을 상상할 수 있다.

여기서 생각해 볼 문제가 하나 있다. advice가 실행되는 순서이다. 예제처럼 beforeAdvice2가 afterAdvice1보다 뒤에 설정되어 있다고 해서 그 적용 순서도 뒤일까. 이렇게 되면 말이 되지 않는다. after advice는 타겟 객체를 호출하고 나서 적용되는  advice이고 before advice는 타겟 객체가 호출되기 전의 advice이다. 설정은 뒤에 오더라도 적용되는 순서는 beforeAdvice2가 먼저여야 한다.

정리하면 advice 체인의 순서가 실제 적용되는 순서는 아니라는 것이다. advice 타입에 따라서 그 적용 순서는 바뀔 수 있다. 다음 그림은 예제에서 설정된 advice들이 실행되는 순서를 그림으로 보여주고 있다.

다음은 앞에서의 예제대로 설정해서 샘플 프로그램을 작성해서 실행한 결과이다.

여기서 한가지 주의할 것은 throws advice는 제일 먼저 설정되어야 한다는 것이다. 그래야 이 후의 advice 적용시 예외가 발생하더라도 그 예외도 설정한 throws advice에서 핸들링할 수 있다. throws advice가 적용되기 전에 중간의 advice에서 예외가 발생하면 그 예외에 대해서는 throws advice가 적용되지 않는다.


지금까지 AOP에 대한 이야기였다. Spring.NET의 중요한 구성 요소중의 하나가 AOP 프레임워크이지만, Spring.NET의 IoC 컨테이너가 AOP에 종속되는 것은 아니다. 이것은 원하지 않는다면 AOP를 사용하지 않고도 Spring.NET 프레임워크를 사용할 수 있다는 것이다.

실전 프로젝트에서도 Spring.NET 원형 그대로를 사용할 수도 있겠지만, Spring.NET 프레임워크를 기반으로 해서 그 프로젝트 상황에 맞도록 한단계 더 추상화된 개발 프레임워크를 제작할 수도 있을 것이다. 이때 다시 프레임워크를 만드는 입장이 되어서 AOP를 사용하면 프레임워크다운 프레임워크가 될 수 있을 것이다. 개발자에게 다양한 Cross cutting concerns에 걸쳐서 동일한 코드를 Copy&Paste하도록 하는 것보다는 하나의 advice 모듈로 구현한 것을 공통팀에서 관리하는 것이 훨씬 더 효율적일 것이라는 것이다. 남은 것은 이제 advice로 구현할 수 있는 cross cutting concerns를 추상화하고 설계하는 것이다.

다음 포스트에서는 별다른 주제가 없으면 Spring.NET이 지원하는 ASP.NET Web Services에 대한 이야기가 될 것이다.

Posted 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