본문 바로가기

IT 살이/04. 기술 - 프로그래밍

개발 프레임워크 만들기 대장정 26 - AOP 적용 예제 II

다음은 AOP를 적용하기 위한 설정으로서 앞 포스트에서 보여준 Spring.Calculator.Web.2005 의 web.config의 일부분이다.

\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 CommonLoggingAroundAdvice\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Aspects.Logging.CommonLoggingAroundAdvice, Spring.Aspects\cf0 "\cf2 >\par ??\tab <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 Level\cf0 "\cf2 \cf6 value\cf2 =\cf0 "\cf2 Debug\cf0 "\cf2 />\par ??\par ??\par ??\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 calculator\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services\cf0 "\cf2 />\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 calculatorWeaved\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop\cf0 "\cf2 >\par ??\tab <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 target\cf0 "\cf2 \cf6 ref\cf2 =\cf0 "\cf2 calculator\cf0 "\cf2 />\par ??\tab <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 interceptorNames\cf0 "\cf2 >\par ??\tab \tab <\cf13 list\cf2 >\par ??\tab \tab \tab <\cf13 value\cf2 >\cf0 CommonLoggingAroundAdvice\cf2 \par ??\tab \tab \par ??\tab \par ??\par ??} -->

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

이 설정은 타겟 객체 AdvancedCalculator의 모든 메소드에 대해서 설정된 advice 로직이 적용된다고 했다. 오늘은 타겟 객체의 특정 메소드만 호출할때에만 CommonLoggingAroundAdvice가 적용되도록 하는 설정법을 알아본다. 즉 특정 메소드를 호출하는 경우에만 로그가 남게 될 것이다. 그러기 위해서는 advice가 적용될 부분 즉 pointcut을 지정해줘야 한다. 그러나 불행히도 현재 사용하고 있는 샘플 프로젝트중에는 이 설정에 대한 예가 없다.  따라서 기존의 설정을 조금 수정해야 한다.  


■ pointcut 필터링하기


\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 CommonLoggingAroundAdvice\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Aspects.Logging.CommonLoggingAroundAdvice, Spring.Aspects\cf0 "\cf2 >\par ??\tab <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 Level\cf0 "\cf2 \cf6 value\cf2 =\cf0 "\cf2 Debug\cf0 "\cf2 />\par ??\par ??\par ??\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 expressionMethodCutAdvisor\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop\cf0 "\cf2 >\par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 pattern\cf0 "\cf2 \cf6 value\cf2 =\cf0 "\cf2 Add*\cf0 "\cf2 />\par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 advice\cf0 "\cf2 \cf6 ref\cf2 =\cf0 "\cf2 CommonLoggingAroundAdvice\cf0 "\cf2 />\par ??\par ??\par ??\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 calculator\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services\cf0 "\cf2 />\par ??\par ??\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 calculatorWeaved\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop\cf0 "\cf2 >\par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 target\cf0 "\cf2 \cf6 ref\cf2 =\cf0 "\cf2 calculator\cf0 "\cf2 />\par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 interceptorNames\cf0 "\cf2 >\par ?? <\cf13 list\cf2 >\par ?? <\cf13 value\cf2 >\cf0 expressionMethodCutAdvisor\cf2 \par ?? \par ?? \par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 ProxyInterfaces\cf0 "\cf2 >\par ?? <\cf13 list\cf2 >\par ?? <\cf13 value\cf2 >\cf0 Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract\cf2 \par ?? \par ?? \par ??\par ??} -->

<!-- Aspect -->


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

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

</object>


<!-- Advisor (advice + pointcut). -->


<object id="regularExpressionMethodCutAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop">

  <property name="pattern" value="Add*"/><!-- pointcut -->

  <property name="advice" ref="CommonLoggingAroundAdvice"/><!-- advice -->

</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>regularExpressionMethodCutAdvisor</value>

    </list>

  </property>

</object>

조금이 아닌가?

변한 부분은 두 군데 있다. Advisor라고 주석이 달린 부분의 코드와 ProxyFactoryObject를 설정하는 부분에서 InterceptorNames 속성에 CommonLoggingAroundAdvce에 대한 id값을 추가하는 것이 아니라 추가된 advisor 객체의 id값을 추가하고 있다.

지난 포스트에서 advice와 pointcut을 합친 것을 advisor라고 했다. 앞의 설정에서는 pointcut 단독의 설정 대신에 advisor를 사용해서 advice와 advice가 적용될 pointcut을 함께 표현하고 있다. advice와 pointcut를 설정하기 위해서 RegularExpressionMethodPointcutAdvisor라는 타입을 사용하고 있는데 이 타입의 이름에서 의미있는 두가지 표현이 포함되어 있다:  RegularExpression, MethodPointcut.

AOP 기본 개념중에서 joinpint, pointcut이 있다. joinpoint란 프로그램이 실행되는 동안의 특정 순간들을 말한다. 예를 들어 메소드가 호출되는 순간, 특정 예외가 발생하는 순간등. pointcut이란 실제로 advice가 적용되는 joinpoint들의 집합을 말한다( Spring.NET 레퍼런스 문서. 13.1.1. AOP concepts 절 참조 ). joinpoint가 개념적인 용어라면 pointcut은 실제로 Spring같은 프레임워크에서 구현되어서 advice가 삽입될 곳을 지정하는 지정하는 객체라 할 수 있겠다.

