강좌/Spring 3.0

010. AOP 일단 덤벼 보자 - 설명편

여름나라겨울이야기 2013. 1. 23. 12:17
728x90

지난 009 강좌를 통해 횡당관심사항을 완벽하게 분리하는 예제를 실습해 보았는데요.

이번 강좌에는 이론적인 토대를 마련해 보도록 하겠습니다.

 

AOP 적용 전후의 Boy.java 와 관련 코드를 비교해 보도록 하겠습니다.

 

AOP 적용 전

AOP 적용 후

package aop001;

public class Boy {
 public void housework() {
  System.out.println("열쇠로 문을 열고 집에 들어간다.");
  
  try {
   System.out.println("컴퓨터로 게임을 한다.");
  } catch (Exception ex) {
   if(ex.getMessage().equals("집에 불남")) {
    System.out.println("119 에 신고한다.");
   }   
  } finally {
   System.out.println("소등하고 잔다.");
  }
  
  System.out.println("자물쇠를 잠그고 집을 나선다.");
 }
}

package aop002;

public class Boy implements IPerson {
 public void housework() {
  System.out.println("컴퓨터로 게임을 한다.");
 }
}

package aop002;

public interface IPerson {
 void housework();
}

package aop002;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
 @Before("execution(public void aop002.Boy.housework())")
 public void before(JoinPoint joinPoint){
  System.out.println("얼굴 인식 확인: 문을 개방하라");
  //System.out.println("열쇠로 문을 열고 집에 들어간다.");
 }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 <aop:aspectj-autoproxy /> 
 
 <bean id="myAspect" class="aop002.MyAspect" />
 <bean id="boy" class="aop002.Boy" />
</beans>

 

흠.. 일단 System.out.println("열쇠로 문을 열고 집에 들어간다."); 만 AOP 로 구현했음에도 코드의 양이 상당히 늘어났군요. ^^;

 

그런데 주목할 것은 Boy.java 의 코드가 어떻게 되었는가 하는 것입니다.  횡단관심사항들은 모두 사라졌고 오직 핵심관심사항만 남았다는 거죠.  개발할 때는 한 개의 Boy.java 를 4 개의 파일들로 분할해서 개발하는 수고를 해야 하지만 유지보수의 관점에서 보면 무척이나 편한 코드가 되어진 것을 알 수 있습니다.

 

 

 

실행 결과를 보면 @Before 로 만들어진 before 메서드가 실행시간(Runtime)에 위 그림과 같이 주입되는 것을 알 수 있습니다.

 

위 그림을 더욱 상세하게 그려보면 아래와 같습니다(줄 긋기 놀이 시작인가요.. )

 

 

 

더 이상 말이 필요 없으시죠. ^^

 

자 그런데 @Before("execution(public void aop002.Boy.housework())") 부분을 아래와 같이 고쳐 보죠

 

@Before("execution(* housework())")

 

수정 후 저장, 실행해 보셔야 겠죠.  잘 동작하죠?

흠 그렇다고 하는 것은 Girl.java 의 housework() 메서드도 @Before 를 통해서 무언가를 해줄 수 있다는 느낌이 확 오시나요?

 

좀 길기는 하지만 역시 AOP 전후의 Boy.java, Girl.java 와 관련 코드를 살펴보도록 하겠습니다.

 

 

 AOP 적용 전

AOP 적용 후 

package aop001;

public class Boy {
 public void housework() {
  System.out.println("열쇠로 문을 열고 집에 들어간다.");
  
  try {
   System.out.println("컴퓨터로 게임을 한다.");
  } catch (Exception ex) {
   if(ex.getMessage().equals("집에 불남")) {
    System.out.println("119 에 신고한다.");
   }   
  } finally {
   System.out.println("소등하고 잔다.");
  }
  
  System.out.println("자물쇠를 잠그고 집을 나선다.");
 }
}

package aop002;

