관리 메뉴

한다 공부

[Spring] AOP (Aspect Oriented Programming) 본문

Dev/Java

[Spring] AOP (Aspect Oriented Programming)

사과당근 2024. 4. 14. 20:17

비즈니스 로직을 구현하다보면 로깅, 예외처리, 트랜잭션 처리 등 같은 코드가 반복되는 경우가 있다.

AOP는 이처럼 공통되는 로직을 묶어서 관심사를 분리하고, 코드의 응집도를 높이는 것이다.

낮은 결합도와 높은 응집도를 위해.

 

로깅, 예외처리, 트랜잭션 등의 반복되는 코드를 횡단 관심이라고 하고,

핵심 비즈니스 로직을 핵심 관심이라고 한다.

 

~ PointCut ~

우선 포인트 컷을 설명하기 전에, 조인포인트에 대해 알아보자.

조인포인트는, 비즈니스 클래스가 가지고 있는 모든 비즈니스 메소드를 의미한다.

포인트컷은 필터링된 조인포인트, 즉 필터링된 비즈니스 메소드를 의미한다.

예를 들어 로깅 기능을 CRUD 중 C와 D에만 적용하고 싶다면, C와 D에 포인트컷을 지정해야 하는 것이다.

 

xml에 이를 설정할 때는, 횡단 관심사에 해당하는 클래스를 <bean>에 등록하고,

<aop:config>를 통해 aop를 설정하면 된다.

<bean id="어드바이스 객체명" class="클래스명">

<aop:config>
	<aop:pointcut id = "allPointcout" expression="execution(표현식)"/>
	<aop:pointcut id = "getPointcut" expression="execution(표현식)">
	<aop:aspect ref="어드바이스 객체명">
		<aop:before pointcut-ref="getPointcut" method="메소드명"/>
	</aop:aspect>
</aop:config>

이런식으로 구성할 수 있는데,

포인트 컷은 <aop:pointcut> 으로 선언할 수 있다.

 

expression 표현식 안에는 리턴타입, 패키지 경로, 클래스명, 메소드명 및 매개변수가 들어간다,

execution(* com.dev.biz..*Impl.*(..))

이라는 표현식은

 

* : 모든 리턴타입에 대해

com.dev.biz.. : com.dev.biz 로 시작하는  모든 패키지에 대해

*Impl : Impl로 끝나는 모든 클래스에 대해

* : 모든 메소드에 대해 (get* 일 경우, 메소드 명이 get으로 시작하는 모슨 메소드에 대해)

(..) : 매개변수의 개수와 타입에 제약 없이

포인트컷을 적용하는 것이다.

 

추가적으로, 1개의 매개변수를 가지는 메소드만 선택할 경우 마지막이 (*) 이며,

2개의 매개변수를 가지는 메소드만 선택할 경우 마지막이 (*,*)이다.

 

~ Advice ~

어드바이스란, AOP의 핵심이 되는 메소드나 코드 즉, 횡단 관심에 해당하는 코드 자체를 의미한다.

이러한 어드바이스는, 클래스의 메소드로 작성이 된다.

스프링에서는 before, after, after-returning, after-throwing, around를 사용하여

어드바이스 메소드가 언제 실행될지 지정할 수 있다.

 

- before는, 포인트컷으로 지정한 비즈니스 메소드 실행 전에 어드바이스가 동작한다.

- after-returning은, 포인트컷으로 지정한 비즈니스 메소드가, 리턴한 데이터를 받아서 동작한다.

리턴값을 백업 DB로 이동시킨다던지 여러가지 방법으로 사용할 수 있다.

- after-throwing은, 포인트컷으로 지정한 메소드에서 예외가 발생했을 때, 예외 객체를 받아서 동작한다.

- after는 포인트컷으로 지정한 비즈니스 메소드가 실행된 뒤에 어드바이스가 동작한다.

- around는 포인트컷으로 지정한 비즈니스 메소드의 호출을 가로채서, 사전처리와 사후처리를 수행한다.

 

추가적으로 위빙(weaving)이란, 핵심 관심(비즈니스 로직)과 횡단 관심(공통 코드)를 연결하는 역할을 한다.

 

~ Aspect ~

AOP의 A에 해당하는 Aspect는 AOP의 핵심이다.

애스펙트란 포인트컷과 어드바이스의 결합으로,

어떤 포인트컷 메소드에 대해, 어떤 어드바이스 메소드가 실행할지를 결정하는 것이다.

 

위에서 본 코드를 다시 보자.

<bean id="어드바이스 객체명" class="클래스명">

<aop:config>
	<aop:pointcut id = "allPointcout" expression="execution(표현식)"/>
	<aop:pointcut id = "getPointcut" expression="execution(표현식)">
	<aop:aspect ref="어드바이스 객체명">
		<aop:before pointcut-ref="getPointcut" method="메소드명"/>
	</aop:aspect>
</aop:config>

1. 우선 aop:before pointcut-ref="getPointcut" 을 통해,

