• 프로젝트를 진행 중에 코드가 반복되는 과정이 있었습니다.
  • 로그인 과정을 확인하는 과정이 반복적으로 나타나는 걸 확인했습니다.
  • 이 과정을 효율적으로 리펙토링 할 필요가 있다 느껴 AOP를 조사 및 적용하였습니다.

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

  • 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하는 것
    (모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것)

 

출처 : https://engkimbs.tistory.com/746(왼), https://shlee0882.tistory.com/206(오)

주요 개념

  • Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.
  • Target : Aspect를 적용하는 곳 (클래스, 메서드 등)
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
  • PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음

특징

  • 프록시 패턴 기반의 AOP 구현체, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해서임
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 등)에 대한 해결책을 지원하는 것이 목적

 AOP를 적용하기 위해서는 두가지의 방식인 AspectJ와 SpringAOP가 있으며 두가지를 비교하여 프로젝트에 해당하는 방식을 조사하였습니다.

AspectJ와 SpringAOP 비교

1. 기능과 목표가 다르다.

  • Spring AOP는 프로그래머가 직면하는 일반적인 문제 해결을 위해 Spring IoC에서 제공하는 간편한 AOP 기능이다. 어디에나 쓸 수 있는 완벽한 AOP 솔루션이 아니라, Spring 컨테이너가 관리하는 Bean에만 사용하려고 만들었고, 실제로 여기에만 AOP를 적용 할 수 있다.
  • AspectJ는 자바코드에서 동작하는 모든 객체에 대해 완벽한 AOP 솔루션 제공을 목표로 하는 기술이다. 성능이 뛰어나고 기능이 매우 강력하지만 그만큼 Spring AOP에 비해 사용방법이나 내부 구조가 훨씬 더 복잡하다. 

2. Weaving 방법이 다르다.

  • Weaving은 공통관심사항(Aspect)의 동작코드(Advice)를 대상 객체(Target)에 연결시켜 관점지향을 구현한 객체로 만드는 과정이다. 좀 더 쉽게 말하면 AOP를 구현하기 위한 바이트코드 조작 방법을 의미한다고 생각하면 된다.

 

두 가지의 방식을 비교하기 위해 차이점으로 Weaving 방법이 다른 점이 있어 Weaving 방법을 조사하던 중 Apring AOP의 구현체 중 CGLIB방식과 JDK Dynamic Proxy방식이 있었습니다.

실제 구현체가 다른 방식으로 동작하기에 어떤 차이점이 있는지 자세히 알아보았습니다.


Spring AOP Weaving

  • Spring AOP는 프록시 기반으로 JDK Dynamic Proxy와 CGLIB을 활용하여 AOP 제공
  • Spring AOP는 런타임 위빙(RTW) 방식을 기반으로 동작

Proxy 패턴

  • 실제 기능을 수행하는 객체 대신 가상의 객체를 사용하여 로직의 흐름을 제어하는 디자인패턴
  • 디자인 패턴 중 구조 패턴에 해당

JDK Dynamic Proxy

  • Java의 리플렉션 패키지에 존재하는 Proxy라는 클래스를 통해 생성된 Proxy 객체를 의미
  • 런타임에 클래스 정보를 가져오는 게 리플렉션 패키지
  • 타깃의 Interface를 기반으로 Proxy를 생성해주는 방식 (프록시 패턴 중 하나)

CGLIB(Code Generator Library)

  • 클래스의 바이트 코드를 조작하여 Proxy객체를 생성해 주는 라이브러리
  • 상속을 이용하여 Proxy화 할 메서드를 오버라이딩하는 방식
  • Class, Method 에 Final사용 불가능
  • 클래스 기반으로 바이트코드를 조작하여 Proxy를 생성하는 방식

JDK Dynamic Proxy와 CGLIB 차이점

출처 : https://gmoon92.github.io/spring/aop/2019/04/20/jdk-dynamic-proxy-and-cglib.html

  • spring AOP에서는 인터페이스 유무에 따라 인터페이스 기반의 프록시 생성 시 Dynamic Proxy를 사용하고 인터페이스 기반이 아닐 시 CGLIB을 사용하는데 spring boot AOP에서는 CGLIB 을 Default로 사용합니다.
  • JDK Dynamic Proxy는 인터페이스를 활용, CGLIB는 상속을 활용함