다시 앞의 advisor 타입의 이름에 대한 이야기로 가서, MethodPointcut은 이런 pointcut중에서도 메소드 pointcut이라는 것을 나타내고 있다. 즉 특정 메소드의 호출을 pointcut으로 지정하는 advisor라 하겠다. 그 특정 메소드를 지정하기 위해서 정규식을 사용하겠다는 것이다. 앞의 설정은 타겟 객체의 메소드중에서 메소드명이 "Add"로 시작하는 것에만 advice CommonLoggingAroundAdvice를 적용하겠다는 것을 표현하고 있다. 이런 설정을 포함하고 있는 RegularExpressionMethodPointcutAdvisor 객체의 id를regularExpressionMethodCutAdvisor으로 설정하고 있다.

ProxyFactoryObject는 InterceptorNames 속성에 그 id를 추가시키고 있다. 타겟 객체의 어떤 메소드를 호출할때 advice를 적용할지에 대한 정보가 모두 regularExrpessionMethodCutAdvisor에 포함되어 있다. 따라서 런타임시에 ProxyFactoryObject가 타겟 객체에 대한 프락시 객체를 동적으로 생성해 낼 때에 그 정보를 이용해서 적절한 메소드 호출의 전, 후에 로깅 advice를 적용하는 AOP 프락시를 만들어 낸다.

web.config의 설정을 이렇게 변경하고 Spring.Calculator.Web.2005  웹 애플리케이션을 다시 실행시켜 본다.

AOP가 적용되는 예를 보기 위해서는 두번재 링크를 클릭해야 한다. 이 링크를 클릭하면 웹 서비스를 테스트 할 수 있는 화면이 출력된다.

참고로 웹 애플리케이션 프로젝트를 봐도 calculatorServiceWeaved.asmx 파일은 없다. Spring.NET의 프레임워크 중에서 특별할 HttpHandler를 제공하고 있는데 이것을 사용하면 .asmx 파일에 대한 Http 요청이 오면 이것을 알아서 처리해 준다. 사용자 입장에서는 마치 asmx 파일이 서버에 존재하는 것처럼 보여진다. 이것에 대해서는 뒤에서 Spring.NET이 Web Services를 지원하는 부분을 알아보면서 살펴볼 것이다.

여튼 이 테스트 페이지에서 마음대로 메소드를 호출해서 테스트를  해 보고 나서 남은 로그를 살펴보자.

2008-08-20 20:50:31,984 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Add'
2008-08-20 20:50:32,015 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned '3'

...

Add 메소드에 대한 로그만 남아 있는 것을 알 수 있다.  regularExpressionMethodCutAdvisor advisor의 설정으로 advice가 적용될 메소드가 필터링된것이다.

RegularExpressionMethodPointcutAdvisor를 사용하면 앞에서처럼 정규식을 이용해서 advice가 적용될 메소드(!)를 지정해줄 수 있었다. 이 외에도 NameMatchMethodPointcutAdvisor를 이용하면 지정한 메소드명과 일치하는 타겟 객체의 메소드에만 advice가 적용될 수 있도록 하는 advisor 타입도 있다.

pointcut 필터링에 대한 이야기는 이것으로 마무리하고 한가지만 더 알아보겠다.


■ AOP용 인터페이스 지정하기


만약 ProxyFactoryObject 객체가 넘겨받은 타겟 객체가 하나의 인터페이스만을 구현하것이 아니라 여러개의 인터페이스를 구현한 경우를 생각해보자.  앞의 예제에서의 AdvancedCalculator는 인터페이스 IAdvancedCalculator만을 구현하고 있다. 따라서 ProxyFactoryObject가 AOP 인터페이스를 생성할때 선택이 필요없었다. 그러나 타겟 객체가 두개 이상의 인터페이스를 구현하고 있다면 어떤 인터페이스에 대한 AOP가 적용되어야 할지를 지정해줘야 한다. ProxyFactoryObject의 속성 ProxyInterfaces가 이런 목적으로 사용된다.

\par ??\par ??<\cf13 object\cf2 \cf6 id\cf2 =\cf0 "\cf2 calculatorWeaved\cf0 "\cf2 \cf6 type\cf2 =\cf0 "\cf2 Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop\cf0 "\cf2 >\par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 target\cf0 "\cf2 \cf6 ref\cf2 =\cf0 "\cf2 calculator\cf0 "\cf2 />\par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 interceptorNames\cf0 "\cf2 >\par ?? <\cf13 list\cf2 >\par ?? <\cf13 value\cf2 >\cf0 expressionMethodCutAdvisor\cf2 \par ?? \par ?? \par ?? <\cf13 property\cf2 \cf6 name\cf2 =\cf0 "\cf2 ProxyInterfaces\cf0 "\cf2 >\par ?? <\cf13 list\cf2 >\par ?? <\cf13 value\cf2 >\cf0 Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract\cf2 \par ?? \par ?? \par ??\par ??} -->

<!-- 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>expressionMethodCutAdvisor</value>

    </list>

  </property>

  <property name="ProxyInterfaces">

    <list>

      <value>Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract</value>

    </list>

  </property>

</object>

이 속성은 타입 형식을 표현하는 "네임스페이스.타입, 어셈블리명" 형식의 문자열을 받는다. 타겟 객체가 하나의 인터페이스만을 구현하고 있다면 이 속성은 생략해도 된다.


오늘은 여기까지.  다음 포스트에서는 advice 종류 즉 around, before, after, throws advice에 대한 얘기를 하고 이어서 Spring.NET의 Web Services 지원 얘기를 해 보겠다.