Spring/Spring Core Advanced

Spring AOP

민철킹 2022. 1. 25. 21:34

Aspect

공통적인 부가 기능인 횡단 관심사를 위해 만들어진 것이 바로 Aspect이다.

Aspect는 관점이라는 뜻인데, 애플리케이션을 바라보는 관점을 각각의 기능에서 횡단 관심사 관점으로 본다는 의미이다.

 

이렇게 횡단 관심사를 처리하는 Aspect를 사용한 프로그래밍 방식이 AOP(Aspect Oriented Programming)!!

이는 객체 지향을 대체하는 것이 아닌 OOP만으로 처리하기 어려운 횡단 관심사를 보조하기 위한 목적을 가지고 있다.

 

Spring이 제공하는 Advisor, Advice, Pointcut 또한 하나의 Aspect라고 볼 수 있다.

 


 

AOP

AOP의 가장 핵심 아이디어는 횡단 관심사를 분리하여 한 곳에서 관리하도록 하는 것이다.

 

AOP 적용 방식

AOP에서 횡단 관심사를 처리하는 방식은 크게 3가지가 존재한다.

  1. 컴파일 시점
  2. 클래스 로딩 시점
  3. 런타임 시점(Proxy)

 

1. 컴파일 시점

컴파일러는 .java파일을 컴파일해 .class를 만든다.

.class를 만드는 시점에 횡단 관심사를 추가해 AOP를 적용할 수 있다.

하지만 이는 AspectJ가 제공하는 컴파일러를 사용해야하는데, AspectJ 컴파일러가 해당 클래스가 Pointcut 적용 대상인지를 확인하고 횡단 관심사를 추가한다.(바이트 코드 조작)

이렇게 원본에 횡단 관심사를 추가하는 것을 위빙이라한다.

이 방식은 AspectJ의 컴파일러를 사용해야하고 복잡하다는 단점이 존재한다.

 

2. 클래스 로딩 시점

자바의 동작과정에 대해 알고 있는 사람이라면 클래스 로딩의 뜻을 알고 있을 것이다.

컴파일러를 통해 생성된 .class파일은 JVM의 클래스 로더에 보관되고 클래스 로딩을 통해 JVM위에서 동작한다.

클래스 로딩 시점은 JVM에 저장하기 전에 횡단 관심사를 추가해 바이트 코드를 조작하는 방식의 AOP를 의미한다.

이 방식은 자바를 실행할 때 java -javaagent를 통해 조작기를 지정해야하므로 번거롭고 운영이 어렵다는 단점이 존재한다.

 

3. 런타임 시점

런타임 시점은 자바의 메인 메서드가 실행된 다음을 의미한다.

따라서, Spring Container, Bean Post Processor등의 도움을 받아 횡단 관심사를 적용해야하는데 최종적으로 Proxy를 통해 Spring Bean에 횡단 관심사를 적용하는 방식의 AOP이다.

이 방식은 Proxy를 사용하기 때문에 바이트 코드를 조작하는 다른 방식에 비해 제약이 있다.

 


 

AOP 적용 위치

  • AOP 적용 가능 지점(JoinPoint): 생성자, 필드 값 접근, static 메서드 접근, 메서드 실행

 

컴파일 시점과 클래스 로딩 시점의 AOP는 바이트코드를 조작하기 때문에 모든 지점에 횡단 관심사를 적용할 수 있다.

하지만, 런타임 시점의 Proxy방식(Spring AOP)는 메서드 실행 시점에만 AOP를 적용할 수 있다.

 

  • Proxy는 메서드 오버라이딩 방식으로 동작하므로, 생성자나 static 메서드, 필드 값 접근에 적용될 수 없다.
  • 즉, Spring AOPSpring Container가 관리하는 Spring Bean에만 적용 가능하다.

 

Spring AOP는 제약이 많지만 대부분의 복잡한 문제를 해결하기에 충분하고 별도의 추가 설정없이 Spring만으로 편리하게 AOP를 사용할 수 있다!!

 


 

AOP 용어

  • JoinPoint
    • AOP를 적용할 수 있는 모든 지점
    • Spring AOPJoinPoint는 메서드 실행 지점
  • Pointcut
    • JoinPoint 중에서 Advice가 적용될 위치
    • AspectJ Expression을 사용
    • Spring AOP는 메서드 실행 지점만 Pointcut으로 선별 가능
  • target
    • Advice가 적용되는 객체, Pointcut으로 결정됨
  • Advice
    • 부가 기능(횡단 관심사)
    • Around, Before, After와 같은 다양한 종류의 Advice가 있음
  • Aspect
    • Advice + Pointcut을 모듈화한 것
  • Advisor
    • 하나의 Advice와 하나의 Pointcut으로 구성
  • AOP Proxy
    • JDK Dynamic Proxy, CGLIB Proxy

 


 

Spring AOP 구현

1. @Around 사용

  • @AroundAspectJ Expression을 사용해 적용할 범위인 pointcut을 지정해준다.
  • 여기서 doLog메서드가 Advice가 된다.
  • Advice를 적용하고 joinPoint.proceed()를 통해 Target을 호출한다.

 

