LikeTech Main.

Spring AOP

Jihoon Na
Jihoon Na

Introduction

안녕하세요. 오늘은 AOP(Aspect Oriented Programming), 즉 관점지향 프로그래밍에 대해서 알아보도록 하겠습니다. 절차 지향, 객체 지향은 많이 들어보았는데... 관점..? 관점 지향? 처음 들었을 때 전혀 어떤 것인지 감이 오지 않더라구요. 관점 지향 프로그래밍은 어떤 비즈니스 로직이 있으면 그 로직의 핵심이 되는 핵심 관점과 횡단 관점으로 나누어 각각을 모듈화 하는 것을 의미합니다. 횡단 관점..? 구체적인 사례를 바로 보시죠!

오늘도 나신입 사원은 열심히 일을 하기 위해 출근을 했다.

나신입 사원(예약팀) : 안녕하세요 대리님. 부르셨나요?
김경력 대리(예약팀) : 오 어서와요 어서와 이번에 대대적으로 모든 메서드에 대해서 수행 시간을 측정하기로 했어요.
나신입 사원(예약팀) : 오 성능 이슈 때문인가요?
김경력 대리(예약팀) : 네네. 성능이 문제가 되는 메서드들에 대해서 최적화를 하기 이전에 우선은 대상이 되는 것들을 베타 테스트 기간에 실시간으로 측정해보기로 했어요.
나신입 사원(예약팀) : 아 그렇군요. 혹시 제가 담당할 일이 있을까요?
김경력 대리(예약팀) : 네네 모든 메서드에서 시작되는 시간과 끝나는 시간을 측정해서 1초 이상 걸린 로직에 대해서 기록을 남기도록 작업해주세요.
나신입 사원(예약팀) : 넵넵 진행하겠습니다!

자리에 돌아온 나신입 사원은 로깅이 적용되어야하는 메서드들을 본다.

메서드는 총 30개. 그리 많지 않아 얼른 끝낼 수 있겠다는 생각을 한다.

public class ReservationService {

    private ReservationDao dao;

    public Reservation create(Reservation reservation) {
        reservation.validation();
        dao.save(reservation);

    }

    public Reservation modify(Long id, LocalDateTime visitTime) {

        TimeValidator.validation(visitTime);

        Reservation reservation = dao.find(id);
        reservation.set(visitTime);
        dao.save(reservation);

    }

    public Reservation delete(Reservation reservation) {
        if(reservation.status == "취소"){
            throw new Exception("취소 할 수 없는 예약입니다.")
        }
        dao.remove(reservation);

    }

}

이런식으로 메서드들이 있으니까.. 각각의 메서드 내부에서 실행 시간을 측정하는 코드를 넣어 실행해보면 되겠지?

그럼 이런식으로...

public class ReservationService {

    private ReservationDao dao;
    private SpecialLogger logger;

    public Reservation create(Reservation reservation) {
        long beforeTime = System.currentTimeMillis();

        reservation.validation();
        dao.save(reservation);

        long afterTime = System.currentTimeMillis();
        logger.log(afterTime - beforeTime);

    }

    public Reservation modify(Long id, LocalDateTime visitTime) {
        long beforeTime = System.currentTimeMillis();

        TimeValidator.validation(visitTime);

        Reservation reservation = dao.find(id);
        reservation.set(visitTime);
        dao.save(reservation);

        long afterTime = System.currentTimeMillis();
        logger.log(afterTime - beforeTime);


    }

    public Reservation delete(Reservation reservation) {
        long beforeTime = System.currentTimeMillis();

        if(reservation.status == "취소"){
            throw new Exception("취소 할 수 없는 예약입니다.")
        }
        dao.remove(reservation);
        long afterTime = System.currentTimeMillis();
        logger.log(afterTime - beforeTime);
    }

}

ㅎㅎ 이런식으로 넣어서~ 금방 30개만 넣으면 끝이네~~ 정말 쉽다~ 얼른 보고하러 가야지..

