내 블로그 목록

2018년 7월 25일 수요일

[SpringFramework] 스프링 AOP편

Q. 스프링 프레임워크에서 AOP 개념이 있다는 말을 들었다. AOP가 무엇인가?


AOP(Aspect Oriented Programming)는 쉽게 말해 ‘모듈화로 중복 제거’이다.


핵심 부분이나 자주 사용되는 부분을 따로 빼서 모듈화한다. 사용하고 싶을 때 마다 이 모듈을 불러와
사용하면 중복 제거도 되고 훨씬 간편하다.


예를 들어, 은행 업무 로직을 보자.


이해를 돕기 위해 은행 업무를 단순화했다. 은행 업무는 계좌이체 / 입출금 / 이자계산이다. 그런데 각
업무에서 로깅, 보안, 트랜잭션 부분이 반복된다. 로깅, 보안, 트랜잭션을 따로 빼서 모듈화한다. 이것
이 AOP 이다.



Q. AOP, Aspect Oreinted Programming을 해석하자면, ‘관점 지향 프로그래밍’이다. 왜 이름을 이렇
게 지었는가?


왜냐하면 모듈의 입장으로 관점을 옮기기 때문이다. 본인이 어떤 부분이 핵심이라고 생각하거나 자주
사용하겠다 싶은 부분을 정해서 이를 모듈로 만든다고 앞서 말했다.



Q. AOP를 사용하는 이유는 무엇인가?


핵심 부분을 모듈화하면 생산성이 향상되고 유지보수가 쉬워진다. 왜냐하면 중복을 제거하면 차지하
는 메모리가 줄어들고, 메모리가 줄어들면 속도가 빨라진다. 또한 모듈화 되어 한 덩어리 한 덩어리로
존재하기 때문에 수정할 부분을 쉽게 찾을 수 있다.


그리고 무엇보다 복잡한 코드들을 정리할 수 있다. 나는 organized person이기 때문에, 정리된 것을
좋아한다. 예를 들어, 지저분한 try/catch/finally를 모듈화 시키면 훨씬 깔끔하지 않겠는가.



Q. AOP에서 사용되는 용어는 어렵다고 알고 있다. 그게 사실인가? 용어에 대해서 설명해달라.


용어의 뉘앙스가 어려운 것이지 의미를 알고보면 그다지 어렵지 않다.


먼저)
Weaving(엮는다): Weaving은 쉽게 말해 ‘모듈 적용하기’이다. 이는 모듈화한 핵심 부분을 끌어와 사
용하는 것을 말한다. 천(1)에 구멍이 나면 다른 천(2)으로 덧대지 않는가.  이때 (2)천에 해당하는 것이
모듈이며, (1)천은 클래스이다.


Weaving은 스프링에서 xml의 설정을 통해서 처리한다.


Pojo: Plain Old java Object. 클래스는 Pojo 형태로 만든다. Pojo 형태가 무엇인가하면 클래스를 상속
하거나 인터페이스를 구현하지 않으며, 애노테이션 사용을 강제하지 않는 형태이다. 즉, 다른 클래스,
라이브러리, 컨테이너 기술에 종속적이지 않기 때문에 독립적인 형태라고 할 수 있다.
이 Pojo 형태는 독립적이기 때문에 어디든지 적용할 수 있다.  이 형태를 Pojo라고 한다.
이는 모듈의 특성 중 하나이다.


Pojo의 코드가 무엇인지 쉽게 예를 들자면, model 패키지에서 getter/setter로 만들었던 클래스. 그것
이 Pojo 형태라고 할 수 있다.


Proxy: 스프링에서 모듈은 Pojo 형태인  Proxy 기반으로 만들어 지는데, 이 구조는 추후에 배우도록 하겠다.


일일이 정리하기 힘들었기에 용어 정리를 불펌해서 필요한 부분들을 추가했다.


Concern
– 애플리케이션을 개발하기 위하여 관심을 가지고 구현 해야 하는 각각의 기능
들을 관심 사항(Concern)이라 함


core concern
– 핵심 관심 사항
– 해당 애플리케이션이 제공하는 핵심이 되는 비즈니스 로직 의미


cross-cutting concern
– 횡단[공통] 관심 사항
– 하나의 영역에서만 활용되는 고유한 관심 사항(Concern)이 아니라, 여러 클래
스 혹은 여러 계층의 애플리케이션 전반에 걸쳐서 공통적으로 필요로 하는 기
능들 의미

Target
핵심 로직을 구현하는 클래스 공통 관심 사항을 적용 받게 되는 대상으로
어드바이스가 적용되는 객체. Concern(=모듈)을 사용하는 클래스.


Aspect
여러 객체에 공통으로 적용되는 공통 관심 사항


