7. AOP 프로그래밍 #3 스프링 AOP 구현

2021. 7. 27. 17:32JAVA/Spring

본 글은 초보 웹 개발자를 위한 스프링5 프로그래밍 입문 도서의 내용을 복습하기 위해 작성된 글입니다.

7.3 스프링 AOP 구현

스프링 AOP를 이용한 공통 기능 구현 방법

  1. Aspect로 사용할 클래스에 @Aspect 애노테이션을 붙인다.
  2. @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다.
  3. 공통 기능을 구현한 메서드에 @Around 애노테이션을 적용한다.

 

3.1 @Aspect, @Pointcut, @Around를 이요한 AOP 구현

공통 기능을 제공하는 Aspect 구현 클래스를 만들고 자바 설정을 이용해서 Aspeect을 어디에 적용할지 설정하면 된다. Aspect는 @Aspect 애노테이션을 이용해서 구현한다. 프록시는 스프링 프레임워크가 알아서 만들어준다.

 

실행시간을 측정하는 Aspect(공동 기능 제공)

package aspect;



import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect	// 공통 기능 정의
public class ExeTimeAspect {
	@Pointcut("execution(public * chap07..*(..))")	// 공통기능 적용대상 설정, public으로 시작하는 메소드
	public void publicTarget()
	{
		
	}
	
	/**
	 * getSignature() : 호출한 메서드의 시그니처
	 * getTarget() : 대상 객체
	 * getArgs() : 인자 목록
	 * 
	 * @param joinPoint : 프록시의 대상 객체
	 * @return
	 * @throws Throwable
	 */
	@Around("publicTarget()")
	public Object measure(ProceedingJoinPoint joinPoint) throws Throwable
	{
		long start = System.nanoTime();	// 공통 기능의 예시, 시간측정
		try {
			Object result = joinPoint.proceed();	// 실제 대상 객체의 메서드 호출
			return result;
		}finally {
			long finish = System.nanoTime();
			Signature sig = joinPoint.getSignature();
			System.out.printf("%s.%s(%s) 실행 시간 : %d ns\n",
								joinPoint.getTarget().getClass().getSimpleName(),
								sig.getName(),Arrays.toString(joinPoint.getArgs()),
								(finish-start));
		}
		
		
	}
}
  • @Aspect 애노테이션 : @Aspect 애노테이션을 적용한 클래스는 Advice와 Pointcut을 함께 제공한다.
  • @Pointcut 애노테이션 : @Pointcut은 공통 기능을 적용할 대상을 설정한다.
  • @Around 애노테이션 : Around Advice를 설정한다. @Around 애노테이션의 인자값이 "publicTarget()"인데 이는 publicTarget() 메서드에 정의한 Pointcut에 공동 기능을 적용한다는 것을 의미한다.
  • ProceedingJoinPoint 타입 파라미터 : 프록시의 대상 객체
  • joinPoint.proceed() : 실제 대상 객체의 메서드를 호출

 

스프링 설정 클래스 구현

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import aspect.ExeTimeAspect;
import chap07.Calculator;
import chap07.RecCalculator;

/**
 * @Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 @EnableAspectJAutoProxy 애노테이션 설정 필수
 */
@Configuration
@EnableAspectJAutoProxy
public class AppCtx {
	@Bean
	public ExeTimeAspect exeTimeAspect() {
		return new ExeTimeAspect();
	}
	
	@Bean
	public Calculator calculator()
	{
		return new RecCalculator();
	}
}
  • @EnableAspectJAutoProxy 애노테이션 : @Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 스프링 설정 클래스(AppCtx)에 설정해야 한다. 이 애노테이션을 추가하면 스프링은 @Aspect 애노테이션이 붙은 빈 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정을 사용한다.
package main;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import chap07.Calculator;
import chap07.RecCalculator;
import config.AppCtx;

public class MainAspect {
	public static void main(String args[])
	{
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		// 수정 전
		Calculator cal = ctx.getBean("calculator", Calculator.class);
        
		long fiveFact = cal.factorial(5);
		System.out.println("cal.factorial(5) = " + fiveFact);
		
		System.out.println(cal.getClass().getName());
		ctx.close();
	}
}
RecCalculator.factorial([5]) 실행 시간 : 13035300 ns
cal.factorial(5) = 120
chap07.RecCalculator$$EnhancerBySpringCGLIB$$e0993ab

실행결과의 첫번째 줄은 ExeTimeAspect 클래스의 measure() 메서드가 출력한 것이다.

 

3.2 ProceedingJoinPoint의 메서드

Around Advice에서 사용할 공통 기능 메서드는 대부분 파라미터로 전달받은 ProceedingJoinPoint의 proceed() 메서드만 호출하면 된다.

	public Object measure(ProceedingJoinPoint joinPoint) throws Throwable
	{
		long start = System.nanoTime();	// 공통 기능의 예시, 시간측정
		try {
			Object result = joinPoint.proceed();	// 실제 대상 객체의 메서드 호출
			return result;
		}finally {
			long finish = System.nanoTime();
			Signature sig = joinPoint.getSignature();
			System.out.printf("%s.%s(%s) 실행 시간 : %d ns\n",
								joinPoint.getTarget().getClass().getSimpleName(),
								sig.getName(),Arrays.toString(joinPoint.getArgs()),
								(finish-start));
		}
		
		
	}

 

ProceedingJoinPoint 인터페이스의 제공 메서드

  • Signature getSignature() : 호출되는 메서드에 대한 정보를 구한다
  • Object getTarget() : 대상 객체를 구한다
  • Ojbect[] getArgs() : 파라미터 목록을 구한다.

org.aspectj.lang.Signature 인터페이스의 제공 메서드

  • String getName() : 호출되는 메서드의 이름을 구한다.
  • String toLongString() : 호출되는 메서드를 완전하게 표현한 문장을 구한다(메서드의 리턴 타입, 파라미터 타입이 모두 표시된다)
  • String toShortString() : 호출되는 메서드를 축약해서 표현한 문장을 구한다(기본 구현은 메서드의 이름만을 구한다.)

 

References

초보 웹 개발자를 위한 스프링5 프로그래밍 입문, 최범균 저