나신입 사원(예약팀) : 안녕하세요 대리님. 말씀하신 내용 진행하였습니다.
김경력 대리(예약팀) : 오 어서와요 어서와 어디보자... 읭? 오마이갓~
나신입 사원(예약팀) : 오 생각하시던 방향이랑 다를까요?
김경력 대리(예약팀) : 네네. 음.. 지금 이렇게 해주시면 나중에 로깅 빼달라고 하거나 로깅에 대한 요구사항이 수정되면 어떻게 하실건가요?
김경력 대리(예약팀) : 예를 들어 로깅은 수행시간이 3초 이상이 지나야 보여주라던지.. 이런 시간이 계속 바뀐다던지 하면요.
나신입 사원(예약팀) : 아 .. 하나하나 수정할 수는 없겠죠..
김경력 대리(예약팀) : 그래요.. 지금 이렇게 진행하시면 비즈니스의 핵심 로직과 로깅같은 부가적인 로직이 혼재되어서 유지관리하기가 어려워져요.
나신입 사원(예약팀) : 아 네.. 그럼 어떻게 진행할 수 있을까요?
김경력 대리(예약팀) : AOP라고 검색해서 그걸 보고 진행하도록 하세요. 이런 부가적인 로직 즉 횡단 관점의 로직들은 별개로 관리하는게 좋아요.
나신입 사원(예약팀) : 아 넵 알겠습니다!

횡단 지향 프로그래밍...?

아니 절차 지향, 객체 지향은 들어봤어도 대체 그게 뭐지 관점..? 관점?!

그래 인터넷엔 없는게 없지. 기술 블로그를 한번 찾아봐야겠다! 오 나랑 비슷한 일을 한 사람이 있네 이 글을 봐야지!

diagram

위 그림을 보면 숙소 예약, 예약 취소, 예약 수정에 대해서 각각의 비즈니스 로직이 있을 것이고 공통적으로 이 비즈니스 로직을 가로지르는 즉 횡단하는 공통의 로직들이 존재합니다. 숙소를 예약하거나 취소하거나 예약을 수정할 때 모두 정보에 대해서 로깅을 해야하고 인증을 해야하고 트랜젝션을 관리해야합니다. 따라서 이러한 로직들은 핵심적인 로직이라기 보다는 여러 로직을 횡단하는 황단 관점의 로직들이라고 할 수 있습니다. 이러한 로직들을 각각의 핵심 비즈니스 로직 내부에 넣는다면 코드가 분산되고 유지 보수가 어려워지겠죠? 그렇기 때문에 이런 로직들을 핵심 비즈니스 로직에서 분리해서 좀더 관리가 용이하도록 하는 것이 관점 지향 프로그램의 핵심 내용입니다.

스프링 AOP 관련 핵심 용어를 먼저 정리하고 넘어가겠습니다.

  1. Target : 핵심 비즈니스 로직, 즉 핵심 관점을 의미합니다. 위의 그림에서는 숙소 예약, 예약 취소, 예약 수정과 같은 기능의 핵심 비즈니스 로직을 의미합니다.
  2. Advice : 공통의 관심이 되는 로직, 즉 횡단 관점을 의미합니다. 그림에서는 로깅, 인증, 트랜젝션에 해당합니다.
  3. Jointpoint : Advice 즉 횡단 관심 로직을 적용 가능한 지점을 의미합니다.
  4. Pointcut : Jointpoint 중에서 실제로 advice 즉 횡단 관점이 적용되는 jointpoint를 의미합니다.
  5. Aspect : 위에서 언급한 advice와 pointcut를 하나로 합쳐 aspect라고 부른다.
  6. Weaving : 실제 횡단 관점인 advice가 수행되어야하는 jointpoint를 advice로 감싸는 것을 의미합니다.

그럼 스프링 AOP의 특징을 알아보겠습니다.

  1. proxy 기반의 aop 지원 : Spring framework는 target, 즉 핵심 로직 객체에 대한 프록시를 만들어서 제공하고 런타임에 횡단 관심사인 advice를 target에 적용하기 위해, 타겟을 감싸는 프록시가 생성된다.
  2. Intercept of proxy : 위에서 언급한 proxy가 target 즉 핵심 로직에 대한 호출을 가로체고 횡단 로직을 수행한 후 다시 핵심 로직을 수행하도록 한다.
  3. Only method jointpoint : Spring AOP는 위에서 언급한 것 처럼 동적 프록시 기반이므로 메서드 조인 포인트만 지원한다.