Advice(설정) - 모듈이라고 할 수 있다.
조인 포인트에 삽입되어 동작할 수 있는 공통 관심 사항의 코드


*동작시점 : Spring에서는 조인포인트 실행 전, 후로 before, after, after
returning, after throwing, around*로 구분
*before,after 보다 around를 많이 사용함.


joinpoint
「클래스의 인스턴스 생성 시점」,
메소드 호출 시점」* 및 「예외 발생 시점」
과 같이 애플리케이션을 실행할 때 특정 작업이 시작되는 시점으로
Advice를 적용 가능한 지점, 즉 어드바이스가 적용될 수 있는 위치


*/시작/메소드 호출 시점/종료/


Pointcut
여러 개의 Joinpoint를 하나로 결합한(묶은) 것


Advisor
Advice와 Pointcut를 하나로 묶어 취급한 것. = 코드 + 실행위치


weaving*
공통 관심 사항의 코드인 Advice를 핵심 관심 사항의 로직에 적용 하는
것을 의미.


*weaving은 xml 설정을 통해서 처리한다. Advice(사용될 모듈)과 사용할 위치(which method in which class)를 xml에서 설정한다.

정리하자면 모듈은 Advice이고 모듈을 사용하는 클래스는 Target이다.
스프링에서 Advice는 Proxy구조로 만들어지고, Proxy구조는 Pojo형태로, 아무런 클래스 라이브러리
등에 영향을 받지 않는 독립적인 형태이다.


Q. Weaving을 왜 두번이나 설명했는가. 그렇게 중요한 부분인가.


Weaving은 중요하다. 왜냐하면 xml 설정 처리를 해야하기 때문이다.


Advice를 위빙(Weaving) 하는 방식은 3 가지가 있기 때문이다.




Spring은 자체적으로 런타임 시에 위빙하는 “프록시 기반의 AOP”를 지원한다. 그래서 3가지 위빙 방
식 중에 ‘런타임 시 위빙하기’를 잘 봐두면 된다.


스프링에서 런타임 시 위빙하기를 적용하는 것에는 주의할 점이 있다.


(1) 중간에 코드 수정이 안된다.
(2) 메서드가 실행될 때 적용된다.
(3) 공통 코드들은 Proxy 형태로 구현해야한다.




Q. Proxy를 설명해달라.


프록시(Proxy) 단어 그 자체의 의미는 ‘대리인’이다.


쉽게 말하면 Proxy는 스프링에서 Advice를 만들기 위해 사용하는 구조이다.


프록시 구조는 이렇게 생겼다


:before - (1)
메서드  - (2)
:after - (1)


뭐, 설명은 다음과 같다. 모듈화 시킨 코드를 사용하겠다. 나는 (2)메서드에서 사용한다. 그럼 (2)메서
드를 pinpoint하는데 이때 pinpoint하는 것을 jointcut이라고 한다. 그런데 이 코드를 메서드 앞에서 사
용할 건가 뒤에서 사용할 건가를 구체적으로 정해줘야 하지 않는가. 이를 <aop:around>,
<aop:before>, <aop:after> 등에서 지정해준다.


위 구조와 같은 것. (1)(2)(1)로 생겨 먹은 것. 이것이 프록시 구조이다.

Q. AOP의 설정 과정을 설명해달라.




자, 예제로 알아보도록 하겠다.


    class ExeTimeAspect = Advice 클래스(Proxy 구조로 만들어 졌다).
    class ProceedingJoinPoint = 내부 객체 클래스.


  1. ExeTimeAspect class를 xml에 으로 등록<bean>한다. 등록을 할 때는 모듈을(<aop:config>) 어떤 클래스의 어떤 메서드(<aop:asepct>, <aop:pointcut>)에서 사용할 것인가도 등록한다.
  2. ProceedingJoinPoint 안에 있는 proceed() 메서드(=핵심로직의 메서드)를 불러와 실행한다. 이는 내부 객체에 있는 내부 메서드이다.


*참고로 System.nanoTime(); 내부 메서드는 System.currentTimeMillis(); 내부 메서드보다 더욱 정교한 시간을 반환한다.


다음은 Advice를 xml 파일에 등록한다.



*<aop:aspect id="measureAspect" ref="exeTimeAspect">
어느 어드바이스 클래스를 사용할 것인가 설정하는 부분.

* <aop:pointcut id="publicMethod" expression="execution(public * test..*(..))" />
어드바이스 클래스에 어떤 메서드에서 실행할 것인가. 시점을 정하는(포인트 컷) 정하는 부분.


aop:pointcut은 어느 시점에서
expresseion = “execution(A)” 가로 안에 있는 식은 메서드를 정의하는 것이다.


