본문 바로가기
Java/[스프링 5 프로그래밍 입문]

[Spring] AOP 프로그래밍(2) - 스프링에서의 AOP

2021. 8. 12.

AOP는 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다. 스프링에서는 프록시 객체를 자동으로 만들어주며 공통 기능을 삽입하는데, 여기서 공통 기능을 Aspect라고 한다. 따라서 공통 기능을 구현한 클래스만 알맞게 구현하면 된다.

 

스프링과 AOP

이전 포스트에서는 프록시의 개념과 AOP와의 관계에 대해 알아보았다. 이번 포스트에서는 스프링에서 실제로 AOP가 어떻게 적용되는지 알아볼 것이다.

 

[Spring] AOP 프로그래밍(1) - 프록시와 AOP

AOP는 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다. 핵심 기능에 공통 기능을 삽입하여 핵심 기능의 코드를 수정하지 않고도 공통 기능의

dct-wonjung.tistory.com

 

AOP는 핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가한다. 이를 위한 방법은 세 가지가 있다.

  1. 컴파일 시점에 코드에 공통 기능을 삽입
  2. 클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입
  3. 런타임에 프록시 객체를 생성해서 공통 기능을 삽입

컴파일, 클래스 로딩, 런타임 등 자바 관련 용어를 잘 모른다면 아래 포스트를 참고하면 좋다.

 

스프링에서는 프록시를 이용한 세 번째 방법을 사용하여 AOP를 제공한다. 중간에 프록시를 생성하여 실제 객체의 기능(핵심 기능)을 실행하기 전/후에 공통 기능을 호출한다. 스프링이 프록시 객체를 자동으로 만들어주기 때문에 직접 구현할 필요가 없으며 공통 기능 모듈만 구현하면 된다.

spring-framework-work-flow
스프링에서 AOP 동작 과정

AOP와 관련된 용어들을 정리해보았다.

  • Advice: 언제 공통 관심 기능을 핵심 로직에 적용할지를 정의
  • JoinPoint: Advice를 적용할 수 있는 지점. 스프링에서는 메서드 호출에 대한 JoinPoint만 지원
  • PointCut: 실제 Advice가 적용되는 JoinPoint
  • Weaving: Advice를 핵심 로직 코드에 적용하는 것
  • Aspect: 여러 객체에 공통으로 적용되는 기능

스프링에서 구현 가능한 Advice의 종류는 5가지가 있는데, 여기서는 대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행하는 데 사용되는 Around Advice에 대해서만 다루겠다.

 

스프링 AOP 구현 - Aspect

스프링 AOP를 이용해 공통 기능을 구하려면 다음 절차를 따르면 된다. 먼저 Aspect로 사용할 클래스에 @Aspect 애노테이션을 붙인다. 그다음 @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다. 그 후 공통 기능을 구현한 메서드에 @Around 애노테이션을 적용한다.

@Aspect
public class ExeTimeAspect {
	
	// @Pointcut: 공통 기능을 적용할 대상 설정
	// chap07 패키지와 그 하위 패키지에 위치한 public 메서드를 Pointcut으로 설정 
	@Pointcut("execution(public * chap07..*(..))")
	private void publicTarget() {
		
	}
	
	// @Around: Around Advice 설정
	// 값이 publicTarget()이므로 publicTarget()메서드에 정의한 Pointcut에 공통 기능을 적용
	// Pointcut에 해당하는 메서드에 measure() 메서드를 적용
	@Around("publicTarget()")
	// ProceedingJoinPoint 타입 파라미터: 프록시 대상 객체의 메서드를 호출할 때 사용
	public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
		long start = System.nanoTime();
		try {
			// proceed() 메서드를 사용해서 실제 대상 객체의 메서드를 호출
			Object result = joinPoint.proceed();
			return result;
		} finally {
			long finish = System.nanoTime();
			// signature: 메서드의 이름 + 파라미터
			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으로 공통 기능을 적용할 대상을 설정하고 @Around로 Around Advice를 설정했다. measure() 메서드의 ProceedingJoinPoint 타입 파라미터는 실제 대상 객체의 메서드를 호출하는 데 사용된다. 따라서 이 코드 이전과 이후에 현재 시간을 구하는 코드를 놓으면 된다.

 

스프링 AOP 구현 - 설정 클래스

공통 기능을 위한 모듈을 구현했으므로 스프링 설정 클래스를 작성해야 한다. @EnableAspectJAutoProxy 애노테이션을 추가하면 스프링이 @Aspect가 붙은 빈 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정을 사용한다.

// @Aspect 애노테이션을 붙인 클래스를 공통 기능으로 적용하려면 @EnableAspectJAutoProxy 애노테이션을 설정 클래스에 붙여야 한다.
// 프록시 생성과 관련된 AnnotationAwareAspectJAutoProxyCreator 객체를 빈으로 등록한다.
// 스프링이 @Aspect 애노테이션이 붙은 빈 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정을 사용한다. 
@Configuration
@EnableAspectJAutoProxy
public class AppCtx {

	@Bean
	public ExeTimeAspect exeTimeAspect() {
		return new ExeTimeAspect();
	}
	
	@Bean
	public Calculator calculator() {
		return new RecCalculator();
	}
	
}
public class Main {
	
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		
		Calculator cal = ctx.getBean("calculator", Calculator.class);
		long sevenFact = cal.fibo(7);
		System.out.println("cal.factorial(7) = " + sevenFact);
		System.out.println(cal.getClass().getName());
		ctx.close();
	}
}

calculator 빈에 공통 기능이 잘 적용되었고 실행되는지 Main 클래스를 위와 같이 구현하여 실행해보았다.

RecCalculator.fibo([7]) 실행 시간 : 23726 ns
cal.factorial(7) = 21
com.sun.proxy.$Proxy20

1번째 줄은 measure() 메서드에 의해 출력되었으며 2~3번째 줄은 Main에서 출력하였다. 이때 Calculator 타입이 RecCalculator가 아니고 $Proxy20인데, 이는 스프링이 생성한 프록시 타입이며 이름은 자바나 스프링 버전에 따라 달라질 수 있다.

Main-class-execution-flow
Main 클래스 실행 시 동작 과정

AOP를 적용하지 않았으면 return 한 객체가 RecCalculator 타입이었을 것이다. 그러나 AOP를 적용함으로써 스프링이 중간에 자동적으로 프록시 객체를 생성했기 때문에 프록시 객체가 반환되었다.

 

 

 

 

참고 서적: <초보 웹 개발자를 위한 스프링 5 프로그래밍 입문>

 

초보 웹 개발자를 위한 스프링5 프로그래밍 입문

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.


Spring Framework 시리즈

  1. 스프링 프로젝트 시작하기 (Maven)
  2. 스프링 컨테이너(Container) 의미
  3. Dependency, DI, Assembler (의존, 의존 주입, 주입기) 개념 정리
  4. 스프링에서의 의존 주입(DI)의 의미와 사용법
  5. 스프링 애노테이션을 사용한 의존 주입(DI)
  6. 의존 자동 주입(1) - @Autowired 애노테이션
  7. 의존 자동 주입(2) - 빈 이름과 한정사
  8. 의존 자동 주입(3) - @Autowired의 필수 여부, 자동 주입과 명시적 의존 주입
  9. 컴포넌트 스캔 - @Component, @ComponentScan 사용하기
  10. 빈 객체의 라이프사이클과 범위 (Life Cycle & Scope of Bean)
  11. AOP 프로그래밍(1) - 프록시와 AOP

댓글