Ref. Spring Framework Documentation v.5.3.16
📌 이 글에서는 공식문서의 Aspect Oriented Programming 부분만 다룹니다.
몇 가지 AOP 핵심 개념과 용어를 정의하는 것으로 시작합니다. 이 용어는 Spring에만 국한되지 않습니다.
@Aspect
어노테이션으로 주석이 달린 일반 클래스(@AspectJ 스타일)를 사용하여 구현됩니다.Spring AOP에는 다음과 같은 advice가 포함됩니다.
Spring Framework의 핵심 신조 중 하나는 비침투성입니다. 이것은 프레임워크 특정 클래스와 인터페이스를 비즈니스 또는 도메인 모델에 강제로 도입해서는 안 된다는 생각입니다. 그러나 어떤 곳에서는 Spring Framework가 코드베이스에 Spring Framework 관련 종속성을 도입하는 옵션을 제공합니다. 이러한 옵션을 제공하는 근거는 특정 시나리오에서 이러한 방식으로 특정 기능 부분을 읽거나 코딩하는 것이 훨씬 더 쉬울 수 있기 때문입니다. 그러나 Spring Framework(거의)는 항상 선택을 제공합니다. 특정 사용 사례 또는 시나리오에 가장 적합한 옵션에 대해 정보에 입각한 결정을 내릴 수 있는 자유가 있습니다.
이 장과 관련된 그러한 선택 중 하나는 AOP 프레임워크(및 AOP 스타일)를 선택하는 것입니다. AspectJ, Spring AOP 를 모두 선택할 수 있습니다. @AspectJ 주석 스타일 접근 방식 또는 Spring XML 구성 스타일 접근 방식 중 하나를 선택할 수도 있습니다. 이 장에서 @AspectJ 스타일 접근 방식을 먼저 도입하기로 선택했다는 사실은 Spring 팀이 Spring XML 구성 스타일보다 @AspectJ 주석 스타일 접근 방식을 선호한다는 표시로 간주되어서는 안 됩니다.
각 스타일의 "whys and wherefores"에 대한 자세한 내용은 사용할 AOP 선언 스타일 선택하기를 참조하세요.
Java @Configuration
으로 @AspectJ 지원을 활성화하려면 다음 예제와 같이 @EnableAspectJAutoProxy
어노테이션을 추가합니다.
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
XML 기반 구성으로 @AspectJ 지원을 활성화하려면 다음 예제와 같이 aop:aspectj-autoproxy
요소를 사용합니다.
<aop:aspectj-autoproxy/>
@AspectJ 지원이 활성화되면 @AspectJ 관점(@Aspect
어노테이션이 있음)이 있는 클래스를 사용하여 애플리케이션 컨텍스트에 정의된 모든 빈이 Spring에서 자동으로 감지되고 Spring AOP를 구성하는 데 사용됩니다.
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
@Pointcut
어노테이션을 사용하여 표시됩니다.아래 예는 pointcut 서명과 pointcut 표현식 사이의 이러한 구별을 명확하게 하는 데 도움이 될 수 있습니다. 다음 예제는 transfer
라는 메서드의 실행과 일치하는 anyOldTransfer
라는 포인트컷을 정의합니다.
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
어드바이스는 포인트컷 표현식과 연관되며 포인트컷과 일치하는 메서드 실행 전후 또는 전후에 실행됩니다. pointcut 표현식은 명명된 pointcut에 대한 단순 참조이거나 제자리에 선언된 pointcut 표현식일 수 있습니다.
@Before
어노테이션을 사용하여 aspect에서 before advice를 선언할 수 있습니다.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
일치하는 메서드 실행이 정상적으로 return 될 때 실행됩니다. @AfterReturning
어노테이션을 사용하여 선언할 수 있습니다.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
After throwing Advice는 일치하는 메서드 실행이 예외를 throw하여 종료될 때 실행됩니다. @AfterThrowing
어노테이션을 사용하여 선언할 수 있습니다.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
일치하는 메서드 실행이 종료되면 after(최종) advice가 실행됩니다. @After
어노테이션을 사용하여 선언합니다. 애프터 어드바이스는 정상 및 예외 반환 조건을 모두 처리할 수 있도록 준비해야 합니다. 일반적으로 리소스 및 유사한 목적을 해제하는 데 사용됩니다.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
around advice는 일치하는 메서드가 실행되는 동안 실행합니다. 메서드 실행 전후에 작업을 수행하고 메서드가 실제로 실행되는 시기와 방법, 실행 여부를 결정할 수 있는 기회가 있습니다. 어라운드 어드바이스는 스레드로부터 안전한 방식(예: 타이머 시작 및 중지)으로 메서드 실행 전후의 상태를 공유해야 하는 경우에 자주 사용됩니다.
항상 요구사항을 충족하는 가장 강력한 형태의 advice를 사용하십시오.
예를 들어, before advice가 당신의 요구에 충분하다면 around advice를 사용하지 마십시오.
어라운드 어드바이스는 @Around
어노테이션으로 메소드에 어노테이션을 작성하여 선언됩니다. 메서드는 반환 형식으로 Object
를 선언해야 하며 메서드의 첫 번째 매개 변수는 ProceedingJoinPoint
형식이어야 합니다. 어드바이스 메서드의 본문 내에서 기본 메서드를 실행하려면 ProceedingJoinPoint
에 대해 proceed()
를 호출해야 합니다. 인수 없이 proceed()
를 호출하면 호출자의 원래 인수가 호출될 때 기본 메서드에 제공됩니다. 고급 사용 사례의 경우 인수 배열(Object[]
)을 허용하는 proceed()
메서드의 오버로드된 변형이 있습니다. 배열의 값은 호출될 때 기본 메서드에 대한 인수로 사용됩니다.
around advice에 의해 반환된 값은 메서드 호출자가 본 반환 값입니다. 예를 들어, 간단한 캐싱 aspect는 캐시에 값이 있는 경우 캐시에서 값을 반환하거나 없는 경우 proceed()
을 호출하고 해당 값을 반환할 수 있습니다. proceed
는 around advice의 본문 내에서 한 번, 여러 번 호출되거나 전혀 호출되지 않을 수 있습니다. 이 모든 것이 합법입니다.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
Introductions(AspectJ에서 유형 간 선언으로 알려짐)를 통해 Aspect는 조언된 객체가 주어진 인터페이스를 구현한다고 선언하고 해당 객체를 대신하여 해당 인터페이스의 구현을 제공할 수 있습니다.
@DeclareParents
어노테이션을 사용하여 introduction을 선언할 수 있습니다. 이 어노테이션은 일치하는 유형에 새 부모가 있음(따라서 이름)이 있음을 선언하는 데 사용됩니다. 예를 들어, UsageTracked
라는 인터페이스와 DefaultUsageTracked
라는 인터페이스의 구현이 주어지면 다음 advice는 서비스 인터페이스의 모든 구현자가 UsageTracked
인터페이스도 구현한다고 선언합니다(예: JMX를 통한 통계).
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}