AOP 가 무엇일까
Aspect Oriented Programming
어떤 로직을 핵심적인 관점과 부가적인 관점으로 나누어 그 관점을 기준으로 모듈화하는 프로그래밍.
여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 반복 작업을 줄이고 핵심 비즈니스 로직 개발에만 집중할 수 있다.
Spring AOP는 프록시 기반으로 JDK Dynamic Proxy와 CGLIB을 활용하여 AOP 제공.
사용 목적에 따라
- 프록시 패턴: 클라이언트가 타깃에 접근하는 방법 제어
- 데코레이터 패턴: 타깃에 부가적인 기능 부여
프록시 동작 방식
- 클라이언트로부터 타켓을 대신해서 요청을 받는 대리인.
- 실제 오브젝트인 타겟은 프록시를 통해 최종적으로 요청받아 처리함.
- 따라서 타겟은 자신의 기능에만 집중하고 부가기능은 프록시에게 위임함.
AOP 의 용어들
Aspect
- 흩어진 관심사를 모듈화 한 것
- 부가 기능을 정의한 Advice 와 어드바이스를 어디에 적용할 지 결정하는 Pointcut 을 함께 가짐.
Joinpoint
- 프로그램이 실행될 떄 발생하는 여러 위치
Target
- Advice가 적용될 핵심 객체
Weaving
- 지정된 객체에 애스팩트를 적용해서 새로운 프록시 객체를 생성하는 과정
동적 프록시
JDK 동적 프록시
- Reflection을 통해 동적으로 proxy 객체 생성
- interface를 기준으로 proxy 생성
CGLIB (Code Generator Library) proxy
- 클래스 상속을 통해 proxy 객체 생성
- interface, class 기준으로 proxy 생성
- 타겟 클래스의 바이트코드를 조작하여 재정의 하기때문에 final 사용 불가
리플렉션이 아닌 바이트 조작을 사용하며, 타겟에 대한 정보를 알고 있기 때문에 JDK Dynamic Proxy에 비해 성능이 좋다.
프록시 팩토리
- 실제 객체의 인터페이스의 유무에 무관하게 프록시 객체를 생성 가능
- 둘을 개념적으로 추상화 한 것
빈 후처리기
- 프록시 객체를 빈으로 등록해야 하므로 수동 빈 등록만 가능하고 Config 에서 모든 빈마다 프록시 객체를 생성 해줘야하는 번거로움이 있다.
- 객체를 빈으로 등록하기 직전에 프록시 객체로 바꿔줄 수 있는 빈 후처리기
- 자동 빈 등록도 프록시가 적용 가능하며, 일일이 프록시 객체를 생성할 필요 없다.
- 자동 프록시 생성기는 2가지 일을 한다.
- @Aspect 를 보고 어드바이저( Advisor )로 변환해서 저장한다.
- 어드바이저를 기반으로 프록시를 생성한다.
- 자동 프록시 생성기는 2가지 일을 한다.
AOP 구현 방법
런타임 시점
- 런타임에 프록시 객체를 생성하여 공통 기능을 삽입하는 방법
- 스프링 AOP가 사용하는 방법
- A라는 클래스 타입의 Bean을 만들 때 A 타입의 Proxy Bean을 만들어 Proxy Bean이 Aspect 코드를 추가하여 동작하는 방법
클래스 로딩 시점
- 컴파일 시점에 코드에 공통 기능을 삽입하는 방법
- 컴파일한 뒤, 클래스를 로딩하는 시점에 클래스 정보를 변경하는 방법
컴파일 시점
- 컴파일 시점에 바이트 코드를 조작하여 AOP가 적용된 바이트 코드를 생성하는 방법
Spring 에서 AOP 를 구현한 방법
클라이언트에서 타겟을 호출하게 되면 타겟이 아닌, 타겟을 감싸고 있는 프록시가 호출된다.
이 때 프록시는 타겟 메소드 실행 전후로 부가 기능을 실행하도록 구성되어 있다.
다만, 프록시 패턴은 타겟 하나 하나마다 프록시 객체를 정의해야 하므로 Spring AOP에서는 런타임 시에 JDK Dynamic Proxy 또는 CGLIB를 활용하여 동적으로 프록시를 생성해 준다.
프록시 팩토리의 서비스 추상화 덕분에 구체적인 CGLIB, JDK 동적 프록시 기술에 의존하지 않고, 매우 편리하게 동적 프록시를 생성할 수 있다.
Spring framework 4.3 에 올라와서 기본 프록시 라이브러리로 CGLib을 사용.
스프링 부트 자동 설정으로 AnnotationAwareAspectJAutoProxyCreator 라는 빈 후처리기가 스프링 빈에 자동으로 등록된다.
빈 후처리기를 통해 프록시를 직접 스프링 빈으로 등록하는 문제, 컴포넌트 스캔을 사용하는 경우 프록시 적용이 안되는 문제를 해결했다.
스프링 부트는 AOP를 적용할 때 기본적으로 proxyTargetClass=true 로 설정해서 사용한다. 따라서 인터페이스가 있어도 항상 CGLIB를 사용해서 구체 클래스를 기반으로 프록시를 생성한다.
스프링은 최종적으로 스프링 부트 2.0에서 CGLIB를 기본으로 사용하도록 결정했다. CGLIB를 사용하면
JDK 동적 프록시에서 동작하지 않는 구체 클래스 주입이 가능하다. 여기에 추가로 CGLIB의 단점들이 이제는 많이 해결되었다. CGLIB의 남은 문제라면 final 클래스나 final 메서드가 있는데, AOP를 적용할 대상에는 final 클래스나 final 메서드를 잘 사용하지는 않으므로 이 부분은 크게 문제가 되지는 않는다.