개발이군고구마

[Spring : Design Pattern] 3. AOP와 Proxy (Self Invocation부터 시작) 본문

SERVER/Spring

[Spring : Design Pattern] 3. AOP와 Proxy (Self Invocation부터 시작)

김구황 2024. 11. 9. 15:59
728x90

1. 이유 

고민의 시작 

@CircuitBreaker 가 적용된 메서드 안에서  @CircuitBreaker 메서드를 호출할 때 applicationContext 에 있는 bean 을 가져오게끔 되어 있음 

application.getBean(this.getClass()).getMethod(adaper, url, json);

 

기존에 메서드는 같은 클래스라면 getMethod를 호출해주면 되지만,

CircuitBreaker 함수를 호출하는 부분에서는 applicationContext.getBean을 통해서 메서드를 호출해주고 있음 

 

🧐  왜 같은 클래스의 메서드를 호출하는데 "굳이" 컨테이너에 있는 클래스의 메서드를 실행해주는 걸까? 

 

 

2. AOP

많은 부분에서 '동일하게' 적용되는 코드들은 한데 모아,

특정 조건에 맞을 때 해당 코드를 호출해주는 방식을 프록시 패턴이라 한다. 

 

[어드바이저 : Advisor]

특정 조건 : 포인트컷 Pointcut

동일하게 적용되는 조건 : 어드바이스 Advice

 

이렇게 어디에, 무엇을 적용할지에 대한 '관점' 을 중심으로 개발 방법을 AOP 라 한다. 

 

스프링에서는 AOP를 구현하기 위해 @Aspect 어노테이션을 만들어 사용하는데,

해당 어노테이션이 붙어 있는 Adviosr를  `@Aspect` 어드바이저 빌더 내부에 저장한다. 

 

 

💥 조건이 하나라도 만족하면 프록시 적용 대상이 된다.

     예를 들어서 메서드 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.

 

  • Circuitbreaker 어노테이션이 붙어있는 메서드가 서비스 클래스를 통해서 들어오게 되면 프록시의 메서드 -> 실제 타겟 메서드로 호출하게 되면서 필요한 '기능'들(어드바이스) 이 추가되서 실행되지만
  • 서비스에서 바로 호출하게 되면 이미 실제 타깃 메서드가 호출된 상태에서 -> 타겟 메서드를 호출하면 프록시가 적용이 되지 않게 됨 
  • 따라서, 프록시를 적용하기 위해 메서드 내에서 빈에 있는 메서드를 호출하도록 함 

 

3. Proxy 패턴 

1) 관점 : 같은 타입 (Proxy / Decorator)

  • Proxy : 검증의 추가 
  • Decorator : 기능의 추가 
    • @Transactional : 해당 어노테이션 있는 클래스들은 빈이 프록시로 등록이 되어있고, 해당 클래스를 통해 메서드를 호출 -> 메서드에 어노테이션 붙었을 경우 트랜잭션 필요 코드 붙여서 반납함 

 

2) 관점 : 다른 타입 (Adapter)

다른 타입의 기능을 추가하는 방법 

 

 

3) 프록시 적용 예제

  • 어노테이션 (포인트 컷) 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
	public @interface Trace {
}
  • 어드바이저 (포인트 컷에 대한 어드바이스 정의) 
@Slf4j
@Aspect
public class TraceAspect {
    @Before("@annotation(hello.aop.exam.annotation.Trace)")
    public void doTrace(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        log.info("trace {} args={}", joinPoint.getSignature(), args);
    }
}
  • 타겟 클래스 (어노테이션을 가지고 있는 메서드) 
@Service
@RequiredArgsConstructor
public class ExamService {
    private final ExamRepository examRepository;
    @Trace
    public void request(String itemId) {
        examRepository.save(itemId);
    }
}
  • 실행
    • Aspect를 찾아서 어드바이저 빌더에 저장 TraceAspect
    • 스프링 객체가 전달되면 어드바이저 빌더 내를 살핀 후, 클래스/메서드 를 하나하나 더 확인하며 포인트 컷에 하나라도 만족하게 되면 @Trace -> request
    • 스프링 빈에 프록시가 등록됨 ExamService / TraceAspect
    • 프록시 등록된 객체의 메서드가 실행이 되면 -> 포인트 컷 조건인지 확인 후 -> 조건에 만족하면 어드바이스를 실행하고 타겟 메서드 실행 (그렇지 않으면 타켓 메서드만 실행) 
examService.request("data" + i);