[Spring] AOP

건미누·2024년 3월 2일

Spring Study

목록 보기
6/7

Spring 3대 요소

CS를 공부하면서 Spring 3대 요소인 이 3가지에 대해 매우 많이 듣게 되었다.

  • IoC/DI (의존성 주입)
  • PSA (서비스 추상화)
  • AOP (관점 지향 프로그래밍)

이번에 스터디에서 발표할 내용으로 AOP를 선택하여 추가적으로 학습하며 더 많은 것을 알게 되었는데 이를 정리해보고자 한다.

AOP (Aspect-Oriented-Programming)

처음에는 관점지향 프로그래밍이 Spring의 3대 요소라고 학습해서 Spring의 특징인 줄 알았는데 그렇지 않았다. 관점지향 프로그래밍은 다양한 언어에서 사용 중이며 JAVA는 AspectJ를 구현체로 AOP를 사용하고 있는 것이었다.

하지만 AOP는 OOP를 보완하며 OOP를 보다 완전하게 만들어주기 때문에 객체지향 프로그래밍을 설계할 때는 뗄 수 없는 것 같다.

관심사의 분리

AOP의 목적은 관심사의 분리에 있다고 볼 수 있다.

위와 같이 다양한 Calculator 기능이 있다. 그러나 실행시간 측정과 로깅의 경우에는 모든 기능에서 부가적으로 사용하는 기능이다. 이 때 만약 아래 코드와 같이 실행시간을 측정한다면 결과는 잘 나오겠지만 후에 유지보수가 매우 힘들 것이다.

StopWatch stopWatch = new StopWatch();
stopWatch.start();
log.info("토큰을 요청하는 중...");
// POST 방식으로 key-value 데이터 요청
OAuthToken oauthTokenRes = wc.post()
        .uri(TOKEN_URI)
        .body(BodyInserters.fromFormData(params))
        .header("Content-type", "application/x-www-form-urlencoded;charset=utf-8")
        .retrieve()
        .bodyToMono(OAuthToken.class).block();

stopWatch.stop();
log.info("토큰 발급 완료! {} ms", stopWatch.getLastTaskTimeMillis());

예를 들어 3000개의 로직에 모두 실행시간을 달았을 때, 내용을 수정하기 위해서는 또 3천개의 실행시간을 확인하는 로그를 수정해야하기 때문이다.
핵심 기능에서 부가적인 기능을 분리하여 모듈화를 하는 것, 이것이 바로 관심사를 분리하여 비즈니스 로직에 집중할 있도록 하는 AOP라고 할 수 있다.

AOP가 필요한 코드

  • 전 영역에서 자주 나타나는 코드지만 핵심 기능이 아닌 부가 기능인 경우
  • 그냥 쓰게되면 중복 코드가 많이 발생하는 경우
  • 비즈니스 로직과 함께 사용했을 때 로직을 이해하기 어려운 경우

AOP 용어

  • Target
    • 어떤 대상에 부가 기능을 부여할 것인가
  • Advice
    • 어떤 부가 기능? Before, AfterReturning, AfterThorwing, After, Around
  • Join point
    • 어디에 적용? 메서드, 필드, 객체, 생성자 등 ⇒ AspectJ
    • Spring AOP는 메서드가 실행될 때 만으로 한정하고 있다.
  • Point cut
    • 실제 advice가 적용될 지점, Spring AOP에서는 advice가 적용될 메서드를 선정

AOP 구현 방법

  • 컴파일 (Aspect J)
    • .java → .class로 컴파일 할 때
  • 클래스 로드 (Aspect J)
    • .class를 메모리 상에 올릴 때
  • 프록시 패턴 (Spring AOP)
    • 컴파일러나 클래스 로더 설정을 하지 않아도 가능
    • 오버라이딩 개념으로 동작, 메서드 실행시점에만 AOP를 적용
    • 스프링 컨테이너가 관리할 수 잇는 빈에만 AOP 적용 가능
    • 문법만 AspectJ를 사용하고, Proxy를 사용해서 타겟 class를 proxy로 감싸서 실행하는 방식
    • IoC/DI 기반이라서 가능한 방식

실제로 SpringBoot에서 @Aspect로 AOP를 적용하는 경우와 적용하지 않는 경우, this.getclass().getName()으로 확인하면 차이가 있다.

AOP를 적용하지 않는 경우 OrderServiceImpl이 그대로 나타나지만 적용하게 되면 Proxy로 감싸져
$$ SpringCGLIB $$ 이 붙게 된다. AOP를 구현하는 방법은 다음과 같다.

만들고자 하는 기능을 AOP로 구현한다. @Aspect 어노테이션을 달고 @Component를 통해 빈에 등록한다. 위의 경우는 주문이 생성의 실행시간을 측정하기 위한 로직이다. advice로는 @Around 어노테이션으로 메서드가 실행되기 전 stopWatch를 실행시키고 완료 후에 실행시간을 로그로 남기도록 하였다.

다음으로는 PointCut을 작성하여 OrderService가 실행되는 모든 구간에 OrderTimeAdvisor가 구동되도록 하였다. PointCut은 위치 뿐 아니라 어노테이션으로도 구동이 가능하다. 커스텀한 어노테이션을 기준으로 작동하게끔 만든다면 보다 효과적으로 사용할 수 있다.

AOP가 구동 되는가? 되지 않는가?

다음과 같은 예시 메서드가 있을 때, AOP를 @IwantCallbyAOP에 작동하도록 구현해놓고 join에는 해당 어노테이션을 걸지 않으면 어떻게 될까?

public void join (JoinRequest joinRequestDto) {
	help();
}

@IwantCallbyAOP
private void help() {
	System.out.println("취업 시켜줘, 돈 벌게해줘어~~~~");
}

정답은 비즈니스 로직은 작동하지만 AOP는 작동하지 않는다 이다.

Spring AOP는 Target class를 proxy로 감싸서 실행하는 방식이다. join에서 private메서드 인 help를 내부적으로 직접 호출할 때, help는 AOP에 의해 감싸진 Proxy객체가 아니기 때문에 Proxy를 경유하지 않고 바로 메서드를 호출하게 되기 때문에 AOP가 적용되지 않는다.

내부 호출 시 getclass().getName()에서 proxy가 붙지 않은 것을 확인할 수 있다.

대표적인 AOP : @Transactional

AOP의 가장 대표적인 예시가 Transactional이다. private 메서드에 @Transactional을 걸었을 때, 실제로 이것이 실행되지 않는 경우가 있는데 바로 위에서 설명한 AOP가 구동되지 않는 경우다.

면접에서 AOP를 사용한 적이 있냐는 질문을 받은 적이 있는데, 프로젝트 당시의 나는 로직을 일일이 손수 작성했었기 때문에 "사용하지 않았고 직접 로그를 작성했다" 라고 답변한 적이 있었다.

지금 생각하면 이 질문은 "너 @Transactional 사용했는데 이게 AOP걸 아니? AOP에 대해 이해하고 있니?" 라는 질문이었고 나는 "@Transactional은 사용했지만 AOP는 쓰지 않았어요! 하지만 AOP가 무엇인지는 알고 있어요!" 라고 대답한 거였다.

내가 쓴 기술이 어떻게 작동하는지 자세히 알아야하는 필요성을 강하게 느낀 학습이었다.

profile
꺾여도 해야지

0개의 댓글