[Spring_4기 본캠프] Spring 플러스 - AOP | Day 63
1. OOP 객체 지향 프로그래밍의 문제점이 떠오르다
- 특정 관심사 업무 코드에 트랜잭션, 보안, 로깅, 접근제어 등의 코드가 존재하게 되면서 핵심 업무와는 관련이 없지만 애플리케이션에 필수적인 부가 기능이 생김
- 관심사 관점에서 이러한 부가 기능들을 횡단 관심사로 부르기로 함
- 비즈니스 클래스에 핵심 기능과 부가 기능들이 공존하게 되면서 메서드의 복잡도가 증가하고 비즈니스 코드 파악이 어렵게 되었다.
- 부가 기능의 불특정 다수 메서드가 반복적으로 구현되면서 횡단 관심사의 모듈화가 어려워짐
- 중복되는 부가 기능에 수정 및 삭제가 필요해지면 사용되는 모든 곳에 수정 및 삭제 동작을 해줘야 함
2. AOP (Aspect Oriented Programming) : 관점 지향 프로그래밍
* AOP 관련 개념
- 어드바이스(Advice) : around, before, after, afterThrowing 등등 타켓에 제공할 부가기능을 담고 있는 모듈
- 포인트컷(Pointcut) : 어드바이스를 적용할 구체적인 범위를 표현하는 규칙 (범위지정.. 즉 파일 경로!)
- 타겟(Target) : 어드바이스가 적용될 객체
- 조인포인트(타겟 안에 메서드) : 어드바이스가 적용되는 지점
- 어스팩트 (위의 것들이 모두 모여 만들어진 하나의 모듈) : 어드바이스와 포인트컷을 하나로 묶은 모듈
* AOP 동작 원리
- 자바 객체를 빈으로 등록할 때 빈 후처리기(BeanPostProcessor)를 거치게 됩니다. 이 때 AOP 설정이 적용된 객체를 감지하고 해당 객체를 프록시 객체로 감싼 후 스프링 컨테이너에 등록함
- Spring AOP 동작 과정 : AOP는 내부적으로 프록시 객체를 생성하고 이를 통해 Advice를 실행
① Pointcut 분석 : Spring은 애플리케이션의 모든 Bean을 분석하여, 설정된 Pointcut에 맞는 Join Point를 찾아냄
② 프록시 객체 생성 : AOP가 적용된 Bean에 대해 프록시 객체를 생성합니다. 이 프록시 객체는 실제 Target 객체의 대리 역할을 수행
③ Advice 실행
- Advice가 설정된 경우 먼저 Advice 코드가 실행됨
- 실제 Target 객체의 메서드가 실행됨
- 필요 시 후처리 Advice가 실행됨
→ Spring AOP는 프록시 객체를 사용해 Target 객체의 메서드 호출을 가로채고, 횡단 관심사를 처리한다!
3. 그래서 AOP를 사용하는 이유가 뭘까?
- 정리하자면, 핵심 기능에 필수적이지만 반복되는 부가 기능들(트랜잭션, 로깅, 접근제어)을 따로 빼내서 관리해주자! 핵심 기능에만 집중할 수 있도록 부가 기능은 AOP가 처리해준다.
4. 프록시 프록시.. 프록시가 뭐에요?
1) 프록시 객체의 특징
- 지연 로딩 : 실제 엔티티를 데이터베이스에서 로드하지 않고 메서드 호출 시점에 데이터베이스에서 로드한다. 이는 초기 로딩 시점에 불필요한 데이터 로딩을 방지하고 성능을 최적화 하는 데에 도움을 준다.
- 프록시 생성 : JPA는 프록시 객체를 생성할 때 원래 엔티티 클래스를 상속받고, 실제 데이터는 필요할 때 로드한다.
- 트랜잭션 범위 : 프록시 객체는 트랜잭션이 유효한 범위 내에서만 동작하며, 트랜잭션이 종료되면 데이터 로딩이 불가능할 수 있다.
→ 프록시 객체는 실제 엔티티에 대한 접근을 지연시키며 실제 엔티티가 필요할 때까지 데이터베이스 쿼리를 실행하지 않는다.
2) 프록시 객체의 동작
- 프록시 초기화 : 프록시 객체가 메서드를 호출할 때, 해당 메서드가 실제 엔티티를 필요로 하는 경우에 데이터베이스에서 데이터를 로드한다.
- 쿼리 실행 : 메서드를 호출하면 프록시 객체는 실제 엔티티를 로드하기 위해 데이터베이스 쿼리를 실행한다.
- 결과 반환
3) 프록시 객체의 장점과 단점
- 장점
> 성능 최적화 : 데이터가 실제로 필요할 때만 로드되므로 불필요한 데이터 로딩 방지
> 메모리 절약 : 초기 로딩 시에 모든 연관 데이터를 로드하지 않아 메모리 사용 절약
- 단점
> N+1 : 여러 엔티티를 처리할 때 연관된 엔티티에 대한 지연 로딩이 반복적으로 발생
> 프록시 초기화 : 프록시 객체가 실제 데이터베이스 쿼리를 발생시키므로 주의 필요
5. Self-Invocation 문제
- 대상 객체 내에서 자신의 메서드를 직접 호출하면 AOP가 적용되지 않는 문제
- 프록시에 외부에서 호출되는 경우에만 트랜잭션을 적용할 수 있다. 하지만 클래스 내부에서 메서드를 호출하면 프록시를 거치지 않고(트랜잭션이 적용되지 X) 실제 메서드를 바로 호출하게 되므로 트랜잭션이 제대로 적용되지 않는다.
- 해결법
> 클래스 분리하기(권장) : 트랜잭셔널이 적용된 메서드를 다른 클래스로 분리하면 해결 할 수 있다. 서로 다른 클래스에서 호출하면 프록시를 통해 트랜잭션이 적용된다.
> 자체 트랜잭션 호출을 AOP로 강제 : Spring AOP와 관련 설정을 이용해 내부 호출을 감지할 수 있도록 설정한다. (But 설정 까다로움)
> Proxy 직접 사용 : Spring이 생성한 프록시 객체를 직접 주입받아 호출하면 트랜잭션을 적용할 수 있다.