public class Boy implements IPerson {
 public void housework() {
  System.out.println("컴퓨터로 게임을 한다.");
 }
}

package aop001;

public class Girl {
 public void housework() {
  System.out.println("열쇠로 문을 열고 집에 들어간다.");
  
  try {
   System.out.println("요리를 한다.");
  } catch (Exception ex) {
   if(ex.getMessage().equals("집에 불남")) {
    System.out.println("119 에 신고한다.");
   }   
  } finally {
   System.out.println("소등하고 잔다.");
  }
  
  System.out.println("자물쇠를 잠그고 집을 나선다.");
 }
}

package aop002;

public class Girl implements IPerson {
 public void housework() {
  System.out.println("요리를 한다.");
 }
}

 

package aop002;

public interface IPerson {
 void housework();
}

 

package aop002;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
 @Before("execution(* housework())")
 public void before(JoinPoint joinPoint){
  System.out.println("얼굴 인식 확인: 문을 개방하라");
  //System.out.println("열쇠로 문을 열고 집에 들어간다.");
 }
}

package aop001;

public class Start {
 public static void main(String[] args) {
  Boy romeo = new Boy();
  Girl juliet = new Girl();
  
  romeo.housework();
  juliet.housework();
 }
}

package aop002;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Start {
 public static void main(String[] args) {
  ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/aop002/expert.xml");
  
  IPerson romeo = (IPerson)context.getBean("boy");
  IPerson juliet = (IPerson)context.getBean("girl");

  romeo.housework();
  juliet.housework();
 }
}

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 <aop:aspectj-autoproxy /> 
 
 <bean id="myAspect" class="aop002.MyAspect" />
 <bean id="boy" class="aop002.Boy" />
 <bean id="girl" class="aop002.Girl" />
</beans>

 

 

하나씩 살펴보죠.

Boy.java 와 Girl.java 에서 초록색 부분이 사라지고 빨간색 부분이 추가 되었습니다.

초록색 부분이 사라진 이유는?

빨간색 부분이 추가된 이유는?

고민 좀 해보시고 답을 보시길...(마우스로 아래 빈 곳을 drag 하시면 답이 보입니다)

 

초록색 부분이 사라진 이유는 횡단관심사항이기 때문입니다.

빨간색 부분이 들어온 이유는 스프링 AOP 가 interface 기반으로 작동하기 때문에 그 요건을 충족하기 위해서 입니다.

물론 CGLiB 를 사용하게 되면 interface 없이도 AOP 가 적용가능하지만 추천하는 방법이 아닙니다.

CGLiB 를 사용해야 할 경우는 소스코드를 변경할 수 없는 서드파티모듈이 final 등으로 선언된 경우 정도인데 관심있는 분은 책에서 더 많은 정보를.. 여긴 초급이라구요.. ^^;

 

IPerson.java 이게 새롭게 나온 이유는?  역시 마우스로 빈 곳 드레그 하시면 답이 나옵니다.

 

바로 위에 설명되어 있잖아요. ㅡㅡ;

스프링 AOP 는 무엇 기반이다?

 

MyAspect.java 가 들어온 이유는?

 

Boy.java 와 Girl.java 에서 공통적으로 나타나는 횡단관심사항을 Boy.java 와 Girl.java 에서 모두 삭제해 버렸지만 결국 누군가는 횡단 관심 사항을 처리 해 줄 필요가 있겠죠.  그것을 Aspect 라 부르는데 바로 MyAspect.java 가 그 역할을 담당하게 됩니다.

 

Start.java 가 변경된 이유는 당연히 스프링프레임워크를 활용하기 위한 코드니까 변경되는 건 당연하겠죠.

 

expert.xml 은 조금 더 세밀하게 살펴보죠.

bean 3 개가 설정되어 있습니다.  bean이 설정되는 이유는 객체의 생성을 스프링프레임워크가 관여하기 위한 필수조건이죠.

