728x90
해당 포스팅에 이어 @annotation, bean 지시자도 알아보겠습니다.
- @annotation: 특정 애너테이션이 적용된 메소드에 대해 포인트컷을 설정합니다. 즉, 메서드에 특정 애너테이션이 붙어 있을 때 그 메서드를 가로챌 수 있습니다.
[참고]
- @annotation: 메서드 수준의 애너테이션을 타겟으로 함.
- @target: 클래스 수준의 애너테이션을 타겟으로 함.
@annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
}
public class MyService {
@MyCustomAnnotation
public void myAnnotatedMethod() {
// some logic
}
}
@Aspect
public class MyAspect {
// @MyCustomAnnotation 어노테이션이 붙은 메소드에 어드바이스 적용
@Pointcut("@annotation(com.example.annotation.MyCustomAnnotation)")
public void annotatedMethod() {}
@Before("annotatedMethod()")
public void beforeAdvice() { // myAnnotatedMethod가 호출될 때 가로챌 수 있습니다.
// Advice logic
}
}
참고 (@annotation vs execution)
@annotation
- 장점: 조인포인트를 명확하게 표시할 수 있습니다. 따라서 해당 애노테이션을 보는 개발자는 해당 메서드나 클래스에 특정 Aspect가 적용되었음을 쉽게 알 수 있습니다.
- 단점: 특정 메서드나 클래스에 애노테이션을 추가해야 합니다. 이는 추가적인 작업이 될 수 있으며, 모든 조인포인트에 일관성 있게 애노테이션을 붙이는 것이 어려울 수도 있습니다.
execution
- 장점: 조인포인트를 코드 내에서 직접 명시하지 않고 Aspect에서 패턴을 사용해 정의할 수 있습니다. 이로 인해 Aspect와 비즈니스 로직의 분리가 더욱 명확해집니다.
- 단점: 조인포인트가 어디에 적용되었는지 코드만 보고 즉시 파악하기가 어려울 수 있습니다. 따라서 Aspect의 로직을 이해하기 위해서는 해당 지시자의 패턴을 잘 파악해야 합니다.
그런 장단점 때문에 실무에서는 둘다 사용하게 되지만, 막상 사용해보면 애노테이션이 좀 더 직관적이고 실용적이기 때문에 많은 개발자들이 애노테이션 방식을 더 선호하는 경향이 있습니다.
아래는 추가 예제입니다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
String value();
}
@Slf4j
@Import(AtAnnotationTest.AtAnnotationAspect.class)
@SpringBootTest
public class AtAnnotationTest {
@Autowired
MemberService memberService;
@Test
void success() {
log.info("memberService Proxy={}", memberService.getClass());
memberService.hello("helloA");
}
@Slf4j
@Aspect
static class AtAnnotationAspect {
// MethodApp 에너테이션이 걸린 MemberServiceImpl(MemberService)의 hello 메소드에 어드바이스 적용
@Around("@annotation(hello.aop.member.annotation.MethodAop)")
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[@annotation] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
매개변수 전달
지금까지 배운 this, target, args,@target, @within, @annotation 포인트컷들은 어드바이스에 매개변수를 전달할 수 있습니다.
아래와 같이 사용합니다. (arg 가 매개변수입니다.)
@Before("allMember() && args(arg,..)")
public void logArgs3(String arg) {
log.info("[logArgs3] arg={}", arg);
}
아래는 다양한 매개변수 전달 예시 확인을 위한 코드입니다.
@ClassAop
@Component
public class MemberServiceImpl implements MemberService {
@Override
@MethodAop("test value")
public String hello(String param) {
return "ok";
}
public String internal(String param) {
return "ok";
}
}
package hello.aop.pointcut;
import hello.aop.member.MemberService;
import hello.aop.member.annotation.ClassAop;
import hello.aop.member.annotation.MethodAop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@Slf4j
@Import(ParameterTest.ParameterAspect.class)
@SpringBootTest
public class ParameterTest {
@Autowired
MemberService memberService;
@Test
void success() {
log.info("memberService Proxy={}", memberService.getClass());
memberService.hello("helloA");
}
@Slf4j
@Aspect
static class ParameterAspect {
@Pointcut("execution(* hello.aop.member..*.*(..))")
private void allMember() {
}
@Around("allMember()")
public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
// 매개변수를 전달받습니다. (helloA)
Object arg1 = joinPoint.getArgs()[0];
log.info("[logArgs1]{}, arg={}", joinPoint.getSignature(), arg1);
return joinPoint.proceed();
}
@Around("allMember() && args(arg,..)") // 매개변수 전달
public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable {
log.info("[logArgs2]{}, arg={}", joinPoint.getSignature(), arg);
return joinPoint.proceed();
}
@Before("allMember() && args(arg,..)") // @Before를 사용
public void logArgs3(String arg) { // 타입으로 String으로 제한
log.info("[logArgs3] arg={}", arg);
}
@Before("allMember() && this(obj)") // 프록시 객체를 전달
public void thisArgs(JoinPoint joinPoint, MemberService obj) {
log.info("[this]{}, obj={}", joinPoint.getSignature(),
obj.getClass());
}
@Before("allMember() && target(obj)") // 실제 대상 객체를 전달
public void targetArgs(JoinPoint joinPoint, MemberService obj) {
log.info("[target]{}, obj={}", joinPoint.getSignature(),
obj.getClass());
}
@Before("allMember() && @target(annotation)") // 타입의 어노테이션 전달
public void atTarget(JoinPoint joinPoint, ClassAop annotation) {
log.info("[@target]{}, obj={}", joinPoint.getSignature(),
annotation);
}
@Before("allMember() && @within(annotation)") // 타입의 어노테이션 전달
public void atWithin(JoinPoint joinPoint, ClassAop annotation) {
log.info("[@within]{}, obj={}", joinPoint.getSignature(),
annotation);
}
@Before("allMember() && @annotation(annotation)") // 메서드의 어노테이션 전달
public void atAnnotation(JoinPoint joinPoint, MethodAop annotation) {
log.info("[@annotation]{}, annotationValue={}",
joinPoint.getSignature(), annotation.value()); // 어노테이션 값 출력
}
}
}
출력결과
참고
- this: 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
- target: Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
즉, this는 스프링 빈으로 등록되어 있는 프록시 객체를 대상으로 포인트컷을 매칭하고, target은 실제 target 객체를 대상으로 포인트컷을 매칭합니다. (* 같은 패턴을 사용할 수 없고, 부모 타입을 허용합니다.)
소스 코드
@annotation을 활요한 실전 예제 코드도 아래에 첨부하였습니다.
예외 발생 시 재시도를 하는 애너테이션(@Retry)와 메소드 호출 정보가 출력되는 편리한 로그를 출력하는 애너테이션(@Trace)을 이용하여 스프링 AOP를 적용한 코드입니다. (advanced/src/test/java/hello/aop/exam/ExamTest.java 코드를 실행하여 동작을 확인할 수 있습니다.)
https://github.com/eunhwa99/SpringAOP/tree/main/advanced/src/main/java/hello/aop/exam
[참고자료]
김영한, "스프링 핵심 원리 - 고급편 ", 인프런
728x90
반응형
'Spring Boot' 카테고리의 다른 글
bootJar vs Jar (feat. 멀티모듈) (2) | 2024.10.21 |
---|---|
스프링 AOP의 내부호출 문제 및 해결법 (1) | 2024.10.03 |
Spring AOP - 포인트컷(pointcut) 지시자(@target, @within) (0) | 2024.10.02 |
Spring AOP - 포인트컷(pointcut) 지시자(within, args) (1) | 2024.10.02 |
Spring AOP - 포인트컷(pointcut) 지시자(execution) (0) | 2024.10.02 |