aop:pointcut id="getPointcut" 부분에서 명시된 표현식으로 필터링된 메소드가 호출된다.

 

2. aop:aspect ref="어드바이스 객체명"을 통해,

bean id="어드바이스 객체명" 부분의 코드를 쫓아가고,

"클래스명" 에 해당하는 클래스 안의, "메소드명"에 해당하는 메소드가 실행된다.

 

3. 이때, aop:before이라고 명시되어 있기 때문에, 횡단 관심 메소드는 핵심 관심 메소드 전에 동작한다.


~ Annotation 기반 AOP 설정 ~

IoC에서 어노테이션을 사용했던 것처럼, AOP도 어노테이션을 통해 XML설정을 최소화시킬 수 있다.

우선 어노테이션으로 AOP를 설정하려면 xml에 다음 엘리먼트를 선언해야 한다.

<aop:aspectj-autoproxy />

이후 스프링 컨테이너는 AOP 관련 어노테이션을 찾아 작업을 처리해준다.

 

AOP관련 어노테이션은 Advice클래스에 설정하면 된다.

그리고 해당 Advice객체를 빈에 등록하여 객체를 생성하게 해야한다.

이때 <bean>을 통한 빈 등록 혹은 @Service 어노테이션을 사용할 수 있다.

 

xml에서 포인트컷을 선언할 때, <aop:pointcut>을 사용했다.

어노테이션을 포인트컷을 선언할 때는, 어드바이스 클래스의 메소드에 @Pointcut 을 사용하면 된다.

AOP 어노테이션을 적용한 어드바이스 클래스의 예시는 다음과 같다.

import org.aspectj.lang.annotation.Pointcut;

@Service
public class Advice {
	@Pointcut("execution(* com.dev.biz..*Impl.*(..))")
   	public void allPointcut() {	}
    
	@Pointcut("execution(* com.dev.biz..*Impl.get*(..))")
	public void getPointcut() {	}
}

그리고 어드바이스 메소드의 동작 시점을 정하려면,

동작 시점 어노테이션을 사용하고, 옆에 괄호와 함께 포인트컷 참조 메소드를 지정하면 된다.

동작 시점을 결정하는 어노테이션으로는 @Before, @AfterReturning, @AfterThrowing, @After, @Around 가 있다.

 

애스팩트 또한 @Aspect 어노테이션을 활용할 수 있다.

애스팩트는 포인트컷과 어드바이스의 결합시키는 것으로,

@Aspect를 하면 어드바이스 객체를 애스팩트 객체로 인식한다.

이때, 어드바이스 객체 안에 있는 포인트컷 메소드와 어드바이스 메소드에 붙은 어노테이션에 의해 위빙이 처리된다.

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

@Service
@Aspect
public class Advice {
	@Pointcut("execution(* com.dev.biz..*Impl.*(..))")
   	public void allPointcut() {	}
    
	@Before("allPointcut()")
	public void myAop(JoinPoint jp) {
		// 원하는 로직 수행
	}
}

어드바이스 메소드 안에 매개변수로 받은 JoinPoint jp는 뭘까?

이는 바로 AOP가 적용된 핵심 비즈니스 메소드에 관련된 정보를 담은 인터페이스이다.

 

만약 After Throwing 기능을 사용한다고 해보자.

이는 예외가 발생한 경우에 동작하는 어드바이스인데, 어디서 예외가 발생하는지 알아야하지 않겠는가

예외가 발생한 그 메소드의 클래스 및 패키지 정보 등을 이용할 수 있는 인터페이스란 것이다 ~

 

jp.getSignature() 을 통해 호출되는 메소드의 "패키지 경로, 클래스 이름, 메소드 이름, 매개변수, 리턴타입" 등의 정보를 알 수 있다.

jp.getArgs()를 사용하면, 비즈니스 메소드를 호출할 때 전달한 인자의 목록을 Object 배열로 받을 수 있다.


한 가지 주의 사항이 있다면, Around 기능을 사용할 때이다.

Aroud를 사용할 때는, JoinPoint 를 매개변수로 받는 것이 아니라, ProceedingJoinPoint 를 매개변수로 받아야한다.

 

그 이유는 Around 가 요청을 가로채기 위해,

Around Advice 메소드를 구현하는 중에 proceed 메소드를 호출해야하기 때문이다.

proceed 메소드를 사용해야 비즈니스 메소드를 호출할 수 있다.

그래서 proceed 메소드 호출 전에 사전 처리 로직을 작성하고,

proceed 메소드 호출 후에 사후 처리 로직을 작성하는게 일반적이다.

 

이 proceed 메소드는 ProceedingJoinPoint 객체에 포함되어 있고,

ProceedingJoinPoint 는 JoinPoint를 상속한 것이다.


XML 기반의 AOP 설정과, 어노테이션 기반의 AOP 설정을 알아보았는데,

실제로는 XML 기반의 AOP 설정을 주로 사용한다고 한다!

 

아자~ 아자~