백엔드/스프링, 스프링부트, JPA, Spring Webflux

[Spring Boot] AOP의 개념과 스프링부트에서 AOP

Kangjieun11 2023. 4. 19. 00:02
728x90

 

 

✅  AOP ? 

Aspect Oriented Programming

 

관점지향 프로그래밍이라고 부른다. 

여러 클래스에 나뉜 책임을 한곳으로 모아서 처리하는 접근 방식이다.

 

관심사의 분리를 통해 소프트웨어 시스템의 모듈성을 개선하는 프로그래밍 패러다임

 

 

⏺ AOP는 왜 필요할까?  : OOP의 한계

 

✔️ OOP

객체지향 프로그래밍은 애플리케이션 설계할 때 

역할과 책임에 따라 class를 분리하면서, 

 

재사용성, 유지보수성을 크게 증가시켰다. 

 

✔️ OOP의 단점 

 

 

소프트웨어 시스템에선 로그/보안/트랜잭션과 같은 비기능적인 부분이 굉장히 중요하다.

그러나 비기능적인 부분은 모듈화가 어려웠다. 

 

여러가지 모듈에서 공통적으로 사용되면서, 코드의 중복이 발생되었다.

 

 

'코드의 중복

 

어플리케이션 전체적으로 걸쳐서 흩어져있는 부가기능을 관심사  Aspect라고 한다.


✔️ AOP를 이용해 극복

AOP는 OOP 와 함께 사용되며 OOP가 가졌던 단점을 극복한다.

 

- AOP를 이용하여 비즈니스 모듈에 주요기능만을 포함시킬 수 있다. 

- 비즈니스 코드(핵심로직)를 수정하지 않고 부가기능을 동작하게 만들 수 있다. 

 

 

 

✅ AOP   용어 정리 

 

 

 

Aspect(관심 영역)

AOP에서 모듈화할 대상, 즉 핵심적인 기능 이외의 부가적인 기능들을 모듈화한 것

보안, 로깅, 예외처리 등 비즈니스 로직 외의 기능들이 Aspect가 될 수 있다.

 

Advice(부가 기능)

Aspect에 적용되는 기능, 즉 부가적인 기능입니다. 메서드 실행 전/후, 예외 발생 시 등 특정 지점에서 실행됩니다. Advice는 Before, After, After-Returning, After-Throwing, Around 등의 타입이 있다.

 

JoinPoint(적용 가능한 지점)

Advice가 적용될 수 있는 지점입니다. 메서드 실행 시점, 필드 값 변경 시점, 생성자 호출 시점 등이 JoinPoint가 될 수 있다.

 

PointCut(Advice를 적용할 대상)

JoinPoint 중에서 Advice가 적용될 지점을 선택하는 기능

메서드 이름, 매개변수 타입, 클래스 이름 등 다양한 기준으로 PointCut을 정할 수 있다.

 

Target Object(핵심 기능을 담당하는 객체)

Advice가 적용되는 대상이 되는 객체입니다. 핵심 기능을 담당하는 객체, 즉 비즈니스 로직을 수행하는 객체가 Target Object가 된다.

 

Weaving(Aspect를 핵심 기능과 결합)

Aspect와 Target Object를 결합하는 것을 의미

즉, Aspect의 Advice를 TargetObject의 JoinPoint에 적용하여, Aspect와 핵심 기능을 결합하는 것

이를 통해 Target Object의 핵심 기능과 Aspect의 부가 기능이 함께 동작

 

 

 

✅ 코드로 보면서 이해하기

아래 코드는 핵심로직의 시간을 측정하는 AOP 코드이다. 

(이거 관련한 내용을 적으려다가 AOP 글부터 적게 되었는데.. 순서가 바뀐 느낌이군)

(자세한 내용은 다른 글에 적겠다 ㅎㅎ) 

 

 

지금은 Aspect와 Advice, PointCut, 핵심로직과 횡단로직에 대해 개념을 잡기위한 코드로서 크게 볼것!!

 

 

 

✅ Spring AOP   Framework

스프링에서는 내부적으로 AOP를 지원하는 aop framework를 제공한다. 

 

  • JDK Dynamic Proxy
  • CGLIB Proxy

 

⏺ JDK Dynamic Proxy

 

인터페이스를 이용한 프록시 객체 생성 방식

target 객체와 동일한 인터페이스를 구현하며, 프록시 객체를 target 객체처럼 사용함

-> 인터페이스를 이용하는 방식으로 프록시 객체를 런타임에 생성한다.

 

자바에서 제공하는 API (java.lang.reflect.Proxy)를 사용하며 인터페이스 기반으로 Proxy를 생성한다. 사용방법에는 여러가지가 있지만 개발자는 InvocationHandler 객체의 invoke()메서드를 오버라이딩하여 실제 객체의 정보를 받아오고 조작하면 된다.

 

장점

인터페이스를 구현한 클래스에 대해서만 프록시 객체 생성되는게 보장된다.

자바 표준 라이브러리에서 지원하는 java.lang.reflect.Proxy 클래스를 이용해서 프록시 객체를 생성함 -> 별도의 라이브러리가 불필요

 

 

단점

인터페이스를 구현하지 않은 클래스에 대해서는 AOP 구현불가능

인터페이스 메서드 호출이 일어날때 추가적인 오버헤드 발생 -> 성능이 느려질 수 있음

 

 

 CGLIB Proxy

클래스를 상속받아 프록시 객체를 생성하는 방식

클래스를 상속받아야하므로 <상속이 가능한 클래스>인 경우에만 CGLIB proxy 방식으로 프록시 객체를 생성할 수 있다. 

 

 

장점

인터페이스를 구현하지 않은 클래스에 대해서도 프록시 객체 생성, AOP구현 가능

JDK Interface기반의 프록시보다 더 빠른 성능이 보장될 수 있다..

동적으로 프록시 객체를 생성하므로 런타임시에도 메서드 호출 변경 가능

 

 

단점

  • 클래스 바이트코드를 조작해 프록시 객체를 생성함. (클래스 로딩 시점) 
    이는 JDK Interface기반의 프록시보다 더 많은 자원이 소모되게 됨.
    • +++ 클래스 로딩 시점에 생성된 프록시 객체는 스프링 컨테이너에서 싱글톤으로 관리
  • final메서드, final 클래스/생성사/인스턴스변수에 대해선  프록시 객체를 생성할 수 없다.
    •  final 메서드와 final클래스 : 상속/오버라이딩이 불가능해서 프록시 객체를 생성 할 수 없음
    • 생성자와 인스턴스 변수 : 클래스의 인스턴스가 생성될 때 초기화 되므로, 프록시 객체를 생성할 수 없음

 

 

 

각각의 장단점을 비교하면서 어떤 상황에서 어떤 AOP구현방식을 사용할지 채택해야한다!

 

 

가장 간단한 방식은 아래와 같다.