Spring의 핵심 개념중 하나인 DI가 애플리케이션 모듈들 간의 결합도를 낮춘다면, AOP(Aspect-Oriented Programming)는 핵심 로직과 부가 기능을 분리하여 애플리케이션 전체에 걸쳐 사용되는 부가 기능을 모듈화하여 재사용할 수 있도록 지원하는 것
AOP란 단어를 번역하면 관점(관심) 지향 프로그래밍 즉 프로젝트 구조를 바라보는 관점을 바꿔보자는 의미이다.
각각의 Service의 핵심기능에서 바라보았을 때 User와 Order는 공통된 요소가 없다. 하지만 부가기능 관점에서 바라보면
각각의 Service의 getXX 메서드를 호출하는 전후에 before과 after라는 메서드가 공통되는 것을 확인 할 수 있다.
기존에 OOP에서 바라보던 관점을 다르게 하여 부가기능적인 측면에서 보았을 때 공통된 요소를 추출하자는 것.
이 때 가로(횡단) 영역의 공통된 부분을 잘라냈다고 하여, AOP를 크로스 컷팅(Cross=Cutting) 이라고 부르기도 한다.
OOP : 비즈니스 로직의 모듈화
모듈화의 핵심 단위는 비즈니스 로직
AOP : 인프라 혹은 부가기능의 모듈화
대표적인 예 : 모니터링 및 로깅, 동기화, 오류 검사 및 처리, 성능 최적화(캐싱) 등
각각의 모듈들의 주 목적 외에 필요한 부가적인 기능들
간단하게 한줄로 AOP를 정리하면 AOP는 공통된 기능을 재사용하는 기법 이다.
OOP에선 공통된 기능을 재사용하는 방법으로 상속이나 위임을 사용한다.
하지만 전체 애플리케이션에서 여기저기 사용되는 부가기능들은 상속이나 위임으로 처리하기에는 깔끔한 모듈화가 어렵다. 그래서 등장한 것이 AOP이다.
AOP의 장점
애플리케이션 전체에 흩어진 공통 기능이 하나의 장소에서 관리되어 유지보수가 좋다.
핵심 로직과 부가 기능의 명확한 분리로, 핵심 로직은 자신의 목적 외에 사항들에는 신경쓰지 않는다.
2. AOP 적용 방식
AOP의 적용 방식은 크게 3가지 방법이 있다.
컴파일 시점
.java 파일을 컴파일러를 통해 .class를 만드는 시점에 부가 기능 로직을 추가하는 방식
모든 지점에 적용 가능
AspectJ가 제공하는 특별한 컴파일러를 사용해야 하기 때문에 특별한 컴파일러가 필요한 점과 복잡하다는 단점
클래스 로딩 시점
.class 파일을 JVM 내부의 클래스 로더에 보관하기 전에 조작하여 부가 기능 로직을 추가하는 방식
모든 지점에 적용 가능
특별한 옵션과 클래스 로더 조작기를 지정해야하므로 운영하기 어려움
런타임 시점
스프링이 사용하는 방식
컴파일이 끝나고 클래스 로더에 이미 다 올라가 자바가 실행된 다음에 동작하는 런타임 방식
실제 대상 코드는 그대로 유지되고 프록시를 통해 부가 기능이 적용
프록시는 메서드 오버라이팅 개념으로 동작하기 대문에 멜서드에만 적용가능 -> 스프링 빈에만 AOP를 적용 가능
특별한 컴파일러나, 복잡한 옵션, 클래스 로더 조작기를 사용하지 않아도 스프링만 있으면 AOP를 적용할 수 있기 대문에 스프링 AOP는 런타임 방식을 사용
3. AOP 용어
Join point
추상적인 개념으로 advice가 적용될 수 있는 모든 위치를 말합니다.
ex) 메서드 실행 시점, 생성자 호출 시점, 필드 값 접근 시점 등등..
스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메서드 실행 지점
Pointcut
조인 포인트 중에서 advice가 적용될 위치를 선별하는 기능
스프링 AOP는 프록시 기반이기 때문에 조인 포인트가 메서드 실행 시점 뿐이 없고 포인트컷도 메서드 실행 시점만 가능
Target
advice의 대상이 되는 객체
Pointcut으로 결정
advice
실질적인 부가 기능 로직을 정의하는 곳
특정 조인 포인트에서 Aspect에 의해 취해지는 조치
Aspect
advice + pointcut을 모듈화 한 것
@Aspect와 같은 의미
Advisor
스프링 AOP에서만 사용되는 용어로 advice + pointcut 한 쌍
Weaving
pointcut으로 결장한 타겟의 join point에 advice를 적용하는 것
AOP 프록시
AOP 기능을 구현하기 위해 만든 프록시 객체
스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시
스프링 AOP의 기본값은 CGLIB 프록시
4. Aspecrt
이 내용은Spring - 프록시 팩토리와 빈 후처리기에서 이어지는 내용입니다. 스프링은 빈을 등록할 때, 빈 후처리기에서 모든 Advisor 빈을 조회한 뒤 Pointcut으로 매칭해보면서 프록시 적용 대상인지 판단하고 대상이라면 프록시를 빈으로 등록한다고 했습니다. 따라서 전 포스팅에서는 advice와 pointcut를 구현해서 Advisor를 만들어 빈으로 등록했지만 그 과정이 다소 불편했습니다. @Aspect 애노테이션을 사용한다면 Advisor를 더욱 쉽게 구현할 수 있습니다. 스프링 AOP를 사용하기 위해서는 다음과 같은 의존성을 추가해야 합니다.
해당 의존성을 추가하게 되면 자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)를 사용할 수 있게 되고, 이것이 Advisor 기반으로 프록시를 생성하는 역할을 합니다. 이와 더불어, 자동 프록시 생성기는@Aspect를 보고 Advisor로 변환해서 저장하는 작업을 수행합니다.
자동 프록시 생성기에 의해 @Asepct에서 Advisor로 변환된 Advisor는 @Aspect Advisor 빌더 내부에 저장됩니다.
동작 과정
그럼 자동 프록시 생성기에 의해 성성된 Advisor는 기존 로직에서 어느 시점에 끼어드는지 보겠습니다.
스프링 빈 대상이 되는 객체를 생성한다.(@Bean, 콤포넌트 스캔 대상)
생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.
모든 Advisor 빈을 조회합니다.
@Aspect Advisor 빌더 내부에 저장된 모든 Advisor를 조회합니다.
3,4에서 조회한 Advisor에 포함되어 있는 포인트컷을 통해 클래스와 메서드 정보를 매칭하면서 프록시를 적용할 대상인지 아닌지 판단합니다.
여러 Advisor의 하나라도 포인트컷의 조건을 만족한다면 프록시를 생성하고 프록시를 빈 저장소로 반환합니다.
만약 프록시 생성 대상이 아니라면 들어온 빈 그대로 빈 저장소로 반환합니다.
빈 저장소는 객체를 받아서 빈으로 등록합니다.
Advisor 빈을 조회하고 이후에 @Aspect Advicor 빌더 내부에 저장된 모든 Advisor를 조회하는 로직이 추가된 것을 확인할 수 있습니다.
주의사항
@Aspect는 Advisor를 쉽게 만들 수 있도록 도와주는 역할을 하는 것이지 컴포넌트 스캔이 되는 것은 아닙니다. 따라서 반드시 스프링 빈으로 등록해줘야 합니다. 다음 세가지 방식 중 아무것이나 선택해서 등록하면 됩니다.