나의 브을로오그으

#7. [스프링 입문] - AOP 본문

Spring

#7. [스프링 입문] - AOP

__jhp_+ 2022. 7. 11. 22:25

AOP가 필요한 상황

- 모든 메소드의 호출 시간을 측정하고 싶다면?

- 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)

- 회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?

[MemberService]

    /**
     * 회원가입
     */
    public Long join(Member member) {
        long begin = System.currentTimeMillis();
        try {
            validateDuplicateMember(member); // 중복 회원 검증
            memberRepository.save(member);
            return member.getId();
        } finally {
            long end = System.currentTimeMillis();
            long timeMs = end - begin;
            System.out.println("join = " + timeMs + "ms");
        }
    }

이렇게 하면 회원가입 메소드 join() 호출 시 시간이 측정되어 콘솔에 출력될 것이다. 그러나 만약 메소드가 1000개라면? 이 코드들을 1000군데에 집어넣어야 한다.......

천신만고 끝에 모든 메소드에 시간측정 코드를 넣었다...

그런데 이런 코드는 문제가 있다.

- 시간측정은 회원가입, 회원조회 기능에서 핵심 관심 사항이 아니다.

- 시간을 측정하는 로직은 공통 관심 사항이다.

- 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.

- 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.

- 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.

 

AOP(Aspect Oriented Programming)

: 본질은 공통 관심 사항(시간측정과 같은..)과 핵심 관심 사항(비즈니스 로직)을 분리하는 것.

AOP 클래스에 @Aspect를 붙여주고, 스프링 빈으로 등록해준다.(컴포넌트 스캔 또는 수동으로 등록)

그다음 AOP 클래스 안에 메소드에 @Around() 어노테이션을 붙여준다.

이유는 이 공통 관심 사항에 해당하는 어떤 것인지를 지정해주어야 하기 떄문이다.

(문서 참고)

 

[TimeTraceAOP]

package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeTraceAop {

    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long end = System.currentTimeMillis();
            long timeMs = end - begin;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}

- 해결

* 회원가입, 회원조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.

* 시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.

* 핵심 관심 사항을 깔끔하게 유지할 수 있다.

* 변경이 필요하면 이 로직만 변경하면 된다.

* 원하는 적용 대상을 선택할 수 있다.

 

- 동작원리

AOP를 스프링 빈으로 등록하게 되면 실제 앱 실행시 다음과 같이 동작한다.

* 스프링 앱이 실행되면서 스프링 빈들을 생성한다.

이때 AOP가 있으면, 프록시(가짜) 스프링 빈을 진짜 스프링 빈을 의존하도록 하고,

controller가 프록시(가짜)service를 의존하게 된다.

 

예) MemberService의 join() 메소드를 호출했다고 해보자.

이때 controller는 proxyService를 의존하고 있으므로 가짜 service의 join() 메소드가 호출되고

(여기서 이제 timetrace에 있는 execute() 메소드가 호출된다.)

그리고 execute()메소드 안에 있는 joinPoint.proceed()메소드를 호출하면

내부적으로 처리를 해서 진짜 MemberService의 join()이 호출된다.

아까 @Around()안에

"execution(* hello.hellospring..*(..))"

이렇게 작성했기 때문에 AOP가 hellospring 밑의 패키지 전부를 공통 관심 사항으로 삼았기 때문에 저렇게 proxy객체가 생성되고, 의존되어 동작하게 된다.