스프링 핵심 개념 - Aop

From JCFWiKi

Jump to: navigation, search

그림:check.gif

  • 산출물 : Spring AOP 이해하기
  • 작성자: 김민아
  • 최초작성일 : 2008//05/21

Copyright © 2008 Daewoo Information Systems Co., Ltd.

그림:information.gif

목차

[편집] 객체지향프로그래밍의 문제

  • Concern
    • 다수의 객체들간에 널리 퍼져서 존재하는 Concern들을 어떻게 관리할 것인가
    • 이 Concern들은 프로그램을 복잡하게 만들고, 코드의 변경을 어렵게 한다.
객체들간에 존재하는 공통 관심 사항들

[편집] AOP(Aspect Oriented Programing)란?

  • 객체 지향 개발을 보완
  • 다양한 객체들에 분산되어 중복적으로 존재하는 공통 관심 사항(Concern)들을 묶어서 모듈화하여 Aspect들을 분리하고
  • 이렇게 분리된 Aspect 기반으로 개발

Aop가 적용되지 않았을 때 Aop가 적용되었을 때

  • 위 그림을 예로 들면 로그처리, 보안처리, 트랜젝션 처리는 모든 로직에서 필요한 부분이다.
  • 객체 지향 관점에서의 개발은 Student Service, Cource Sevice...등등 로직마다 로그처리, 보안처리, 트랜젝션 처리를 일일이 해주어여한다.(위)
  • 하지만 AOP라는 개념을 활용하면 로그처리, 보안처리, 트랜젝션 처리 모듈을 별도로 만들어 weaving이라는 개념을 사용하여 Student Service, Cource Sevice...등등 로직이 처리될 때마다 로그처리, 보안처리, 트랜젝션 처리를 적용되도록 수 있다.(아래)

[편집] AOP의 주요 개념/용어

[편집] Aspect

  • 하나의 Concern에 대한 모듈화 된 묶음

[편집] Joinpoint

  • 프로그램이 실행되는 동안의 Aspect가 수행될 지점
  • 다양한 객체들에 존재하던 Concern의 실제 위치

[편집] Advice

  • 각각의 Joinpoint에 적용될 행위

[편집] Pointcut

  • Jointpoint의 묶음으로서 Advice는 pointcut 단위로 적용된다.

[편집] Introduction

  • Jointpoint를 가지는 클래스에 메소드나 필드들을 추가하는 행위

[편집] Weaving

  • Aspect들을 Advice될 대상 객체들와 엮어주는 행위