Spring AOP는 위와 같은 특징을 가지고 있습니다. 프록시 패턴으로 구현되어 런타임에 실제 핵심 로직의 호출을 가로체어 동작하기 때문에 메서드 조인 포인트만 지원됩니다.

그럼 구체적으로 어떤 종류의 advice들이 있는지 알아보겠습니다.

  1. Before : target 메소드가 실행되기 이전에 advice 수행
  2. After : target 메소드의 성공 여부 관계 없이 이후 advice 수행
  3. AfterReturning : target 메소드가 성공적으로 종료되어 값을 반환한 경우 advice 수행
  4. AfterThrowing : target 메소드 수행 도중 예외를 던지면 advice 수행
  5. Around : target 메소드 호출 전과 호출 후에 advice 수행

오호 .. 그러면 나도 한번 적용을 해봐야지...

그럼.. 이 블로그 내용에 따르면 이미 우리 제품에 있는 각각의 메서드들이 target이고, Advice는 내가 지금 넣어야하는 코드인 실행시간 측정 기능이고... 그러면.. 이 코드가 적용되는 메서드들을 어노테이션으로 구분하기 위해 어노테이션을 먼저 정의해야겠다..

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

좋아 일단 내가 사용할 어노테이션을 정의 했으니까 이제 이 어노테이션이 쓰인 메서드에서 어떤 로직이 수행되어야하는지 aspect 코드를 작성해보자....

@Component
@Aspect
public class performanceTimeLoggingAspect {

    private SpecialLogger logger;

    @Around("@annotation(performanceTime)")
    public Object checkPerformanceTime(ProceedingJointPoint jointPoint) throws Throwable{

        long beforeTime = System.currentTimeMillis();

        Object proceed = jointPoint.proceed();

        long afterTime = System.currentTimeMillis();

        logger.log(afterTime - beforeTime);

        return proceed;
    }

}

오호..

이렇게 실제 비즈니스의 핵심 로직이 되는 것과 이런 로깅 같은 부가적인 로직을 분리해서 관리해야 좋은거구나...

그럼 이렇게 정의를 하고 아까 진행했던 걸 다시 보면..

public class ReservationService {

    private ReservationDao dao;

    @performanceTime
    public Reservation create(Reservation reservation) {
        reservation.validation();
        dao.save(reservation);

    }

    @performanceTime
    public Reservation modify(Long id, LocalDateTime visitTime) {

        TimeValidator.validation(visitTime);

        Reservation reservation = dao.find(id);
        reservation.set(visitTime);
        dao.save(reservation);

    }

    @performanceTime
    public Reservation delete(Reservation reservation) {
        if(reservation.status == "취소"){
            throw new Exception("취소 할 수 없는 예약입니다.")
        }
        dao.remove(reservation);

    }

}

오.. 이렇게 간단히 어노테이션만 추가되면 되니 핵심 비즈니스 로직에 변경이 없구나..

리턴 타입이나, 클레스 이름이나 패키지 이름을 이용해서 적용할 수도 있네? (홍보)

역시.. 우리 사수님 최고..

나신입 사원(예약팀) : 안녕하세요 대리님. 말씀하신 내용 다시 진행하였습니다.
김경력 대리(예약팀) : 오 어서와요 어서와 어디보자... 오~~ 오마이갓 너무 잘했자나~~
나신입 사원(예약팀) : 오 ㅎㅎ 이렇게 진행하면 되나요?
김경력 대리(예약팀) : 네네 ㅎㅎ 제가 딱 생각하던 방향이었어요. 수고하셨습니다.
나신입 사원(예약팀) : 넵!! 감사합니다!!!

나신입 사원은 오늘도 무사히 하루를 마칠 수 있었습니다.

결론

AOP 즉 관점 지향 프로그래밍으로 핵심 비즈니스 로직과 부가적인 로직을 분리하여 유지 보수가 용이하도록 할 수 있다.