카테고리 없음

[Spring] 스프링 AOP란?

ckm7907 2024. 5. 29. 14:16

AOP를 사용하는 이유?

 

애플리케이션 로직은 크게 핵심 기능과 부가 기능으로 나눌 수 있다.

1. 핵심 기능 : 해당 객체가 제공하는 고유의 기능

2. 부가 기능 : 핵심 기능을 보조하기 위해 제공되는 기능 (ex: 로깅, 트랜잭션, 보안)

 

보통 기존 프로젝트에 부가 기능을 추가 하게 되면 하나의 클래스가 아닌 여러 클래스에 부가 기능을 추가하게 된다.

예를 들어 프로젝트의 모든 클래스에 로그 기능을 추가한다면 하나의 부가 기능(로그 추적)을 여러 곳에 동일하게 사용하게 됩니다.

이러한 부가 기능을 바로 횡단 관심사(cross-cutting concerns)라고 합니다.

 

만약, 여기서 모든 서비스 메소드가 호출될 때 마다 로그를 남겨야 한다고 가정하자.

하드코딩을 하면 그냥 모든 메소드에 로깅 코드를 삽입하면 되겠지만 수많은 코드 중복이 발생하게 되고, 이는 각 서비스 메소드가 가지는 책임과는 무관하다.

즉 SRP(단일책임원칙)을 위반하게 되는 것이다.

 

이런 경우에 사용하는 것이 바로 AOP이다!

 

AOP란?

Aspect-Oriented Programming의 약자이다.

프록시 패턴을 기반으로 구현이 되어 있다. 프록시 객체는 원래 객체를 감싸고 있는 객체입니다. 프록시 객체가 원래 객체를 감싸서 client의 요청을 처리하게 하는 패턴입니다.

 

AOP 적용 방식

AOP의 적용 방식은 크게 3가지가 있다.

1. 컴파일 시점

2. 클래스 로딩 시점

3. 런타임 시점 ( 프록시 사용 )

 

컴파일 시점과 클래스 로딩 시점 적용 방식은 AspectJ 프레임워크를 직접 사용해야 하고, 이 AspectJ를 학습하기 위해선 엄청난 분량과 설정의 번거러움이 있어

 

주로 런타임 시점 적용방식을 사용하는 스프링 AOP를 사용 한다.

 

Sping AOP API의 AOP 구현 동작과정은 다음과 같다.

1. 스프링 빈 대상이 되는 객체를 생성한다

2. 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후 처리기에 전달한다.

3. 모든 Advisor 빈을 조회하고 Pointcut을 통해 클래스와 메서드 정보를 매칭해보면서 프록시를 적용할 대상인지 판단한다.

4. 객체의 모든 메서드를 포인트컷에 비교해보면서 조건이 하나라도 만족한다면 프록시를 생성하고 프록시를 빈 저장소에 판단한다.

5. 만약 프록시 생성 대상이 아니라면 들어온 빈 그대로 빈 저장소로 반환한다. 빈 저장소는 객체를 받아서 빈으로 등록한다.

 

요약하자면, 빈을 스프링 컨테이너에 등록할 때, advisor에서 프록시가 적용할 게 있으면 빈 객체에 프록시를 추가하여 등록한다.

 

요청에 대한 동작 흐름은 아래와 같다.

https://sallykim5087.tistory.com/158

1. AOP에 @Before이나, @Around 로 설정된 메소드가 먼저 실행된다.

2. 그 후 Contoller에서 메인 로직이 실행된다.

3. Controller에 실행이 끝났으면 @Aound 이나, @After 로 설정된 메소드가 실행된다.

 

코드 예시

@Pointcut("execution(* com.ssafy.pair06_ssafy.domain.*.controller..*.*(..))")
    private void allOrder() {}

@Around("allOrder()")
    public Object calculateExecutionTime(ProceedingJoinPoint pJoinPoint) throws Throwable {
        StopWatch sw = new StopWatch();
        sw.start();

        Object result = pJoinPoint.proceed();

        sw.stop();
        long executionTime = sw.getTotalTimeMillis();

        Method method = getMethod(pJoinPoint);

        String task = getClass(pJoinPoint) + "." + method.getName();

        log.info("======= AROUND method name = {} =======", task);
        log.info("[ExecutionTime] {} (ms)", executionTime);

        return result;
    }

 

예시 들어, 위의 코드는 @Around를 사용해서 실행 시간을 체크하는 AOP 코드이다.

 

AOP 용어 정리

AOP 프록시

AOP 기능을 구현하기 위해 만든 프록시객체, 스프링에서는 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시 이다.

조인 포인트 (Join Point)

어드바이스가 적용될 수 있는 위치로, AOP를적용할 수 있는 모든 지점이라 생각하면 된다.

스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메소드 실행 지점으로 제한된다.

포인트컷 (Pointcut)

조인 포인트 중에서 어드바이스(qnrk rlsmd)를 어디에 적용할 지, 적용하지 않을 지 위치를 판단하는 필터링하는 기능 (주로 AspectJ 표현식을 사용해서 지정 )

프록시를 사용하는 스프링 AOP 

타겟 (Target)

어드바이스를 받는 객체, 포인컷으로 결정

어드바이스 (Advice)

부가 기능

특정 조인 포인트에서 Aspect에 의해 취해지는 조치

Around(주변), Before(전), Aftrer(후)와 같은 다양한 종류의 어드바이스가 있음

애스펙트(Aspect)

어드바이스 + 포인트컷을 모듈화 한 것

하나의 어드바이스만이 아닌 여러 어드바이스와ㅏ 포인트컷이 함께 존재할 수 있다.

어드바이저(advisor)

하나의 어드바이스와하나의 포인트 컷으로 구성

즉, 어드바이스 + 포인트 컷 = 어드바이저

 

스프링 AOP 적용 ( @Aspect )

implementation 'org.springframework.boot:spring-boot-starter-aop' // 스프링 aop 추가

  

위의 라이브러리를 추가하면 aspectJ 관련 라이브러리를 등록하고, 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록해줍니다.

이러한 스프링 부트의 자동설정은 "AnnotationAwareAspectJAutoProxyCreator"라는 빈 후처리기가 스프링 빈에 자동으로 등록해주는데, 이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기입니다. 

내부 호출 이슈

내부 호출 문제라는 것은 같은 클래스 안에서 어떤 메서드를 또다른 메서드 안에서 호출할 때 발생하는 문제를 뜻한다.

내부 호출할때 this.~~~ 를 호출하는 것이다. 여기서 this는 프록시 객체가 아닌 실제 객체를 의미하기 때문에 당연하게도 내부 호출된 메서드는 프록시 기능이 적용되지 않는 것이다.

 

이와 관련된 부분은 다음 블로그에 계속 적겠습니다.

 

### 출처

https://hstory0208.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-AOPAspect-Oriented-Programming%EB%9E%80-Aspect

https://memodayoungee.tistory.com/114

https://yein-lee.tistory.com/47

https://velog.io/@youjung/Spring-AOP