1. Aspect Oriented Programming with Spring

2023. 5. 16. 17:34Spring Framework/Spring AOP

[공식문서]

AOP(Aspect-Oriented Programming)는 프로그램 구조를 생각하는 또 다른 방법을 제공하여 OOP(Object-Oriented Programming)를 보완합니다. OOP에서의 주요 모듈화 단위는 클래스인 반면, AOP에서는 모듈화 단위가 aspect입니다. aspect는 트랜잭션 관리와 같은 여러 타입과 객체에 걸쳐 있는 문제를 모듈화할 수 있게 해줍니다. 이러한 문제는 AOP 문헌에서 흔히 crosscutting concerns라고 불립니다.

Spring의 주요 구성 요소 중 하나는 AOP 프레임워크입니다. Spring IoC 컨테이너는 AOP에 의존하지 않으므로(AOP를 사용하지 않아도 된다는 의미) AOP를 꼭 사용해야 하는 것은 아니지만, AOP는 Spring IoC를 보완하여 매우 강력한 미들웨어 솔루션을 제공합니다.

AspectJ pointcuts을 사용하는 Spring AOP

Spring은 schema-based approach이나 @AspectJ 애노테이션 스타일을 사용하여 사용자 정의 aspect을 작성하는 간단하고 강력한 방법을 제공합니다. 이 두 스타일 모두 완전히 타입화된 advice과 AspectJ pointcut 언어의 사용을 제공하면서도 여전히 Spring AOP를 사용하여 위빙(weaving)합니다.

이 장에서는 스키마 기반 및 @AspectJ 기반 AOP 지원에 대해 논의합니다. 보다 저수준의 AOP 지원은 다음 장에서 다루어집니다.

Spring Framework에서 AOP는 다음과 같은 목적으로 사용됩니다:

  • 선언적(declarative) 엔터프라이즈 서비스를 제공하기 위해. 가장 중요한 서비스는 선언적(declarative) 트랜잭션 관리입니다.
  • 사용자들이 OOP를 AOP와 함께 사용하여 사용자 정의 aspect를 구현할 수 있도록 하기 위해.

만약 여러분이 일반적인 선언적 서비스나 풀링과 같은 다른 미리 패키지된 선언적 미들웨어 서비스에만 관심이 있다면, Spring AOP를 직접 다룰 필요는 없으며 이 장의 대부분을 건너뛰어도 됩니다.

AOP Concepts

먼저, 중심이 되는 AOP 개념과 용어를 정의해보겠습니다. 이 용어들은 Spring에만 국한된 것이 아닙니다. 불행히도, AOP 용어는 직관적이지 않은 경우가 많습니다. 하지만 Spring이 자체 용어를 사용하는 것보다는 이 용어들을 사용하는 것이 덜 혼란스러울 것입니다.

  • Aspect: 여러 클래스에 걸쳐 있는 관심사의 모듈화입니다. 트랜잭션 관리는 엔터프라이즈 자바 애플리케이션에서 공통 관심사의 좋은 예입니다. Spring AOP에서 aspect는 일반 클래스(스키마 기반 접근 방식)를 사용하거나 @Aspect 애노테이션이 있는 일반 클래스를 사용하여 구현됩니다(@AspectJ 스타일).
  • Join point: 프로그램 실행 중의 한 시점으로, 메서드 실행이나 예외 처리와 같은 순간을 의미합니다. Spring AOP에서 조인 포인트는 항상 메서드 실행을 나타냅니다.
  • Advice: 특정 조인 포인트에서 aspect가 수행하는 동작입니다. 어드바이스의 종류로는 "around", "before", "after" 어드바이스가 있습니다. (어드바이스의 종류는 나중에 다룹니다.) Spring을 포함한 많은 AOP 프레임워크는 어드바이스를 인터셉터로 모델링하며, 조인 포인트 주변에 인터셉터 체인을 유지합니다.
  • Pointcut: 조인 포인트를 매칭하는 조건입니다. 어드바이스는 포인트컷 표현식과 연관되며, 포인트컷이 매칭되는 모든 조인 포인트에서 실행됩니다(예: 특정 이름의 메서드 실행). 포인트컷 표현식에 의해 매칭된 조인 포인트의 개념은 AOP의 핵심이며, Spring은 기본적으로 AspectJ 포인트컷 표현식 언어를 사용합니다.
  • Introduction: 타입을 대신하여 추가 메서드나 필드를 선언하는 것입니다. Spring AOP는 어드바이스된 객체에 새로운 인터페이스(및 해당 구현)를 도입할 수 있게 합니다. 예를 들어, Introduction을 사용하여 빈이 IsModified 인터페이스를 구현하도록 하여 캐싱을 간소화할 수 있습니다. (Introduction은 AspectJ 커뮤니티에서 inter-type 선언으로 알려져 있습니다.)
  • Target object: 하나 이상의 aspect에 의해 어드바이스된 객체입니다. "어드바이스된 객체"라고도 합니다. Spring AOP는 런타임 프록시를 사용하여 구현되므로, 이 객체는 항상 프록시 객체입니다.
  • AOP proxy: aspect contracts(어드바이스 메서드 실행 등)을 구현하기 위해 AOP 프레임워크에서 생성된 객체입니다. Spring Framework에서는 AOP 프록시가 JDK 동적 프록시 또는 CGLIB 프록시입니다.
  • Weaving: aspect와 다른 애플리케이션 타입 또는 객체를 연결하여 어드바이스된 객체를 생성하는 과정입니다. 이는 컴파일 타임(예: AspectJ 컴파일러 사용), 로드 타임, 또는 런타임에 수행될 수 있습니다. Spring AOP는 다른 순수 자바 AOP 프레임워크처럼 런타임에 위빙을 수행합니다.