2. @Pointcut 사용

  • @Pointcut을 사용해 pointcut을 별도의 메서드로 분리하고 @Around에서 호출하는 방식으로 AOP를 적용한다.
  • 이 때 호출되는 메서드와 파라미터를 합쳐 pointcut signature라고 부른다.
  • 메서드의 반환 타입은 반드시 void여야하고, 내용은 비워준다.
  • 만약 다른 Apsect에서 참고하려면 public접근 제어자를 사용하자.
  • 분리하면 하나의 포인트컷 표현식을 여러 곳에서 함께 사용할 수 있다는 장점이 존재한다.

 

3. @Pointcut 조합

  • @Around에는 &&, ||, !을 사용해 pointcut을 조합할 수 있다.

 

4. @Pointcut 모듈화

  • 별도의 클래스에 @Pointcut을 모아 모듈화를 진행한다.
  • 외부에서 참조할 것이기 때문에 접근 제어자를 public으로 변경했다.

 

  • 참조할 포인트컷 모듈의 패키지명.클래스명.메서드명@Around에 작성해 외부에 있는 @Pointcut을 가져와 적용할 수 있다.

 

5. Advice 순서 지정

AOP를 적용할 때 Advice들의 순서를 지정해야할 때가 있을 것이다.

Spring에서 제공하는 @Order애노테이션을 사용해 순서를 지정할 수 있지만 이는 클래스 단위에 적용되기 때문에 하나의 @Aspect안에 여러 Advice가 존재한다면 사용할 수 없다.

이럴 때는 불편하지만 Advice를 쪼개야한다.

  • 각각의 클래스로 분리하거나 하나의 껍데기 클래스를 만들고 내부 클래스로 각각 @Aspect을 적용하고 @Order를 사용해 순서를 지정해주면된다.
  • @Order의 숫자가 낮은 것이 우선 순위가 높다.
  • 이 때 각각의 @Aspect가 적용된 클래스를 Spring Bean으로 등록해주어야한다.

 


 

Advice 종류

  • @Around: 메서드 호출 전후에 수행되고 가장 강력한 Advice로써 JoinPoint 실행 여부 선택, 반환 값 변환, 예외 반환 등이 가능
  • @Before: JoinPoint 실행 이전에 실행
  • @After Returning: JoinPoint가 정상 완료후 실행
  • @After Throwing: 메서드가 예외를 던지는 경우 실행
  • @After: JoinPoint의 동작에 관계없이 무조건 실행(finally)

@Before, @After Returning, @After Throwing, @After@Around의 기능을 시점별로 나누어놓은 것이다.

  • 이전에 사용했던 @Around를 각 4개의 시점으로 나눠보면 위와 같이 나눌 수 있다.

 

@Before

  • joinPoint.proceed() 이전의 시점을 사용한다.
  • 참고로 @Before을 사용한다면 jointPoint.proceed()를 직접 호출해주지 않아도 된다.(자동으로 호출해줌)
  • 또한 파라미터로는 ProceedingJoinPoint가 아닌 JoinPoint를 사용해야함을 주의하자.
  • 예외가 발생했다면 다음 코드가 호출되지 않는다.

 

@AfterReturning

  • joinPoint.proceed()가 수행되고 메서드가 정상적으로 반환될 때
  • 여기서는 직접 리턴을 받는 것이 아니기 때문에 그 값을 변경할 수는 없다.(조작은 가능)
  • 애노테이션의 returning 속성으로 이름을 지정해주고 이를 꺼내서 사용할 수 있다.
  • returning절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행(부모 타입을 지정하면 모든 자식 타입은 허용)

 

@AfterThrowing

  • 메서드가 예외를 던질 때 실행되는 로직

 

@After

  • 일반적으로 리소스를 해제하는데 사용

모든 Advice는 JoinPoint를 첫번째 파라미터에 사용할 수 있음(생략해도 됨)

하지만, @Around는 다음 AdviceTarget을 호출해야하므로 ProceedingJoinPoint를 사용해야함

 

@Around

  • 메서드 실행 전후 모두에 작업을 수행할 수 있다.
  • JoinPoint 실행 여부를 선택할 수 있다.
  • 전달 값, 반환 값, 예외를 변환할 수 있다.

 

실행 순서

동일한 @Aspect안에서 동일한 JoinPoint의 우선순위가 정해져있다.

실행 순서 : @Around -> @Before -> @After -> @AfterRetruning -> @AfterThrowing

  • 리턴 순서는 반대이다.

 


 

@Around는 가장 넓은 기능을 제공하지만 생각해야할 범위가 넓어져 실수할 가능성이 존재한다.

하지만 @Before, @After는 기능은 적지만 실수할 가능성이 낮고 코드가 단순해지고 의도가 명확하다.

 

좋은 설계는 제약이 있는 것이다.

 

제약이 실수를 미연에 방지하는 가이드 역할을 해주기 때문이다.

반응형

'Spring > Spring Core Advanced' 카테고리의 다른 글

@Aspect AOP  (0) 2022.01.25
빈 후처리기  (3) 2022.01.15
Spring의 프록시 기술  (0) 2021.12.16
동적 프록시 기술  (0) 2021.11.18
프록시 패턴과 데코레이터 패턴  (0) 2021.11.08