Weaving Concern
  • Weaving 방식
    • 컴파일 타임 Weaving(AspectJ,..)
    • 클래스 로딩 타임 Weaving((AspectJ,...)
    • 런타임 Weaving(SpringAOP,..)

[편집] 프로그램에 의한 AOP 구현

  • 스프링에서 선언적인 혹은 어노테이션에 의한 AOP 적용 방법을 살펴 보기 전에, 프로그램에 의해서 직접 AOP를 실행시켜보는 작업을 해보자. 프로그램적으로 AOP를 적용해 봄으로서 AOP가 작동하는 원리에 대한 이해를 도울 수 있다.
  • 관련 모듈은 다음과 같다.
    • Printer 클래스 : aop 적용 대상이 되는 클래스(Pointcut)
    • LogAdvisor 클래스 : aspect의 advice 역할을 수행할 클래스
    • TestPrinter 클래스 : Printer와 LogAdvisor 클래스를 통해 AOP를 실행할 테스트 클래스
public class Printer {
 
	public void print(){		
		System.out.println("print!!!");		
	}
	
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
 
public class LogAdvisor implements MethodInterceptor {
 
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("[before : LogAdvisor]");
		Object object = invocation.proceed();
		System.out.println("[after : LogAdvisor]");
		return object;
	}
 
}
import org.springframework.aop.framework.ProxyFactory;
 
public class TestPrinter {
 
	public static void main(String[] args) {
		Printer printer = new Printer();
 
		ProxyFactory pf = new ProxyFactory();
 
		pf.addAdvice(new LogAdvisor());
		pf.setTarget(printer);
 
		Printer printerProxy = (Printer) pf.getProxy();
 
		// write the messages
		printerProxy.print();
	}
}
  • 위의 TestPrinter 클래스의 main(..) 메소드에서는 Printer 클래스를 이용하여 프록시 클래스를 생성하고 이를 통해서 print()메소드를 호출하고 있다. org.springframework.aop.framework.ProxyFactory 클래스에서는 이와 같이 pointcut 클래스와 advice 클래스를 가지고 AOP를 수행할 수 있는 프록시 클래스를 생성해 준다. 생성된 클래스를 pointcut 클래스와 타입 호환성을 갖게 된다.


[편집] 스프링 XML을 사용한 선언적인 AOP 적용 방법

  • 앞에서 작성했던 프로그램에 의한 AOP를 스프링을 통해서 선언적으로 적용해 본다.
  • 관련 클래스
    • Printer 클래스 : (상동)
    • LogAdvisor 클래스 : (상동)
    • TestPrinter 클래스 : (상동)
    • ac.xml : aop의 적용을 선언적으로 처리하기 위한 스프링 설정 파일.
public class Printer {
 
	public void print(){		
		System.out.println("print!!!");		
	}
	
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
 
public class LogAdvisor implements MethodInterceptor {
 
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("[before : LogAdvisor]");
		Object object = invocation.proceed();
		System.out.println("[after : LogAdvisor]");
		return object;
	}
 
}
import org.springframework.aop.framework.ProxyFactory;
 
public class TestPrinter {
 
	public static void main(String[] args) {
		
		ApplicationContext context = new ClassPathXmlApplicationContext("aop/ac.xml");
		Printer printer = (Printer) context.getBean("printer");
		
		printer.print();
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
 
	<bean id="targetPrinter" class="aop2.Printer" />
	
	<bean id="logAdvisor" class="aop2.LogAdvisor">
	</bean>	
	
	<bean id="printer" 
	    class="org.springframework.aop.framework.ProxyFactoryBean">	
	    <property name="target" ref="targetPrinter"/>
	    <property name="interceptorNames">
	        <list>
	            <value>logAdvisor</value>
	        </list>
	    </property>
	</bean>	
 
</beans>
  • Printer와 LogAdvisor는 앞선 소스와 동일하다. TestPrinter 클래스의 main() 메소드를 살펴 보면, 직접 프록시를 생성하는 것이 아니라 스프링을 통해서 프린터 프록시를 가져오도록 하고 있다. ac.xml을 살펴보면, 빈 정의에 의해서 프록시 대상(pointcut)과 Advisor를 설정하고 있을 것을 살펴 볼 수 있다.


[편집] 스프링 aop 태그를 사용한 AOP 적용 방법

  • 스프링은 다양한 방법으로 AOP를 적용할 수 있도록 지원.
  • 여기서는 단순하고, 유연성있는 aop 네임스페이스를 활용한 AOP 적용 방법을 권장한다.
aop 태그 내용
<aop:config /> aop 설정을 포함.
<aop:aspect /> Aspect를 설정.
<aop:pointcut /> pointcut을 정의.
<aop:before /> before advice를 정의.
<aop:after-returning /> 메소드의 정상 실행 결과 후에 적용될 advice 정의.
<aop:after-throwing /> 메소드가 예외 발생시 적용될 advice 정의.
<aop:after /> 메소드 실행 후(예외발생시라도) 무조건 실행하는 advice 정의.
<aop:around /> 메소드의 시작전/후를 모두 제어할 수 있는 advice 정의
  • 설정 예제 : before advice
<bean id="logging" class="edu.aop.LogAspect">
 
<aop:config>
	<aop:aspect id="loggingAspect" ref="logging">
		<aop:pointcut id="serviceOperation" expression="execution(* *..*Service.*(..)"/>
		<aop:before pointcut-ref="serviceOperation" method="doLog" />
	</aop:aspect>
</aop:config>
  • pointcut의 표현식
    • 문법
      • execution(수식어 리턴타입 패키지.클래스명.메소드명(파라미터))
      • within(타입)
      • bean(빈이름)
    • 연산자
      • &&
      • ||
    • 패턴
      • * : 모든 유형의 값
      • .. : 0개 이상..
    • 예제
      • execution(public void set*(..))
      • execution(* *..*Service.*(*,*))
      • execution(Integer edu.*.*(*))
      • within(edu.ctl.*)
      • bean(*Service)

[편집] 단순한 Log Aspect 만들기

[편집] 순서

  • 타겟 클래스 작성(AOP타겟)
  • Aspect Advisor 클래스 작성
  • Spring Context에 AOP 설정
  • 테스팅 클래스 작성 후 실행.

[편집] 타겟 클래스 만들기

  • 콘솔에 메세지를 프린트하는 메소드 3가지 구현.
public class Printer {
	
	public void print(){
		System.out.println("print is executed.");
	}	
	public void printMessage(String message){
		System.out.println("print is executed. Message is " + message);		
	}	
	public String printMessateAndReturn(String message){
		System.out.println("print is executed. Message is " + 
          message + "It will be returned.");
		return message;
	}
}

[편집] Aspect Advisor 클래스 만들기

  • LogAdvisor 클래스를 작성한다.
  • LogAdvisor는 around advice를 제공하는 doLog(..) 메소드를 하나 갖는다.
public class LogAdvisor{	
	public Object doLog(ProceedingJoinPoint call) throws Throwable {			
		Signature signature = call.getSignature();
		System.out.println(new Date() + " : "+ 
				call.getTarget().getClass().getName() + "." + 
				signature.getName() + " method is executed.");
		return call.proceed();
	}
}

[편집] Spring Context에 AOP 설정

  • 시나리오상 컨텍스트 파일명은 as-aspect.xml로 한다.
  • AOP타겟(Pointcut)인 Printer와 Aspect Advisor인 LogAdvisor 클래스를 스프링 빈으로 정의한다.
  • < aop:config /> 태그를 사용하여 advisor와 pointcut이 연관지어지도록 설정한다.
<beans default-autowire="no" default-lazy-init="false"
	default-dependency-check="none"
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
	http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
	http://www.springframework.org/schema/jee 
        http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
 
	<bean id="logAdvisor" class="edu.aspect.LogAdvisor"/>	
	<bean id="printer" class="edu.aspect.Printer"/>
	<aop:config>
		<aop:aspect ref="logAdvisor" >
			<aop:around pointcut="execution(* edu.aspect.Printer.*(..))" 
                         method="doLog" />			
		</aop:aspect>
	</aop:config>
</beans>

[편집] 테스트 클래스 작성 및 실행

  • JUnit을 사용하여 초기화를 하고 각각의 메소드를 호출해 본다.
  • 3개의 testXXX() 메소드가 각각 실행되면서 Aspect가 적용된 결과가 콘솔에 보여질 것이다.
public class LogAspectTest extends junit.framework.TestCase{
	
	private Printer printer;
	
	protected void setUp() throws Exception {
		AbstractApplicationContext context = 
                   new ClassPathXmlApplicationContext("edu/aspect/ac-aspect.xml");
		printer = (Printer) context.getBean("printer");
		super.setUp();
	}
	
	public void testPrint() {		
		printer.print();
	}
	
	public void testPrintMessage(){
		printer.printMessage("No~");
	}
	
	public void testPrintMessateAndReturn(){
		printer.printMessateAndReturn("Yes~");
	}
}
  • 실행 결과는 아래와 같다.
Thu May 22 21:41:29 KST 2008 : edu.aspect.Printer.print method is executed.
print is executed.
Thu May 22 21:41:29 KST 2008 : edu.aspect.Printer.printMessage method is executed.
print is executed. Message is No~
Thu May 22 21:41:29 KST 2008 : edu.aspect.Printer.printMessateAndReturn method is executed.
print is executed. Message is Yes~It will be returned.

그림:information.gif