[스프링SPRING]관점 지향(AOP, Aspect-Oriented Programming) 총정리

[스프링SPRING]관점 지향(AOP, Aspect-Oriented Programming) 총정리

관점 지향 (AOP, Aspect-Oriented Programming)

  • 메서드나 객체 안에 주기능과 보조기능으로 분리한 후 선택적으로 적용해서 사용하는 방법
  • 전체 코드에 흩어져있는 보조 기능들을 한 곳에 모아서 처리가능하다
  • 필요시(=주기능이 사용될때마다) 선택적으로 보조기능을 사용할 수 있다.
  • 예시 : 쇼핑몰개발자는 쇼핑몰 정보를 처리만 하고 보안처리는 spring 프레임워크가 하는 것
  • 장점 : 코드가 단순해지면서 가독성 향상, 중복된 코드 제거.
  • 사용처: 로깅, 보안, 트랜젝션처리시 사용




AOP용어

AOP용어 설명 특징
Target Advice가 적용되는 클래스/메서드 -
Advice Aspect의 실체, 구현해놓은 클래스 메서드호출기준으로 여러 지점에서 호출 가능
Pointcut Jointpoint의 부분으로 실제로 Advice가 적용된 대상, JointPoint의 상세한 스펙을 정의한 것 패키지이름/클래스이름/메서드이름을 정규식으로 지정 후 처리함
Jointpoint Advice가 적용될 위치, 끼어들 수 있는 지점 스프링에서는 method 결합점만 제공
Aspect 공통기능, 구현하고자 하는 보조 기능 -
Weaving(위빙) Advice를 핵심기능에 적용하는 행위 -




스프링API를 활용한 AOP구현방법

  1. Target 클래스 지정
  2. Advice 클래스 지정
  3. 설정파일(스프링프레임워크)에서 Pointcut을 지정
  4. 설정파일(스프링프레임워크)에서 Advice와 Pointcut을 결합하는 Advicer 생성
  5. 설정파일(스프링프레임워크)에서 ProxyFactoryBean(스프링에서 제공)클래스를 사용해서 Target에 Advice를 적용
  6. getBean()메서드로 해당 bean(객체)접근해서 사용




스프링API에서 제공하는 Advice인터페이스

인터페이스 이름 추상메서드 이름 특징
MethodBeforeAdvice before() 주 메서드 실행하기 전에 실행. 예를 들면 내 주기능이 로그인이다. 로그인전에 보안처리나 로깅처리를 실행하고자할때 내 주기능 실행전에 실행하는 것
AfterReturningAdvice afterReturning() 주 메서드 실행 후 실행
ThrowsAdvice afterThrowing() 주 메서드에서 예외가 발생시 실행
MethodInterceptor invoke() 주 메서드의 실행 전/후에 실행(예외발생시 실행도 포함)




예시 : 계산기 만들기

간단한 계산기 예제를 통해서 AOP를 알아보자

  • Calculator.java 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Calculator {
//계산기객체 target

public void add(int x, int y){
int result = x + y;
System.out.println("add 결과: "+result);
}

public void sub(int x, int y){
int result = x - y;
System.out.println("sub 결과: "+result);
}

public void mul(int x, int y){
int result = x * y;
System.out.println("mul 결과: "+result);
}

public void div(int x, int y){
int result = x / y;
System.out.println("div 결과: "+result);
}
}




target 객체생성

advice 객체생성

위에서 학습한 순서에 따라 객체 생성해보자.
1번과 2번을 함께 xml파일에 생성

  • AOPTest.xml생성
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>

<!-- DTD -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<!-- BEAN -->
<beans>
<!--1.타겟 클래스 객체생성 -->
<bean id="calcTarget" class="com.itwill.aop.Calculator" />

<!--2.로그기능을 처리하는 advice 객체생성 -->
<bean id="logAdvice" class="com.itwill.aop.LoggingAdvice" />
</beans>




설정파일(스프링프레임워크)에서 Pointcut을 지정

설정파일(스프링프레임워크)에서 Advice와 Pointcut을 결합하는 Advicer 생성

3번 4번을 함께 java파일에 작성하자
스프링API에서 제공하는 Advice인터페이스 중 MethodInterceptor를 구현해보았다.

  • LoggingAdvice.java 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

//로그를 기록하는 로강클래스
public class LoggingAdvice implements MethodInterceptor {

//주 메서드의 실행 전/후에 실행(예외발생시 실행도 포함)하는 메서드 오버라이딩
@Override
public Object invoke(MethodInvocation inv) throws Throwable {
//메서드 호출 전에 수행하는 구문
System.out.println(inv.getMethod()+" 메서드 호출 전");

//메서드 호출
Object obj = inv.proceed();

//메서드 호출 후에 수행하는 구문
System.out.println(inv.getMethod()+" 메서드 호출 후");

return obj;
}
}




설정파일(스프링프레임워크)에서 ProxyFactoryBean(스프링에서 제공)클래스를 사용해서 Target에 Advice를 적용

  • AOPTest.xml 추가 작성 (주석 5번내용)
  • property의 name이 궁금해져서 ProxyFactoryBean공식문서를 찾아봤다
    • interceptorNames는 setInterceptorNames()를 통해서 의존 주입(Setter)을 하였다.
    • 하지만 target의 경우 동일한 파라미터이름을 찾을 수 없었다. 하지만 그 비슷한 것을 찾았는데 setTargetName(String targetName)이다. name="targetName"으로 하면 error가 발생한다.
    • 데이터타입이 object인데 targetName의 데이터타입은 String이기때문이다. 그럼 어떤 메서드에 의해서 의존주입이 되는 것일까? 부모클래스인 Class AdvisedSupport의 setTarget(Object target)
    • 따라서 property 속성을 꼭 name="target"으로 지정해야 스프링이 해당 클래스가 target클래스인 걸 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>

<!-- DTD -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<!-- BEAN -->
<beans>
<!--1.타겟 클래스 객체생성 -->
<bean id="calcTarget" class="com.itwill.aop.Calculator" />

<!--2.로그기능을 처리하는 advice 객체생성 -->
<bean id="logAdvice" class="com.itwill.aop.LoggingAdvice" />

<!--5.ProxyFactoryBean(스프링에서 제공)클래스를 사용해서 Target에 Advice를 적용 -->
<bean id="proxyCalc" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--5-1.타켓클래스 지정(setTargetName메서드를 이용한 setter 의존주입) -->
<property name="target" ref="calcTarget" />
<!--5-2.타겟클래스에서 메서드 호출시 logAdive객체 실행(setter 의존주입) -->
<property name="interceptorNames">
<list>
<value>logAdvice</value>
</list>
</property>
</bean>
</beans>




getBean()메서드로 해당 bean(객체)접근해서 사용

  • CalcAOPTest.java생성 후 getBean()사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class CalcAOPTest {

public static void main(String[] args) {
System.out.println(" 계산기 실행 ! ");

ApplicationContext appCtx = new ClassPathXmlApplicationContext("AOPTest.xml");

System.out.println("-----------target 객체생성");
Calculator cal2 = (Calculator)appCtx.getBean("calcTarget");
cal2.add(200, 200);

System.out.println("-----------Proxy로 객체생성");
Calculator cal = (Calculator)appCtx.getBean("proxyCalc");
cal.sub(100, 200);
cal.mul(5, 2);
}
}

로그를 통해 정상적으로 객체를 생성하고 호출하는 것을 확인할 수 있다.