[Spring] AOP

yuKeon·2023년 11월 27일
0

1. AOP란?

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed "crosscutting" concerns in AOP literature.)

AOP란 무엇일까? 공식 문서에 의하면 AOP는 프로그램 구조를 다른 시각으로 바라보며 OOP를 보완하는 개념이다. OOP에서 모듈화의 단위는 클래스지만 AOP에서는 aspect가 된다. aspect는 무엇일까? 다음 장에서 AOP의 핵심 개념을 알아본다.

2. AOP 용어

Aspect
: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications.

여러 클래스에 걸쳐 관심사를 모듈화한 것으로 다수의 Advice와 다수의 Pointcut의 결합체다. 대표적인 예로 트랜잭션 관리가 있다.

Join point
: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

프로그램이나 메서드, 예외 처리 등의 지점으로 Aspect 적용이 가능한 모든 지점을 말한다. Spring AOP에서 Join Point는 항상 메서드 실행을 표현한다.

Advice
: Action taken by an aspect at a particular join point. Different types of advice include "around", "before", and "after" advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.

특정 Join Point에서 Aspect가 취할 행동 즉, 메서드를 의미한다. 
Spring에서는 Advise를 인터셉터로 모델링하고 Joinpoint 주변에서 연결을 유지한다. Advice는 다양한 실행 시점이 있다.

Around
: 메소드 호출 이전, 이후, 예외발생 등 모든 시점에 동작한다.

Before
: 메소드 호출 이전 시점에 동작한다.

After
: 메소드 호출 이후 시점에 동작한다.

After returning
: 메서드 호출이 성공했을 때 동작한다.

After throwing
: 메서드 호출이 실패했을 때 동작한다.

Pointcut
: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.

Pointcut은 Join Point의 부분 집합으로 Aspect를 적용할 타겟 메서드를 선택하는 지시자다. Spring은 기본적으로 AspectJ Pointcut 표현식을 사용한다.

Target object
: An object being advised by one or more aspects. Also referred to as the "advised object". Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.

Advice를 적용받는 하나 이상의 객체다. Spring AOP는 런타임 프록시를 사용하여 구현되므로 이 객체는 항상 프록시 객체다.

AOP proxy
: An object created by the AOP framework in order to implement the aspect contracts (advice method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.

AOP 프레임워크에서 Advice 메서드를 구현하기 위해 생성한 객체다. 
Spring에서 AOP 프록시는 JDK 동적 프록시 CGLIB 프록시가 있다.

Weaving
: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

Pointcut으로 지정된 객체에 Aspect를 적용해서 새로운 Proxy 객체를 생성하는 과정이다.이는 컴파일 시점, 로드 시점 또는 런타임 시점에 수행된다. Spring AOP는 런타임에 Weaving을 수행한다.

3. AOP 적용 방식

컴파일 시점
.java 파일을 컴파일러를 통해 .class를 만드는 시점에 부가 기능 로직을 추가하는 방식이다. 모든 지점에 적용 가능하지만 AspectJ가 제공하는 컴파일러를 사용하므로 특정 컴파일러가 필요하다는 점과 복잡하다는 단점이 있다.

클래스 로딩 시점
: .class 파일을 JVM 내부의 클래스 로더에 보관하기 전에 조작하여 부가 기능 로직을 추가하는 방식이다. 모든 지점에 적용 가능하지만 특별한 옵션과 클래스 로더 조작기를 지정해야하므로 운영이 어렵다.

런타임 시점
: Spring에서 사용되는 방식이다. 컴파일이 끝나고 클래스 로더에 .class 파일이 올라가 Java가 실행된 다음 동작하는 방식이다. 실제 대상 코드는 그대로 유지되고 프록시를 통해 부가 기능이 적용된다.

특정 컴파일러, 복잡한 옵션, 클래스 로더 조작기를 사용하지 않아도 Spring만 있으면 AOP를 적용할 수 있어서 Spring AOP는 런타임 방식을 사용한다.

4. Spring AOP 특징

프록시 기반
: Spring은 프록시 기반 AOP를 지원한다. Spring은 Target의 프록시 객체를 생성하는데 이는 런타임 시점에 생성된다.

인터셉터
: 프록시가 요청을 가로챈다. 프록시는 Target으로의 요청을 가로채 관심사 로직을 수행한 후에 Target 객체으로 요청이 전달된다.

동적 프록시
: Spring은 동적 프록시 기반의 AOP이므로 메서드 Joinpoint만 지원한다.

5. AOP 적용 예시

실제 프로젝트에 Spring AOP를 적용해 보자. PSQ 서비스는 하루 한 번 사용자의 최초 로그인 시 추천 문제를 Slack으로 발송한다. 로그인을 요청하면 해당 사용자가 당일에 추천 문제를 받았는지 검사하게 된다. 즉, 로그인과 관련 없는 부가 로직이 추가된다.
이를 커스텀 어노테이션과 Spring AOP를 적용하여 개선해 보자.

개발 환경
Language : Java 11
Framework : Spring Boot 2.7.8

4.1. 커스텀 어노테이션 생성

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecommendationCheck {
}

로그인 메서드에 적용되어 추천 여부를 검사하기 때문에 Target을 ElementType.METHOD로 설정한다.

4.2. AOP 로직 구현

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RecommendationCheckAspect {
    private final SessionUtil sessionUtil;
    private final MemberService memberService;
    private final ProblemService problemService;

    @After("@annotation(com.psq.backend.util.annotation.RecommendationCheck)")
    public void recommendationCheck() {
        log.info("[AOP] 문제 추천 여부 검사 시작");
        SessionInfo sessionInfo = sessionUtil.getSessionInfo();
        Member member = memberService.getMemberById(sessionInfo.getId());

        if (!member.isRecommended()) {
            log.info("{} 유저는 문제 추천 대상입니다.", member.getEmail());
            problemService.recommendProblem(member);
        }
    }
}

@RecommendationCheck 어노테이션이 붙은 메서드가 호출되면 recommendationCheck() 메서드가 실행된다. @After를 사용한 이유는 세션 여부 때문이다. 현재 로그인 과정은 사용자가 로그인을 시도하고 요청이 성공하면 세션 ID를 클라이언트에 전달한다. 이후 모든 요청에 세션 ID로 사용자를 검증한다.

즉, 로그인이 성공하기 전까지 클라이언트는 세션 ID를 보유하지 않고 있으므로 recommendationCheck() 메서드를 @Before로 설정하게되면 세션 ID가 생성되기도 전에 로직이 실행돼서 401(Unauthorized) 예외가 발생한다.

4.3. 결과

6. 마치며

Spring AOP의 개념을 알아보고 간단한 구현도 해봤다. 여전히 완벽히 아는 것은 아니지만
"공통 관심사항 분리?"보다는 구체적으로 말할 수 있을 것 같다.

Ref.

0개의 댓글