[Spring 입문] 07. AOP

PADO·2021년 1월 24일
0

Spring 입문

목록 보기
8/8
post-thumbnail

🌱 AOP

AOP가 필요한 상황

AOP

공통 코드 (공통 관심사)를 비즈니스 로직과 분리하여, 실제 로직이 실행되기 전후로 공통 관심사를 실행

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

MemberService 회원 조회 시간 측정 추가

public Long join(Member member) {
        long start = System.currentTimeMillis();

        try {
            validateDuplicateMember(member);
            memberRepository.save(member);
            return member.getId();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("join: " + timeMs + "ms");
        }
    }

참고) 초기 실행 시간은 Class metadata 로딩 등으로 오래 걸릴 수 있다. 따라서 실제 운영에선 서버 올리고 이것저것 호출하는 warm up을 수행한다.

문제

  • 회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.
    → 핵심 비즈니스 로직은 try 안의 세 줄, 시간 측정 로직은 곁다리 로직이다.
  • 시간을 측정하는 로직은 공통 관심 사항이다.
    → 핵심 비즈니스 로직: 핵심 관심 사항, 시간 측정 로직: 공통 관심 사항
  • 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.
  • 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.
    → 시간 측정 전체 로직을 메소드 하나로 뽑기 어렵다.
  • 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.
    → ms를 s로 바꿔라 ... 모든 함수들의 시간 측정 로직을 수정?

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

 

AOP 적용

AOP: Aspect Oriented Programming (관점 지향 프로그래밍)

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

시간 측정 로직을 메소드 별로 붙이는 게 아니라, 한 곳에 모아 원하는 곳에 적용하는 것

시간 측정 AOP 등록

@Aspect
@Component
public class TimeTraceAop {

		@Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println(joinPoint.toString());
        try {
            // 다음 메소드로 진행
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(joinPoint.toString() + " " timeMs + "ms");

        }
    }
}

@Aspect

공통 기능을 제공하는 Aspect 클래스를 작성한다.

@Component

AOP 클래스를 빈으로 등록한다.

SpringConfig.java에서 빈으로 등록 가능

@Bean
    public TimeTraceAop timeTraceAop() {
        return new TimeTraceAop();
    }

→ 직접 @Bean으로 등록했을 때 순환 참조가 발생하는 이유
: TimeTraceAop의 AOP 대상을 지정하는 @Around 코드(@Around("execution(* hello.firstspring..*(..))"))에서 SpringConfig의 timeTraceAop() 메서드도 AOP 대상으로 처리하기 때문이다. 이게 바로 자기 자신인 TimeTraceAop를 생성하는 코드인지라 순환 참조 문제가 발생한다.
반면 컴포넌트 스캔을 사용할 때는 AOP의 대상이 되는 이런 코드 자체가 없기 때문에 문제가 발생하지 않는다.
AOP 설정 클래스를 빈으로 직접 등록할 때는 아래와 같이 AOP 대상에서 SpringConfig를 빼주면 된다.

@Aspect
public class TimeTraceAop {

    @Around("execution(* hello.hellospring..*(..)) && !target(hello.hellospring.SpringConfig)")

    //@Around("execution(* hello.hellospring..*(..))")

    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {...}

}

@Around()

공통 관심 사항을 어디까지 적용할 건지 타겟팅한다.

해결

  • 회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.
  • 시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.
  • 핵심 관심 사항을 깔끔하게 유지할 수 있다.
  • 변경이 필요하면 이 로직만 변경하면 된다.
  • 원하는 적용 대상을 선택할 수 있다.

스프링의 AOP 동작 방식

AOP 적용 전 의존관계

AOP 적용 전에는 Controller가 Service를 의존하여 호출

AOP 적용 후 의존관계

AOP를 적용하여 적용 범위를 지정하면 Spring이 프록시라는 가짜 Service를 생성
→ Container에 Spring bean을 등록할 때 진짜 Spring bean이 아닌 프록시 Spring bean을 등록
→ 프록시 Spring bean에서 joinPoint.proceed() 호출 시 실제 Service를 호출

AOP 적용 후 전체 그림

Container에서 스프링 빈을 관리하여, 실제 대신 Proxy를 만들어 주입을 해주면 되니 DI의 장점 → AOP의 기반이 된다.

0개의 댓글