Spring AOP에서 제공하는 어드바이스 유형:

  • Before advice: 조인 포인트 전에 실행되는 어드바이스로, 조인 포인트로의 실행 흐름을 방해할 수 없습니다(예외를 발생시키지 않는 한).
  • After returning advice: 조인 포인트가 정상적으로 완료된 후(예: 메서드가 예외를 던지지 않고 반환된 경우) 실행되는 어드바이스.
  • After throwing advice: 메서드가 예외를 던져 종료되면 실행되는 어드바이스.
  • After (finally) advice: 조인 포인트가 어떤 방식으로 종료되든(정상 또는 예외 반환) 실행되는 어드바이스.
  • Around advice 메서드 호출과 같은 조인 포인트를 둘러싸는 어드바이스로, 가장 강력한 어드바이스 유형입니다. Around 어드바이스는 메서드 호출 전후에 커스텀 동작을 수행할 수 있으며, 조인 포인트로의 실행을 계속 진행할지, 자체 반환 값을 반환하거나 예외를 발생시켜 조언된 메서드 실행을 단축할지 선택할 책임이 있습니다.

Around 어드바이스는 가장 일반적인 어드바이스 유형입니다. Spring AOP가 AspectJ와 같이 모든 종류의 어드바이스를 제공하므로, 필요한 동작을 구현할 수 있는 가장 덜 강력한 어드바이스 유형을 사용하는 것이 좋습니다. 예를 들어, 메서드의 반환 값으로 캐시를 업데이트해야 한다면, Around 어드바이스 대신 After returning 어드바이스를 사용하는 것이 더 좋습니다. 더 구체적인 어드바이스 유형을 사용하면 더 간단한 프로그래밍 모델을 제공하며 오류 발생 가능성이 적습니다. 예를 들어, Around 어드바이스에 사용되는 JoinPoint에서 proceed() 메서드를 호출할 필요가 없으며, 따라서 이를 호출하지 않아도 됩니다.

모든 어드바이스 파라미터는 정적으로 타입이 지정되므로, 적절한 타입(예: 메서드 실행의 리턴 값 타입)의 어드바이스 파라미터로 작업할 수 있습니다.

포인트컷에 의해 매칭된 조인 포인트의 개념은 AOP의 핵심으로, 이를 통해 객체 지향 계층 구조와 독립적으로 어드바이스를 적용할 수 있습니다. 예를 들어, 선언적 트랜잭션 관리를 제공하는 Around 어드바이스를 서비스 계층의 여러 객체에 걸친 메서드 집합에 적용할 수 있습니다.

Spring AOP Capabilities and Goals

Spring AOP는 순수 자바로 구현되었습니다. 이를 위해 특별한 컴파일 과정이 필요하지 않습니다. 또한, Spring AOP는 클래스 로더 계층을 제어할 필요가 없으므로 서블릿 컨테이너나 애플리케이션 서버에서 사용하기에 적합합니다.

현재 Spring AOP는 메서드 실행 조인 포인트(즉, Spring 빈의 메서드 실행을 어드바이스하는 것)만을 지원합니다. 필드 인터셉션은 구현되어 있지 않지만, 필드 인터셉션 지원이 추가되더라도 Spring AOP의 핵심 API를 손상시키지 않고 구현될 수 있습니다. 필드 접근과 업데이트 조인 포인트를 어드바이스해야 할 필요가 있다면 AspectJ와 같은 언어를 고려해야 합니다.

Spring AOP의 AOP 접근 방식은 대부분의 다른 AOP 프레임워크와 다릅니다. 목표는 가장 완벽한 AOP 구현을 제공하는 것이 아닙니다(비록 Spring AOP가 매우 강력하기는 하지만). 오히려, AOP 구현과 Spring IoC 사이의 긴밀한 통합을 제공하여 엔터프라이즈 애플리케이션에서 흔히 발생하는 문제를 해결하는 데 중점을 둡니다.