AspectJ Weaving

AspectJ는 3가지 유형의 Weaving을 제공

  • Compile-Time 위빙(CTW)
    • AspectJ 전용 컴파일러를 이용하여 Aspect 부분과 Target 코드 부분을 입력으로 받고 하나로 엮인 바이트코드(.class)를 생성
    • 컴파일이 완료된 이후에는 앱의 성능에 전혀 영향을 끼치지 않음
  • Post-Compile 위빙(PCW)
    • 외부 라이브러리를 Weaving 할 때 사용
    • Compile-Time 위빙과 거의 동일한 동작
    • 보통 클래스파일을 JAR와 엮기 위해서 사용해서 Binary 위빙(BW)이라고 부르는게 일반적
  • Load-Time 위빙(LTW)
    • 전용 컴파일러를 사용하지않고, 조작되지 않은 바이트코드(.class)를 가 JVM에 로드될 때 ClassLoader를 이용하여 바이트코드를 조작하는 위빙 방식
    • 객체를 로드할 때 위빙이 일어나는 거라 앱 성능의 하락을 발생시킬 수 있음

 

 AspectJ가 Spring AOP 보다 러닝커브가 높긴 하지만 비교적 Spring AOP 보다 AspectJ를 사용하였을 때 성능이 좋으며 모든 객체를 대상으로 적용이 가능한 점으로 인해 AspectJ를 선택하였습니다.


AspectJ 주요 용어

  • Aspect
    • 관점 또는 횡단 관심사
    • AspectJ에서는 @Aspect 어노테이션이 붙은 클래스를 의미합니다.
  • Pointcut
    • 어떤 메서드나 클래스에 Aspect를 적용할 것인지를 정의하는 표현식
    • AspectJ에서는 다양한 Pointcut 어노테이션을 사용하여 정의합니다.
    • JoinPoint, ProceedJoinPoint
  • Advice
    • Aspect의 구현체로, 특정 지점에서 실행되는 코드 블록
    • @Before, @After, @Around 등의 어노테이션을 사용하여 지정합니다.
  • Weaving
    • Aspect 코드가 대상 코드에 삽입되어 실행되도록 하는 과정
    • AspectJ에서는 컴파일 타임 또는 런타임에 weaving이 수행됩니다.

출처 : https://codevang.tistory.com/244


AspectJ의 Pointcut 표현식

    • AspectJ는 Pointcut을 명시할 수 있는 다양한 명시자를 제공
    • 스프링은 메서드 호출과 관련된 명시자만을 지원
    • execution 명시자
      • Advice를 적용할 메서드를 명시할 때 사용
      • 기본 형식 
        • execution([수식어패턴] [리턴타입패턴] [패키지패턴?].이름패턴(파라미터패턴) [수식어패턴?])
          • 수식어패턴: 메서드의 접근 제어자 및 다른 특성을 지정합니다. 생략 가능
          • 리턴타입패턴: 반환 타입을 지정합니다. *는 모든 반환 타입을 의미합니다.
          • 패키지패턴: 메서드가 속한 패키지를 지정합니다. 생략 가능하며, *를 사용하여 모든 패키지를 나타낼 수 있습니다.
          • 이름패턴: 메서드의 이름을 지정합니다. 와일드카드(*)를 사용하여 이름 일부를 나타낼 수 있습니다.
          • 파라미터패턴: 메서드의 파라미터 타입을 지정합니다. 와일드카드(*)를 사용하여 모든 타입을 나타낼 수 있습니다.
          • ?: 해당 부분이 생략 가능
        • Advice를 적용할 메서드를 명시할 때 사용
        • execution( com.people...*(..))**
          • com.people 패키지와 하위 패키지에 속한 모든 클래스의 리턴타입에 상관없이 인자값이 0개 이상인 메서드 호출을 선택합니다.
          • ex) execution(* com.community_server.controller...*(..))
        • execution( com..(..)) && @annotation(@annotation)**
          •  com 패키지와 그 하위 패키지에 속한 모든 클래스의 메서드 중, 특정 어노테이션(@annotation)이 적용된 메서드 호출을 선택합니다.
          • ex) execution(* com.community_server..*(..)) && @annotation(com.community_server.aop.LoginCheck) && @annotation(loginCheck)
  • @Aspect 어노테이션을 이용하는 경우
    • '&&' 연산자를 사용하여 두 표현식을 모두 만족하는 Joinpoint에만 Advice가 적용
    • JoinPointAspectJ에서 제공하는 문법 중 하나
    • ex) @Around("@annotation(com.community_server.aop.LoginCheck) && @annotation(loginCheck)")