(public * test..*(..))
을 해석하자면


public 타입의
*모든 타입의 패키지 중에
test.. test 패키지 안의 하위 모든 클래스를 대상으로
(*  그 안에 포함되어 있는 것들 중에 반환타입이 뭐든지 상관없고
(..) 매개 변수의 개수가 0개 이상인


주로 execution안에 오는 문법은 다음과 같다. 이는 추후에 구체적으로 다루도록 하겠다.
‘execution([접근 제한자 패턴] 타입패턴 [타입패턴.] 이름패턴 (타입패턴 | “..”, …) ‘

*<aop:around pointcut-ref="publicMethod" method="measure" />
어드바이스를 메서드 앞부분에서 실행할 것인가 뒷부분에서 실행할 것인가를 정해준다.

*advice와 aspect는 여러개 존재 가능하다.

Q. 구체적인 예시를 들어달라.


프로젝트를 생성한다.


프로젝트를 생성할 때는


(1) Lib 버전 1.8로 변경
(2) Complier 버전 1.8로 변경
(3) pom.xml dependency에 aspectjweaver 추가


를 먼저 꼭 실행해야 한다.


(1)/(2)는 Buildpath로 하면 되니 설정 방법을 생략하겠다.
(3)은


pom.xml 소스에서
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.2</version>
</dependency>
를 등록 하거나


Namespace에서 ‘aop’를 클릭 한 후 저장한다.


자, 그럼 이제 프로젝트가 만들어졌다.


앞선 예제를 차례차례 실행해 보겠다.




구조는 이렇다.


먼저 인터페이스를 만들어 준다.


[Calcurator.java]
package test;
public interface Calcurator {
public long factorial(long num);
}

인터페이스를 구현한다. 구현 클래스는 두 가지 이다.


[RecCalcurator.java]
package test;
public class RecCalcurator implements Calcurator {
@Override
public long factorial(long num) {
if(num == 0) {
return 1 ;
}else {
return num*factorial(num -1);
}
}
}

[CalcuratorImp.java]
package test;
public class CalcuratorImp implements Calcurator{
@Override
public long factorial(long num) {
long result = 1;
for(int i=1; i<= num; i++) {
result*=i;
}
return result;
}
}


Advice 클래스를 만들어 준다. 당연히 구조는 프록시다.


[ExeTimeAspect.java]


package aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
//Advice 클래스
public class ExeTimeAspect {
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
// 공통되는 코드 : 시작 시간
long startTime = System.nanoTime();
try {
// 핵심 코드 실행
Object result = joinPoint.proceed();  //핵심 로직의 메서드 실행 요청
return result;
}finally {
// 공통되는 코드 : 종료 시간
long endTime = System.nanoTime();
Signature signature = joinPoint.getSignature(); //객체 받기
System.out.printf("%S.%S 실행 시간 : %d ns\n", joinPoint.getTarget().getClass().getSimpleName(),
signature.getName(), (endTime - startTime));
//signature.getName(): 메서드 이름 가져오기
}
}
}

이것은 xml에 등록해준다.


[appCtx.xml]
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<bean id="recCal" class="test.RecCalcurator"/>
<bean id="calImp" class="test.CalcuratorImp"/>
<bean id="exeTimeAspect" class="aspect.ExeTimeAspect"/>
<aop:config>
<aop:aspect id="measureAspect" ref="exeTimeAspect">
<!-- 포인트 컷을 어느 메서드로 정할 것이니 -->
<aop:pointcut id="publicMethod"
expression="execution(public * test..*(..))"/>
<!-- 포인트 컷의 어느 부분에서(시작/끝) 사용할 것이니 -->
<aop:around method="measure" pointcut-ref="publicMethod"/>
</aop:aspect>
</aop:config>
</beans>


등록이 끝나면, 빈들을 사용할 클래스를 만들어 준다.


[AopMain.java]
package main;
import org.springframework.context.support.GenericXmlApplicationContext;
import test.Calcurator;
public class AopMain {
public static void main(String[] args) {
GenericXmlApplicationContext context = new GenericXmlApplicationContext("classpath:aopCtx.xml");
Calcurator c1 = context.getBean("recCal", Calcurator.class);
// 상위 클래스(인터페이스)로 불러와도 상관없다.
long f1 = c1.factorial(10);
System.out.println("RecCalcurator.factorial(10): " +f1);
System.out.println("----------------------------------");
Calcurator c2 = context.getBean("calImp", Calcurator.class );
long f2 = c2.factorial(10);
System.out.println("CalcuratorImp.factorial(10): " +f2);
}
}









Q. pointcut의 expression 표현식을 구체적으로 알려준다 했지 않는가?








댓글 없음:

댓글 쓰기