따라서 Spring Framework의 AOP 기능은 일반적으로 Spring IoC 컨테이너와 함께 사용됩니다. aspect은 일반적인 빈 정의 문법을 사용하여 구성됩니다(이로 인해 강력한 "auto-proxying" 기능을 사용할 수 있습니다). 이것은 다른 AOP 구현과의 중요한 차이점입니다. Spring AOP를 사용하면 매우 세밀한 객체(일반적으로 도메인 객체)를 어드바이스하는 것과 같은 작업을 쉽게 또는 효율적으로 수행할 수 없습니다. 이러한 경우에는 AspectJ가 가장 좋은 선택입니다. 그러나 우리의 경험에 따르면 Spring AOP는 AOP에 적합한 엔터프라이즈 자바 애플리케이션의 대부분의 문제에 대해 훌륭한 솔루션을 제공합니다.

Spring AOP는 종합적인 AOP 솔루션을 제공하기 위해 AspectJ와 경쟁하려고 하지 않습니다. 우리는 Spring AOP와 같은 프록시 기반 프레임워크와 AspectJ와 같은 완전한 프레임워크가 모두 가치가 있으며, 경쟁 관계가 아닌 상호 보완적이라고 믿습니다. Spring은 Spring AOP와 IoC를 AspectJ와 원활하게 통합하여 일관된 Spring 기반 애플리케이션 아키텍처 내에서 AOP를 사용할 수 있도록 합니다. 이 통합은 Spring AOP API나 AOP Alliance API에 영향을 미치지 않으며, Spring AOP는 이전 버전과의 호환성을 유지합니다. Spring AOP API에 대한 논의는 다음 장에서 다루고 있습니다.

Note

Spring Framework의 중심 원칙 중 하나는 비침투성(non-invasiveness)입니다. 이는 비즈니스 또는 도메인 모델에 프레임워크 특정 클래스와 인터페이스를 강제로 도입하지 않아야 한다는 아이디어입니다. 그러나 특정 시나리오에서는 Spring Framework가 특정 코드베이스에 Spring Framework 관련 의존성을 도입할 수 있는 옵션을 제공하기도 합니다. 이러한 옵션을 제공하는 이유는 특정 기능을 더 쉽게 읽거나 코딩할 수 있는 경우가 있기 때문입니다. 그러나 Spring Framework는 거의 항상 선택권을 제공합니다. 특정 사용 사례나 시나리오에 가장 적합한 옵션을 선택할 수 있는 자유를 제공하는 것입니다.

이 장과 관련된 선택 중 하나는 어떤 AOP 프레임워크(및 어떤 AOP 스타일)를 선택할 것인지에 대한 것입니다. AspectJ, Spring AOP, 또는 둘 다 선택할 수 있습니다. 또한 @AspectJ 애노테이션 스타일 접근 방식 또는 Spring XML 구성 스타일 접근 방식 중에서 선택할 수 있습니다. 이 장에서 @AspectJ 스타일 접근 방식을 먼저 소개한다고 해서 Spring 팀이 @AspectJ 애노테이션 스타일 접근 방식을 Spring XML 구성 스타일보다 선호한다고 해석해서는 안 됩니다.

각 스타일의 장단점에 대한 보다 완전한 논의는 "Choosing which AOP Declaration Style to Use"를 참조하십시오.

AOP Proxies

Spring AOP는 기본적으로 AOP 프록시를 위해 표준 JDK dynamic proxy를 사용합니다. 이를 통해 어떤 인터페이스(또는 인터페이스 세트)도 프록시화할 수 있습니다.

Spring AOP는 또한 CGLIB 프록시를 사용할 수 있습니다. 이는 인터페이스가 아닌 클래스를 프록시화하기 위해 필요합니다. 기본적으로, 비즈니스 객체가 인터페이스를 구현하지 않는 경우 CGLIB이 사용됩니다. 클래스보다는 인터페이스를 대상으로 프로그래밍하는 것이 좋은 관행이기 때문에, 비즈니스 클래스는 일반적으로 하나 이상의 비즈니스 인터페이스를 구현합니다. 인터페이스에 선언되지 않은 메서드를 어드바이스해야 하거나, 프록시 객체를 구체적인 타입으로 메서드에 전달해야 하는 경우(아마도 드물겠지만)에는 CGLIB 사용을 강제할 수 있습니다.

Spring AOP가 프록시 기반이라는 사실을 이해하는 것이 중요합니다. 이 구현 세부 사항이 실제로 무엇을 의미하는지에 대한 철저한 검토는 "Understanding AOP Proxies"를 참조하십시오.