ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring: AOP의 기본적인 개념 & SpringAOP 훑어보기
    각종 학습 요약/Spring 2022. 6. 20. 13:05

    AOP의 기본적인 개념 & SpringAOP 훑어보기

    이 글에서는 Java안에서 다뤄지고 있는 AOP 개념에 관한 주제들을 간략하게 훑어봅니다. 근데 SpringAOP가 주인공이에요.

    AOP란?


    Aspect Oriented Programming. 한글로 관점(관심)지향 프로그래밍. 처음에 이름만 봐서는 무슨 뜻인지 전혀 와닿지 않았고, 지금 봐도 솔직히 왜 저런 이름인지는 모르겠어요. ㅋㅋ.
    AOP 개념을 한 마디로 요약하면, 원하는 시점에 작성된 코드에 개입하는 것입니다.
    이 설명을 들으면, 질문이 많은 사람이라면 이런 질문을 떠올릴 수 있을 거 같아요. '개입할 거면, 그냥 애초에 코드로 작성하면 되잖아?'
    맞아요. 그냥 코드 안에 같이 작성해도 되겠죠. 언제 개입을 고려하면 좋을까요? 왜 이런 기능이 생겨났을까요?

    1. 만약, 개입해야 하는 코드 부분의 갯수가 1만개라면? 10만개라면? 1~10만번 복붙할 건가?
    2. 개입할 기능이 변경될 여지가 있는 것이라면? 혹은 개입 범위(특정 코드 앞에 넣었다가, 특정 코드 뒤로 옮긴다든지)를 조정할 필요성이 있다면? 10만번의 코드를 다시 수정할 건가?
    3. 메인 비즈니스 로직과는 상관없지만, 중간중간 삽입해보고 싶은 코드(예를 들면 로그를 찍어본다든지)가 있을 때. 잘 돌아가고 있는지 궁금해서 println() 1만번 찍었다가, 전부 지울 건가?

    더 많은 경우를 고려할 수도 있겠지만, 저라면 위의 경우들에서 AOP의 필요성이 체감될 것 같아요.

    AOP의 주요 개념 빠르게 훑어보기


    • Aspect : 관심사를 묶어놓은 단위. 그런 모듈.
    • Target : 개입을 당하는 코드 대상.
    • Advice : 참견할 내용.
    • JoinPoint : 참견할 지점. 좀만 더 설명하자면, JoinPoint는 생성자 호출, 필드 접근/호출, 메서드 수행 등의 시점이 될 수 있다.
    • PointCut : 구체적인 참견 내용. JoinPoint에 대해 어떻게 적용될 건지 구체적으로 명시한다.

    그리고 아마 엄청 자주 접하게 될 단어

    • 공통관심사 cross-cutting concern : 여기 저기에서 공통적으로 사용할 필요가 있는 기능. 앞서 예로 들었듯이, 개입해야 하는 코드가 1만~10만 군데라면, 일일히 기입해주는 것보다 해당 코드를 '공통관심사'로 묶어서 AOP로 관리하는 편이 낫겠죠. 고럴 때 사용하는 단어입니다. 모듈로 묶여질 코드 말이에요. 메인 비즈니스 로직과 상관이 없는 인프라 로직(로그를 찍는다든지, 수행시간을 기록한다든지..)이 주된 대상입니다.

    Java에서 AOP 다루기 1. AspectJ


    Java에서 AOP를 다루는 방법은 크게 두 가지가 있지 않을까 싶은데요. 첫번째는 AspectJ라는 라이브러리를 사용하는 방법입니다.
    SpringAOP에 비하면 굉장히 복잡한 기능을 지원합니다. 필드, 클래스, 메소드 등 오만군데에서 참견할 수 있어요!
    그게 가능한 이유는, AspectJ는 컴파일 타임에 코드에 관여하기 때문입니다. 아래의 두 경우에 속해요.

    1. .java 파일을 .class로 컴파일 하는 과정에 개입하거나
    2. .class 파일이 클래스로더에 의해 로드될 때

    Java에서 AOP 다루기 2. SpringAOP with Proxy Pattern


    런타임에서는 컴파일타임 때처럼 강력한 Aspect 적용은 불가능하지만, 이것만으로도 충분히 유용하게 활용을 할 수 있습니다.
    만약에 런타임에서 AOP를 구현해야한다면 꼭 알아두어야 할 개념은 Proxy Pattern(위키백과)이에요.
    프록시패턴이란, 본래 수행될 동작이 특정 클래스에 구현되어있다면, 해당 클래스를 상속한 Proxy Class가 가로채서 필요한 기능을 앞뒤로 덧붙여 놓고, 해당 메서드(기능이 덧붙여진 메서드)를 실행하는 것이죠.
    그럼 또 질문이 생길 수 있을 것 같아요. '그렇다면 인스턴스를 생성할 때 원본 class가 아닌 Proxy Class를 생성해서, Proxy Class의 메서드를 실행해야 하잖아? OriginClass cls = new OriginClass(); 해도 알아서 참견되는게 아니라, OriginClass cls = new ProxyClass();해서 말이야.'
    맞습니다. 하지만 누군가가 DI를 통해 OriginClass에 ProxyClass를 주입해준다면 어떨까요? 비즈니스 로직에서는 OriginClass라는 타입만 가지고 사용할 테니 감쪽같겠죠! 근데 또 마침 저희가 사용중인 Spring은... 컨테이너가 DI를 해주는 아주 좋은 웹프레임워크였으니 문제될 것이 없겠죠!

    정리하자면, SpringAOP는 Proxy pattern과 DI를 이용한 메서드 단위의 JoinPoint를 제공합니다. 애노테이션 기반으로 구현이 되는데요. 구체적으로 어떤 코드로 구현되는지는 백기선님의 영상 설명으로 대체하고, 구체적으로 어떤 시점들에 참견을 할 수 있는지 알아볼게요.

    언제 참견할 건지


    위에서 말했듯, Spring AOP의 Aspect는 Proxy Pattern으로 구현 된 런타임 기반입니다. 그래서 메서드 단위로 적용이 되어요.
    이 메서드 단위로, 언제 참견을 실행할 건지를 결정할 수 있는데 그걸 흔히 'Advice의 종류'라고 말합니다. 이 종류가 어떤 것들이 있는지 살펴볼게요.

    예를 들건데요.
    어떤 사람이 자기소개를 하려고 "안녕하세요."라고 말한다고 해볼게요. 근데 누군가 옆에서 서가지고, 마치 자기소개 하던 사람이 말하는 것처럼 참견을 덧붙이는 거에요. 참견 내용은 이런 거라고 가정해볼게요. "이름은 Nick입니다."

    • 원문(Targer) : "안녕하세요"
    • 참견할 내용(Advice) : "이름은 Nick입니다"

    그럼 이제 참견할 수 있는 경우들을, 예시를 빗대어 하나씩 살펴볼게요.

    1. @Before

    "이름은 Nick이야"
    "안녕하세요."

    Before로 구현된 Advice가 Target과 만나는 지점(JoinPoint)의 모습은, 원문보다 참견 내용이 우선되는 형태입니다. 쉽게 말해 원래 실행될 내용보다 앞서 실행되어요.
    Advice가 실행되던 중에 예외가 발생하면 Target이 실행되지 않고 메서드 실행이 종료된다는 점을 유의해야 합니다.
    Before Advice의 리턴 타입은 대개 void입니다.

    코드 예제 :

    @AfterReturning(value = "참견할 메소드(ex: com.blahblah.OrderService() )", returning = "result")
    public void doReturn(JoinPoint joinPoint, Object result) throws Throwable {
        log.info("[return] {} return={}", joinPoint.getSignature(), result);
    }
    1. @After

    "안녕하세요"
    "이름은 Nick이야"

    예시가 있으니 쉽죠? 뒤에 실행됩니다. After Advice는 Target에서 예외가 발생하더라도, 마치 finally 구문처럼 무조건 실행이 됩니다.

    코드 예제 :

    @After(value = "참견할 메소드(ex: com.blahblah.OrderService()")
    public void doAfter(JoinPoint joinPoint) throws Throwable {
        log.info("[after] {}", joinPoint.getSignature());
    }
    1. @AfterReturning

    "안녕하세요"
    "이름은 Nick이야"

    어라? 똑같아보이죠? 하지만 'Returning'이라고 쓰여있듯이, AfterReturning Advice는 정상적으로 Return될 때에 실행됩니다. 그렇다면 당연히 예외 없이 정상적으로 수행되었을 경우에만 실행이 되겠죠?
    그러면 예외가 발생하든지 말든지(After), 예외가 발생 안할 때(AfterReturning)를 확인했으니까 다음은...

    코드 예제 :

    @AfterReturning(value = "참견할 메소드(ex: com.blahblah.OrderService()", returning = "result")
    public void doReturn(JoinPoint joinPoint, Object result) throws Throwable {
        log.info("[return] {} return={}", joinPoint.getSignature(), result);
    }
    1. @AfterThrowing

    "안녕하세요" (실행되지 않음)
    "이름은 Nick이야"

    네. 그렇습니다. 이 기능을 활용한다면 오류가 발생했을 때 로그를 찍기 좋겠어요!
    이 Advice는 애노테이션/메서드 매개변수로 Exception을 넘겨주는데, 이때 매개변수명이 일치해야만 합니다.

    코드 예제 :

    @AfterThrowing(value = "참견할 메소드(ex: com.blahblah.OrderService()", throwing = "exception")
    public void doThrowing(JoinPoint joinPoint, Exception exception) throws Throwable {
        log.info("[exception] {} message={}", joinPoint.getSignature(), exception.getMessage());
    }
    1. @Around

    내 이름이 뭘까
    안녕하세요
    Nick이라고 해

    대상 메서드 실행 전후로 수행됩니다. JoinPoint를 실행할건지, 반환 값을 그대로 반환할지 말지, 예외 사항에 대해 어떻게 처리할 건지 등을 조작할 수 있는 굉장히 강력한 기능입니다.

    코드 예제 :

    @Around(value = "참견할 메소드(ex: com.blahblah.OrderService()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //실행할 around - 타겟 이전에
    
        // 타겟 수행
        Object proceed = joinPoint.proceed();
    
        //실행할 around - 타겟 이후에
    
        return proceed;
    }

    마무리


    이렇게 AOP의 개념을 간단히 예로 설명하고, Spring AOP에 대해서도 알아보았습니다.
    만약에 Java가 AOP를 어떻게 다루고 있는지 좀 더 자세히 알아보고 싶다면 AspectJ 라이브러리에 대해 공부해보면 좋겠습니다.
    저는 일단 SpringAOP에 대해서 만이라도 좀 더 공부를 해야할 것 같네요!
    그럼 이번 글도 이렇게 마치도록 하겠습니다. 감사합니다!

    댓글

Designed by Tistory.