그래서 당연히 3 개의 bean 에 대한 정보를 등록했습니다.

(물론 스프링프레임워크는 객체 생성 뿐만 아니라 객체의 생명주기 전반에 걸쳐서 관여하기는 합니다.)

boy bean 과 girl bean 은 AOP 의 대상이기에 등록될 필요가 있었고

myAspect bean 은 AOP 의 Aspect 이기에 등록될 필요가 있는 겁니다.

 

그럼 <aop:aspectj-autoproxy /> 은 뭘까요?

이름에서 보면 aspect 는 알겠고 그 뒤에 j 는 java 인듯 하고 auto 는 자동이고 proxy 는..?

 

Proxy 는 사전을 찾아보면 대변인, 대변자라고 나오는군요.  각 정당에는 그 정당의 입장을 대변인이 발표하죠. 그 정당을 대신해서요. 감이 오시나요?

 

그림으로 설명하는 것이 빠르겠네요.

일단 Proxy 없이 Start.java 의 main() 메소드에서 remeo 객체의 homework() 메서드를 호출하는 그림을 보면 다음과 같습니다.

 

 

 

오랜만에 보는 객체 다이어그램입니다.  UML 을 모르셔도 어느 정도 감이 오시죠. ^^

그럼 이번에는 Proxy 를 통한 호출 그림입니다.

 

 

 

호출하는 쪽에서는 remeo.homework() 을 호출하면 그 앞에 proxy(대변인)가 그 요청을 받아 진짜 remeo 객체에게 요청을 전달하게 됩니다.  그럼 빨간색의 homework() 는 그냥 전달만 할까요?  아니죠.. 여러분도 누군가에게 보내지는 선물을 중간에 전달하는 입장이라면 장난끼가 가득한 유치함을 발휘하고 싶은 생각이 드시지 않나요? 저만 그런가.. ^^;

결론적으로 빨간색  homework() 는 중간에서 주고 받는 내용을 감시하거나 조작할 수 있다는 것인데요.

Spring AOP 는 바로 이렇게 Proxy 를 사용하게 되는 것이죠.  그런데 Spring AOP 에서 재밌는 것는 호출하는 쪽(remeo.homework() 메소드 호출)에서나 호출당하는 쪽(remeo 객체), 그 누구도 Proxy 가 존재하는지 조차 모른다는 거죠.  오직 스프링 프레임워크만이 그 존재를 알게 되는 것입니다.  사실 여러분이 쓰고 있는 컴퓨터 세상에도 여러분 모르게 다양한 Proxy 들이 사용되고 있답니다.

혹시 Buffer 의 개념을 알고 계신 분은 그와 유사하다고 보셔도 크게 지장은 없습니다.  존재 목적은 다르지만 하는 역활이 중간에서 가로채는 것이라는 동일한 일을 하고 있으니까요.

 

결국, <aop:aspectj-autoproxy /> 는  Spring Framework 에게 AOP Proxy 를 사용하라고 알려주는 지시자가 되겠습니다.  그것도 auto 가 붙었으니 자동으로 말입니다.

 

길었는데 한번 쉬고 넘어가셔야겠죠.  스프링 AOP 를 이해하기 위한 중요한 내용임으로 이 강좌를 10번 읽으시고, 또 다른 강좌와 책 등을 통해서 끝까지 이해하시고 넘어가셔야 합니다.  핵심은 뭐다?

 

Spring AOP 는 interface 기반이다.

Spring AOP 는 Proxy 기반이다.

Spring AOP 는 runtime 기반이다.

 

이번까지는 @Before, 즉 메소드 호출 전에 Proxy 가 개입하도록 했는데요.  짐작하셨겠지만 @After 있습니다.

무려 5가지나 있습니다.  그건 다음 다음 강좌로.. ^^

 

아 조건은 기억 나시죠? 댓글 1개 이상일 경우 다음 강좌 공개..

 

그럼 마라나타...


반응형