Joinpoint

  • Joinpoint는 메서드 매개변수, 리턴 값, throw 된 예외 같은 조인 지점에서 사용할 수 있는 상태에 대한 액세스를 제공하는 AspectJ 인터페이스이다.
  • 또한, 해당 메서드에 대한 정적 정보를 제공한다.

아래의 Advice와 함께 사용이 가능하다.

  • @Before (이전) : 어드바이스 타깃 메서드가 호출되기 전에 어드바이스 기능을 수행
  • @After (이후) : 타깃 메서드의 결과에 관계없이(즉 성공, 예외 관계없이) 타깃 메서드가 완료되면 어드바이스 기능을 수행
  • @AfterReturning (정상적 반환 이후) : 타깃 메서드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
  • @AfterThrowing (예외 발생 이후) : 타깃  메서드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
  • @Around (메소드 실행 전후) : 어드바이스가 타깃 메서드를 감싸서 타깃 메서드 호출전과 후에 어드바이스 기능을 수행

출처 : https://nankisu.tistory.com/6

 

ProceedingJoinPoint

  • JoinPoint의 확장으로, proceed() 라는 메서드를 추가 사용할 수 있습니다.
  • 해당 메소드가 실행되게 되면, 다음 어드바이스 혹은 대상 메서드를 실행합니다.
  • 코드의 흐름을 제어하고, 추가 호출을 진행할 지 여부를 결정할 수 있는 권한을 제공합니다.
  • @Around를 사용시 ProceedJoinPoint 사용 가능합니다.

ProceedingJoinPoint를 활용하여 반환값을 통해 로그인한 유저와 요청하는 유저의 번호를 비교하여, 사용자가 요청하는 유저 정보가 다른 유저에게 조회 및 수정되지 않도록 하는 기능을 구현하였습니다. 이를 위해 @Around 어노테이션을 사용하여 메서드 실행 전체를 감싸고, 확장성을 고려하여 ProceedingJoinPoint를 이용하여 유저 권한을 비교하는 로직을 추가하였습니다.


이론적으로 정리 하였으니 실제 프로젝트에 적용하였습니다.

 

이와 같이 중복되는 코드를 Aspect로 작성하여 세션을 사용한 로그인 체크 로직을 구성하였습니다. 

@Around을 사용하여 ProceedingJoinPoint로 사용자의 유저 정보를 반환하는 로직으로 구성하였습니다.

이를 컨트롤러의 loginUserNumber로 받아 Service단에서 검증하는 방식으로 마무리했습니다.


결과

  • AOP를 적용하기 위한 두 가지 방식(AspectJ, Spring AOP)을 조사하여 프로젝트에 해당하는 기능과 목표를 해당하는 방식을 선정하였습니다. 또한 차이점을 확인하기 위해 Weaving에 대한 차이가 있어 조사하여 비교하였습니다.
  • AOP를 적용하기 전에 필요한 개념(Aspect, Advice, PointCut 등)을 조사 및 이해하여 적용하였습니다.
  • Advice가 실행할 위치를 생각하며 해당하는 어노테이션을 쓰기 위해 JoingPoint와 ProceedingJoinPoint를 조사하였습니다.
  • AOP를 적용함으로써 코드의 관심사를 종단/횡단으로 분리하여 중복되는 코드를 제거하여 다른 모듈의 수정 없이 해당 로직만 변경가능한 상태가 되었습니다. 추가적으로 가독성과 유지보수성이 좋아져 코드 생산성이 높아졌습니다. 

느낀점

  • AOP을 진행하며 다양한 환경에서 적용할 수 있다고 생각했습니다. 추후 로깅 AOP나 예외처리 AOP 등 AOP을 여러 방면으로 적용하고자 합니다.

참고

+ Recent posts