안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 SpringBoot의 프록시 방식의 AOP에서 발생하는 내부 호출 문제를 해결하는 방법에 대해서 정리하는 글을 작성하도록 하겠습니다.

 

스프링에서는 AOP를 적용하기 위해 프록시를 통해 대상 객체를 호출하는 방법을 따릅니다. 프록시에서 먼저 어드바이스를 호출하고 대상 객체를 호출합니다.

 

saveRepository라는 테스트 메서드를 실행하면 다음의 절차에 따라 진행됩니다.

blogRepository는 인터페이스가 아닌 구현체에 해당하므로 스프링의 AOP 프록시 정책에 따라 CGLIB 방식의 프록시가 적용됩니다.

 

1. 프록시 처리 과정 코드 예시  

1) Pointcuts

@Slf4j
public class Pointcuts {

    @Pointcut("execution(* hello.aop.blog.*.save(..))")
    public void allSave() {}
    
}

 

2) BlogRepository

@Slf4j
@Repository
public class BlogRepository {

    public void save(String content) {

        log.info("[BlogRepository.save 실행]");
        if (content == null) {
            log.info("[BlogRepository.save 에러]");
            throw new IllegalArgumentException();
        }

        log.info("[BlogRepository.save 저장]");
    }
}

 

3) ApplyAspect

@Slf4j
public class ApplyAspect {

    @Aspect
    public static class AllSave {
        @Around("hello.aop.blog.Pointcuts.allSave()")
        public Object saveAround(ProceedingJoinPoint joinPoint) throws Throwable {

            try{
                log.info("[Around AllSave ProceedingJoinPoint]");
                Object result = joinPoint.proceed();
                log.info("[Around AllSave ProceedingJoinPoint] result = {}", result);
                return result;

            } catch (Exception e) {
                log.info("[Around AllSave Exception] message = {}", e.getMessage());
                throw e;
            }
        }
    }
}

 

4) TestCode

@Slf4j
@SpringBootTest
@Import({ApplyAspect.AllLoad.class, ApplyAspect.AllSave.class, ApplyAspect.AllService.class})
class ApplyAspectTest {

    @Autowired
    BlogRepository blogRepository;

    @Autowired
    BlogService blogService;

    @Test
    @DisplayName("BlogRepository save AOP 로그 확인")
    public void saveRepository() throws Exception {
        blogRepository.save("안녕하세요");
    }
}

 

2. 디버깅 모드로 프록시 처리 과정 확인하기 

 

1) CGLIB 형태의 프록시 생성

 

2) 어드바이저가 호출

- 부가 기능인 로그를 기록하는 기능이 실행된 후, ProceedingJoinpoint 실행(실제 비즈니스 로직)

 

3) 프록시 객체에서 타겟 객체의 비즈니스 로직 실행

- 완료 후, 다시 어드바이저 진행 (proceed() 이후 로직 실행)

 

이렇듯, 프록시가 직접 객체를 호출한다면, 정상적으로 부가 기능(로그)이 실행되는 것을 확인할 수 있습니다. \

하지만, 프록시 객체가 직접 객체를 호출하지 않고 호출된 객체 내부에서 다른 메서드를 호출한다면, 프록시가 적용되지 않습니다.

 

3. 내부 호출 프록시 미적용

Pointcuts을 수정한 후, BlogRepository 내부에 saveInternal 메소드를 추가한 후, 테스트를 진행하면 다음과 같습니다.

 

1) 메서드 이름 매칭 포인트컷을 수정 (save*)

 

2) BlogRepository에 public 접근 제어자로 설정하여 메서드 추가

 

3) BlogRepository에 save() 메서드에 saveInternal 메서드 호출 추가

public void save(String content) {

    log.info("[BlogRepository.save 실행]");
    saveInternal();   // 메서드 호출 추가
    if (content == null) {
        log.info("[BlogRepository.save 에러]");
        throw new IllegalArgumentException();
    }

    log.info("[BlogRepository.save 저장]");
}

 

4) 실행 후, 로그 확인

로그를 확인하면 saveInternal에 해당하는 프록시 로그가 기록되지 않은 것을 확인할 수 있습니다.

 

4. 내부 호출 프록시 적용하기

대안은 자기 자신을 주입하는 방법과 지연 조회 방식, 구조 변경 방법을 사용 것입니다.

이 중, 저는 구조 변경 방법을 활용하여 로그가 기록되지 않는

saveInternal 메서드에 프록시가 적용되도록 수정하는 작업을 수행하겠습니다.

 

1) 코드 수정 BlogSaveInternalRepository를 생성하여

- 별도의 클래스 내부에 saveInternal() 메서드 생성

@Slf4j
@Component
public class BlogSaveInterRepository {

    public void saveInternal() {
        log.info("[BlogRepository.saveInternal 실행]");
    }
}

 

2) BlogRepository는 BlogSaveInternalRepository를 의존 관게 주입을 받음

@Slf4j
@Repository
@RequiredArgsConstructor
public class BlogRepository {

    private final BlogSaveInterRepository blogSaveInterRepository;
    public void save(String content) {

        log.info("[BlogRepository.save 실행]");
        blogSaveInterRepository.saveInternal();
        if (content == null) {
            log.info("[BlogRepository.save 에러]");
            throw new IllegalArgumentException();
        }

        log.info("[BlogRepository.save 저장]");
    }
}

 

3) 로그 출력 결과 확인

 

 

5. 내부 구조 확인하기

 

기존의 클라이언트의 요청이 BlogRepository의 AOP proxy에서 타겟 메서드인 save()를 호출하고 save() 메서드 내부에서 saveInternal() 메서드를 호출하는 구조였다면,

save()에서 BlogInternalRepository의 saveInternal() AOP Proxy를 호출하는 방식으로 변경됨으로써, saveInternal도 프록시 객체의 호출을 받아 로그를 출력할 수 있는 구조가 되었습니다. 

 

이상으로 SpringBoot에서 내부 호출 프록시 미적용 문제를 해결하기 포스팅을 마치겠습니다.

더 많은 설명과 자세한 정보는 영한님의 스프링 핵심 원리 고급편에서 확인하실 수 있습니다.

감사합니다.!!!

 

참고 자료 : 영한님 스프링 핵심 원리 고급편 https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8

+ Recent posts