From JCFWiKi
|
|
- 산출물 : Spring AOP 이해하기
- 작성자: 김민아
- 최초작성일 : 2008//05/21
Copyright © 2008 Daewoo Information Systems Co., Ltd.
|
[편집] 객체지향프로그래밍의 문제
- Concern
- 다수의 객체들간에 널리 퍼져서 존재하는 Concern들을 어떻게 관리할 것인가
- 이 Concern들은 프로그램을 복잡하게 만들고, 코드의 변경을 어렵게 한다.
[편집] AOP(Aspect Oriented Programing)란?
- 객체 지향 개발을 보완
- 다양한 객체들에 분산되어 중복적으로 존재하는 공통 관심 사항(Concern)들을 묶어서 모듈화하여 Aspect들을 분리하고
- 이렇게 분리된 Aspect 기반으로 개발
- 위 그림을 예로 들면 로그처리, 보안처리, 트랜젝션 처리는 모든 로직에서 필요한 부분이다.
- 객체 지향 관점에서의 개발은 Student Service, Cource Sevice...등등 로직마다 로그처리, 보안처리, 트랜젝션 처리를 일일이 해주어여한다.(위)
- 하지만 AOP라는 개념을 활용하면 로그처리, 보안처리, 트랜젝션 처리 모듈을 별도로 만들어 weaving이라는 개념을 사용하여 Student Service, Cource Sevice...등등 로직이 처리될 때마다 로그처리, 보안처리, 트랜젝션 처리를 적용되도록 수 있다.(아래)
[편집] AOP의 주요 개념/용어
[편집] Aspect
[편집] Joinpoint
- 프로그램이 실행되는 동안의 Aspect가 수행될 지점
- 다양한 객체들에 존재하던 Concern의 실제 위치
[편집] Advice
[편집] Pointcut
- Jointpoint의 묶음으로서 Advice는 pointcut 단위로 적용된다.
[편집] Introduction
- Jointpoint를 가지는 클래스에 메소드나 필드들을 추가하는 행위
[편집] Weaving
- Aspect들을 Advice될 대상 객체들와 엮어주는 행위
- 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 정의
|
<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.