BackEnd/Spring

[Spring] AOP : Aspect Oriented Programming

샤아이인 2022. 1. 14.

내돈내고 내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.

섹션 7. AOP

 

 

1. AOP가 필요한 상황

우리가 작성한 코드에 함수가 1000개 있다고 해보자.

문제는 갑자기 위에서 모든 함수들의 시간을 측정해보라는 명령이 내려왔다는 것 이다.

 

AOP를 아직 모르는 사람은 함수 하나를 다음과 같이 변경하였다.

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");
    } 
}
 

함수의 시작과 끝에 시간을 측정하고, 이후 finish - start 를 통하여 걸린 시간을 측정하였다.

 

"후! 이제 999 개만 이렇게 더 고치면 되겠군!!"

 

이방식이 정상적인 방법이라 생각하는가?

 

문제

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

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

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

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

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

 

이때 등장하는 것이 바로 AOP이다!

 

2. AOP의 적용

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

공통 관심 사항은 시간측정에 해당되고, 기존에 서비스 로직들이 핵심 관리 사항에 해당하게 됩니다.

 

코드먼저 확인해 봅시다.

@Aspect
@Component
public class TimeTraceAop {

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

만들어진 AOP 코드를 bean에 등록시켜야 합니다.

AOP를 bean에 등록시킬때는 component scan을 활용해도 되고, 아니면 config에서 bean으로 등록시켜 AOP를 사용한다는걸 명시적으로 보여주는것 또한 좋은 방식이라 하셨다.

 

위의 코드에서 실제 메소드가 호출되는 부분은 joinpoint 부분이다.

조인포인트(Joinpoint) : 클라이언트가 호출하는 모든 비즈니스 메소드, 조인포인트 중에서 포인트컷되기 때문에 포인트컷의 후보로 생각할 수 있다.

 

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

이 에노테이션은 hello.hellospring 페키지 하부에 있는 모든 메소드들에 적용하겠다는 의미이다.

 

모든 메소드의 실행시 시간이 측정됨을 알 수 있다.

 

간략하게 원리를 설명해 주셨다.

AOP에서는 프록시가 사용된다고 알려주셨다. 우선 다음 그림을 살펴보자.

출처 - 인프런 스프링 입문 (김영한) 강의

AOP 스프링 빈에 등록될때 proxy memberService를 하나 생성한다.

따라서 proceed()를 호출할때 비로서 진짜 memberService가 호출된다.

 

다음 코드는 바로위 그림에서본 DI 부분이다. 여기서 getClass()로 확인해보면 알수있다.

@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService){
        this.memberService = memberService;
        System.out.println("memberService: " + memberService.getClass());
    }
    ...
}
 

class이름이 Memberservice가 아닌 MemberService ~ CGLIB 이런식으로 끝난다. 프록시가 대신 DI된 것 이다.

DI가 가능하기때문에 오는 장점이라 할수 있겠다.

 

다음은 AOP적용전 과 의 모습을 그림으로 확인할 수 있다.

출처 - 인프런 스프링 입문 (김영한) 강의

댓글