while(1) work();
article thumbnail
반응형

Spring을 사용한 측면 지향 프로그래밍

객체지향 프로그래밍(AOP)은 프로그램 구조에 대한 또 다른 사고 방식을 제공함으로써 객체지향 프로그래밍(OOP)을 보완합니다. OOP에서 모듈성의 핵심 단위는 클래스인 반면, AOP에서는 모듈성의 단위가 측면입니다. 측면을 사용하면 여러 유형과 객체에 걸쳐 있는 관심사(예: 트랜잭션 관리)를 모듈화할 수 있습니다. (이러한 우려는 종종 AOP 문헌에서 "교차" 우려라고 불립니다.)

Spring의 핵심 구성 요소 중 하나는 AOP 프레임워크입니다. Spring IoC 컨테이너는 AOP에 의존하지 않지만(즉, 원하지 않는 경우 AOP를 사용할 필요가 없습니다), AOP는 Spring IoC를 보완하여 매우 유능한 미들웨어 솔루션을 제공합니다.

AspectJ 포인트컷을 사용한 Spring AOP

Spring은스키마 기반 접근 방식 또는 @AspectJ 주석 스타일을 사용하여 사용자 정의 측면을 작성하는 간단하고 강력한 방법을 제공합니다. 이 두 가지 스타일 모두 위빙에 Spring AOP를 사용하면서 완전한 타이핑 조언과 AspectJ 포인트컷 언어 사용을 제공합니다.

이 장에서는 스키마 및 @AspectJ 기반 AOP 지원에 대해 설명합니다. 하위 수준 AOP 지원은 다음 장에서 설명합니다.

Spring 프레임워크에서 AOP는 다음을 위해 사용됩니다:

  • 선언적 엔터프라이즈 서비스 제공. 이러한 서비스 중 가장 중요한 것은선언적 트랜잭션 관리입니다.
  • 사용자가 사용자 정의 측면을 구현하여 AOP를 통해 OOP 사용을 보완할 수 있습니다.
  일반적인 선언적 서비스나 풀링과 같은 사전 패키지화된 선언적 미들웨어 서비스에만 관심이 있는 경우 Spring AOP로 직접 작업할 필요가 없으며 이 장의 대부분을 건너뛸 수 있습니다

 

AOP 개념

몇 가지 핵심적인 AOP 개념과 용어를 정의하는 것으로 시작하겠습니다. 이러한 용어는 Spring에만 국한된 것이 아닙니다. 불행히도 AOP 용어는 특별히 직관적이지 않습니다. 그러나 Spring이 자체 용어를 사용한다면 더욱 혼란스러울 것입니다.

  • Aspect: 여러 클래스에 걸쳐 있는 관심사를 모듈화한 것입니다. 트랜잭션 관리는 엔터프라이즈 Java 애플리케이션에서 교차하는 관심사의 좋은 예입니다. Spring AOP에서 측면은 일반 클래스( 스키마 기반 접근 방식) 또는@Aspect 어노테이션으로 주석이 달린 일반 클래스( @AspectJ 스타일)를 사용하여 구현됩니다.
  • 조인 포인트: 메서드 실행 또는 예외 처리와 같은 프로그램 실행 중 한 지점입니다. Spring AOP에서 조인 포인트는 항상 메서드 실행을 나타냅니다.
  • 조언: 조언: 특정 조인 포인트에서 측면이 취하는 조치입니다. 다양한 유형의 조언에는 "주변", "이전" 및 "이후" 조언이 포함됩니다. (조언 유형은 나중에 설명합니다.) Spring을 포함한 많은 AOP 프레임워크는 조언을 인터셉터로 모델링하고 조인 지점 주변에 인터셉터 체인을 유지합니다.
  • 포인트컷: 조인 지점과 일치하는 술어입니다. 조언은 포인트컷 표현식과 연관되며 포인트컷과 일치하는 모든 조인 지점(예: 특정 이름을 가진 메서드의 실행)에서 실행됩니다. 포인트컷 표현식에 의해 일치하는 조인점의 개념은 AOP의 핵심이며, Spring은 기본적으로 AspectJ 포인트컷 표현식 언어를 사용합니다.
  • 소개: 타입을 대신하여 추가 메서드 또는 필드 선언. Spring AOP를 사용하면 모든 권고 객체에 새로운 인터페이스(및 해당 구현)를 도입할 수 있습니다. 예를 들어, 도입을 사용하여 빈이IsModified 인터페이스를 구현하도록 하여 캐싱을 간소화할 수 있습니다. (도입은 AspectJ 커뮤니티에서 유형 간 선언이라고 합니다.)
  • 대상 객체: 하나 이상의 측면에 의해 조언을 받는 객체입니다. "조언된 객체"라고도 합니다. Spring AOP는 런타임 프록시를 사용하여 구현되므로 이 객체는 항상 프록시된 객체입니다.
  • AOP 프록시: 애스펙트 컨트랙트(조언 메서드 실행 등)를 구현하기 위해 AOP 프레임워크에서 생성된 객체입니다. Spring 프레임워크에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시입니다.
  • 위빙: 조언된 객체를 생성하기 위해 다른 애플리케이션 유형 또는 객체와 측면을 연결합니다. 이 작업은 컴파일 시간(예: AspectJ 컴파일러 사용), 로드 시간 또는 런타임에 수행할 수 있습니다. Spring AOP는 다른 순수 Java AOP 프레임워크와 마찬가지로 런타임에 위빙을 수행합니다.

Spring AOP에는 다음과 같은 유형의 조언이 포함됩니다:

  • 비포 어드바이스: 조인 지점 전에 실행되지만 예외를 던지지 않는 한 조인 지점으로 진행되는 실행 흐름을 방지할 수 있는 기능이 없는 조언입니다.
  • 조언 반환 후: 조인 포인트가 정상적으로 완료된 후에 실행되는 어드바이스(예: 메서드가 예외를 던지지 않고 반환되는 경우).
  • 조언을 던진 후: 메서드가 예외를 던져 종료될 때 실행할 어드바이스입니다.
  • (최종) 조언 후: 조인 포인트가 종료되는 수단(정상 또는 예외 반환)에 관계없이 실행되는 조언입니다.
  • 조언 주위: 메서드 호출과 같이 조인 지점을 둘러싸는 조언. 가장 강력한 종류의 조언입니다. Around 조언은 메서드 호출 전후에 사용자 지정 동작을 수행할 수 있습니다. 또한 조인 지점으로 진행할지 아니면 자체 반환 값을 반환하거나 예외를 던져 조언된 메서드 실행을 단축할지 여부를 선택하는 역할도 담당합니다.

주변 조언은 가장 일반적인 종류의 조언입니다. Spring AOP는 AspectJ와 마찬가지로 모든 범위의 조언 유형을 제공하므로 필요한 동작을 구현할 수 있는 가장 강력한 조언 유형을 사용하는 것이 좋습니다. 예를 들어 메서드의 반환 값으로 캐시만 업데이트해야 하는 경우, 어라운드 어드바이저로도 동일한 작업을 수행할 수 있지만 어라운드 어드바이저보다 애프터 리턴 어드바이저를 구현하는 것이 더 낫습니다. 가장 구체적인 조언 유형을 사용하면 오류 가능성이 적은 더 간단한 프로그래밍 모델을 제공합니다. 예를 들어, 어라운드 어드바이스에 사용되는 조인포인트에서 진행()메서드를 호출할 필요가 없으므로 실패할 염려가 없습니다.

모든 조언 매개변수는 정적으로 유형화되어 있으므로 객체 배열이 아닌 적절한 유형(예: 메서드 실행의 반환 값 유형)의 조언 매개변수로 작업할 수 있습니다.

포인트컷으로 일치하는 조인 포인트의 개념은 인터셉션만 제공하는 이전 기술과 구별되는 AOP의 핵심입니다. 포인트컷을 사용하면 객체 지향 계층 구조와 독립적으로 조언을 타겟팅할 수 있습니다. 예를 들어, 서비스 계층의 모든 비즈니스 작업과 같이 여러 개체에 걸쳐 있는 메서드 집합에 선언적 트랜잭션 관리를 제공하는 어라운드 조언을 적용할 수 있습니다.

 

Spring AOP 기능 및 목표

Spring AOP는 순수 Java로 구현됩니다. 특별한 컴파일 프로세스가 필요하지 않습니다. Spring AOP는 클래스 로더 계층 구조를 제어할 필요가 없으므로 서블릿 컨테이너 또는 애플리케이션 서버에서 사용하기에 적합합니다.

Spring AOP는 현재 메서드 실행 조인 포인트(Spring 빈에서 메서드 실행을 조언하는 기능)만 지원합니다. 필드 차단은 구현되어 있지 않지만, 핵심 Spring AOP API를 손상시키지 않고도 필드 차단에 대한 지원을 추가할 수 있습니다. 필드 액세스를 조언하고 조인 지점을 업데이트해야 하는 경우 AspectJ와 같은 언어를 고려하세요.

Spring AOP의 접근 방식은 대부분의 다른 AOP 프레임워크와 다릅니다. 가장 완벽한 AOP 구현을 제공하는 것이 목표가 아닙니다(물론 Spring AOP도 충분히 가능하지만요). 그보다는 엔터프라이즈 애플리케이션의 일반적인 문제를 해결하는 데 도움이 되도록 AOP 구현과 Spring IoC 간의 긴밀한 통합을 제공하는 것이 목표입니다.

따라서 예를 들어, Spring 프레임워크의 AOP 기능은 일반적으로 Spring IoC 컨테이너와 함께 사용됩니다. 측면은 일반 빈 정의 구문을 사용하여 구성됩니다(강력한 "자동 프록시" 기능을 허용하지만). 이것은 다른 AOP 구현과 중요한 차이점입니다. 매우 세분화된 객체(일반적으로 도메인 객체)에 대한 조언과 같은 일부 작업은 Spring AOP로는 쉽고 효율적으로 수행할 수 없습니다. 이러한 경우에는 AspectJ가 최선의 선택입니다. 하지만 저희의 경험에 따르면 Spring AOP는 AOP를 사용할 수 있는 엔터프라이즈 Java 애플리케이션의 대부분의 문제에 대한 훌륭한 솔루션을 제공합니다.

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

 
Spring 프레임워크의 핵심 원칙 중 하나는 비침투성입니다. 이는 비즈니스 또는 도메인 모델에 프레임워크 전용 클래스 및 인터페이스를 강제로 도입해서는 안 된다는 개념입니다. 그러나 일부 장소에서는 Spring 프레임워크에서 코드베이스에 Spring 프레임워크 특정 종속성을 도입할 수 있는 옵션을 제공합니다. 이러한 옵션을 제공하는 이유는 특정 시나리오에서는 이러한 방식으로 특정 기능을 읽거나 코딩하는 것이 더 쉬울 수 있기 때문입니다. 그러나 Spring 프레임워크는 (거의) 항상 선택권을 제공합니다. 사용자는 특정 사용 사례나 시나리오에 가장 적합한 옵션에 대해 정보에 입각해 자유롭게 결정할 수 있습니다.
이 장과 관련된 선택 중 하나는 어떤 AOP 프레임워크(그리고 어떤 AOP 스타일)를 선택할 것인지에 대한 선택입니다. AspectJ, Spring AOP 또는 둘 다 선택할 수 있습니다. 또한 @AspectJ 어노테이션 스타일 접근 방식 또는 Spring XML 구성 스타일 접근 방식 중 하나를 선택할 수도 있습니다. 이 장에서 @AspectJ 스타일 접근 방식을 먼저 소개하기로 했다는 사실을 Spring 팀이 Spring XML 구성 스타일보다 @AspectJ 어노테이션 스타일 접근 방식을 선호한다는 의미로 받아들여서는 안 됩니다.
각 스타일의 장단점에 대한 자세한 내용은 사용할 AOP 선언 스타일 선택하기를 참조하세요.

 

AOP 프록시

Spring AOP는 기본적으로 AOP 프록시에 표준 JDK 동적 프록시를 사용합니다. 이를 통해 모든 인터페이스(또는 인터페이스 집합)를 프록시할 수 있습니다.

Spring AOP는 CGLIB 프록시도 사용할 수 있습니다. 이는 인터페이스가 아닌 클래스를 프록시하는 데 필요합니다. 기본적으로 비즈니스 객체가 인터페이스를 구현하지 않는 경우 CGLIB가 사용됩니다. 클래스보다는 인터페이스로 프로그래밍하는 것이 좋은 관행이므로 비즈니스 클래스는 일반적으로 하나 이상의 비즈니스 인터페이스를 구현합니다. 인터페이스에 선언되지 않은 메서드를 알려주어야 하거나 메서드에 프록시된 객체를 구체적인 유형으로 전달해야 하는 (드물지만) 경우에CGLIB를 강제로 사용할 수 있습니다.

Spring AOP가 프록시 기반이라는 사실을 파악하는 것이 중요합니다. 이 구현 세부 사항이 실제로 무엇을 의미하는지에 대한 자세한 내용은AOP 프록시 이해를 참조하세요.

 

@AspectJ 지원

aspectJ는 어노테이션으로 주석이 달린 일반 Java 클래스로 측면을 선언하는 스타일을 말합니다. 애스펙트J 스타일은 애스펙트J 5 릴리즈의 일부로 애스펙트J프로젝트에서 도입되었습니다. Spring은 포인트컷 구문 분석 및 일치를 위해 AspectJ에서 제공하는 라이브러리를 사용하여 AspectJ 5와 동일한 어노테이션을 해석합니다. 하지만 AOP 런타임은 여전히 순수한 Spring AOP이며, AspectJ 컴파일러나 위버에 대한 종속성이 없습니다.

  AspectJ 컴파일러와 위버를 사용하면 전체 AspectJ 언어를 사용할 수 있으며, 이에 대해서는 Spring 애플리케이션에서 AspectJ 사용에서 설명합니다

 

Enabling @AspectJ Support

Spring 구성에서 @AspectJ 측면을 사용하려면, @AspectJ 측면을 기반으로 Spring AOP를 구성하고 해당 측면의 조언 여부에 따라 빈을 자동 프록시하는 Spring 지원을 활성화해야 합니다. 자동 프록시는 Spring이 하나 이상의 측면에 의해 빈이 조언을 받는다고 판단하면 해당 빈에 대한 프록시를 자동으로 생성하여 메서드 호출을 가로채고 필요에 따라 조언이 실행되도록 하는 것을 의미합니다.

XML 또는 Java 스타일 구성으로 @AspectJ 지원을 활성화할 수 있습니다. 두 경우 모두 애플리케이션(버전 1.9 이상)의 클래스 경로에 AspectJ의 aspectjweaver.jar 라이브러리가 있는지 확인해야 합니다. 이 라이브러리는 AspectJ 배포의lib 디렉토리 또는 Maven Central 리포지토리에서 사용할 수 있습니다.

Enabling @AspectJ Support with Java Configuration

Java @Configuration에서@AspectJ 지원을 사용하도록 설정하려면 다음 예제와 같이 @EnableAspectJAutoProxy어노테이션을 추가하세요:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

Enabling @AspectJ Support with XML Configuration

XML 기반 구성으로 @AspectJ 지원을 사용 설정하려면 다음 예에서 보는 바와 같이 aop:aspectj-autoproxy요소를 사용하세요:

<aop:aspectj-autoproxy/>

여기서는XML 스키마 기반 구성에 설명된 대로 스키마 지원을 사용한다고 가정합니다. aop 네임스페이스에서 태그를 가져오는 방법은 AOP 스키마를 참조하세요.

 

Aspect 선언하기

애스펙트J 지원을 활성화하면 애플리케이션 컨텍스트에 정의된 모든 빈이 애스펙트J 애스펙트( @Aspect 어노테이션이 있는)인 클래스와 함께 자동으로 Spring에서 감지되어 Spring AOP를 구성하는 데 사용됩니다. 다음 두 예제는 그다지 유용하지 않은 측면에 필요한 최소한의 단계를 보여줍니다.

두 예제 중 첫 번째 예제는 애플리케이션 컨텍스트에서 @Aspect로 어노테이션된 빈 클래스를 가리키는 일반 빈 정의를 보여줍니다:

<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
	<!-- configure properties of the aspect here -->
</bean>

두 예제 중 두 번째 예제는 @Aspect로 주석이 달린 NotVeryUsefulAspect 클래스 정의를 보여줍니다:

package com.xyz;

import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {
}

Aspect( @Aspect로 주석이 달린 클래스)는 다른 클래스와 마찬가지로 메서드와 필드를 가질 수 있습니다. 또한 포인트컷, 조언 및 소개(유형 간) 선언을 포함할 수도 있습니다.

 
컴포넌트 스캔을 통한 측면 자동 감지
Spring XML 구성에서 @Configuration 클래스의 @Bean 메서드를 통해, 또는 다른 Spring 관리 빈과 동일하게 클래스 경로 스캔을 통해 Spring이 자동 감지하도록 @Aspect 클래스를 일반 빈으로 등록할 수 있습니다. 그러나@Aspect 어노테이션만으로는 클래스 경로의 자동 감지에 충분하지 않다는 점에 유의하세요. 이를 위해서는 별도의 @Component 어노테이션을 추가해야 합니다(또는 Spring의 구성 요소 스캐너의 규칙에 따라 자격을 갖춘 사용자 정의 스테레오타입 어노테이션을 추가할 수도 있습니다)
 
다른 측면과 함께 조언하기?
Spring AOP에서 측면 자체는 다른 측면으로부터 조언의 대상이 될 수 없습니다. 클래스의 @Aspect 어노테이션은 해당 클래스를 하나의 측면으로 표시하므로 자동 프록시에서 제외됩니다

 

Declaring a Pointcut

포인트컷은 관심 있는 조인 지점을 결정하므로 조언이 실행되는 시점을 제어할 수 있습니다. Spring AOP는 Spring 빈에 대한 메서드 실행 조인 포인트만 지원하므로, 포인트컷은 Spring 빈의 메서드 실행과 일치하는 것으로 생각할 수 있습니다. 포인트컷 선언은 이름과 매개 변수로 구성된 서명과 관심 있는 메서드 실행을 정확히 결정하는 포인트컷 표현식의 두 부분으로 구성됩니다. AOP의 @AspectJ 주석 스타일에서 포인트컷 서명은 일반 메서드 정의로 제공되며, 포인트컷 표현식은 @Pointcut 주석을 사용하여 표시됩니다(포인트컷 서명 역할을 하는 메서드의 반환 유형이 무효여야 함).

예제를 통해 포인트컷 서명과 포인트컷 표현식을 명확하게 구분할 수 있습니다. 다음 예는 transfer라는 메서드의 실행과 일치하는 anyOldTransfer라는 포인트컷을 정의합니다:

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

포인트컷 어노테이션의 값을 형성하는 포인트컷 표현식은 일반 AspectJ 포인트컷 표현식입니다. AspectJ의 포인트컷 언어에 대한 자세한 내용은 AspectJ 프로그래밍 가이드 (및 확장판의 경우AspectJ 5 개발자 노트) 또는 AspectJ 관련 서적(예: Colyer 등이 쓴 Eclipse AspectJ 또는 Ramnivas Laddad 등이 쓴 AspectJ in Action) 중 하나를 참조하세요.

Supported Pointcut Designators

Spring AOP는 포인트컷 표현식에 사용할 수 있도록 다음과 같은 AspectJ 포인트컷 지정자(PCD)를 지원합니다:

  • 실행: 메서드 실행 조인 포인트와 일치하는 경우. 이것은 Spring AOP로 작업할 때 사용하는 기본 포인트컷 지정자입니다.
  • within: 특정 타입 내의 조인점으로 매칭을 제한합니다(Spring AOP를 사용할 때 매칭 타입 내에서 선언된 메서드의 실행).
  • this: 빈 참조(Spring AOP 프록시)가 지정된 유형의 인스턴스인 조인 지점(Spring AOP 사용 시 메서드 실행)으로 매칭을 제한합니다.
  • target: 대상 객체(프록시되는 응용 프로그램 객체)가 지정된 유형의 인스턴스인 조인 지점(Spring AOP 사용 시 메서드 실행)으로 일치를 제한합니다.
  • args: 인수가 지정된 유형의 인스턴스인 조인 지점(Spring AOP 사용 시 메서드 실행)으로 일치를 제한합니다.
  • 대상: 실행 중인 객체의 클래스에 지정된 유형의 어노테이션이 있는 조인 지점(Spring AOP 사용 시 메서드 실행)으로 일치를 제한합니다.
  • @args: 전달된 실제 인수의 런타임 유형에 지정된 유형의 어노테이션이 있는 조인 지점(Spring AOP 사용 시 메서드 실행)으로 일치를 제한합니다.
  • @within: 지정된 어노테이션이 있는 타입 내의 조인 지점(Spring AOP 사용 시 지정된 어노테이션이 있는 타입으로 선언된 메서드의 실행)으로 매칭을 제한합니다.
  • @annotation: 조인 포인트의 주체(Spring AOP에서 실행 중인 메서드)에 지정된 어노테이션이 있는 조인 포인트로 매칭을 제한합니다.
기타 포인트컷 유형

전체 AspectJ 포인트컷 언어는 Spring에서 지원되지 않는 추가 포인트컷 지정자( call, get, set, preinitialization,staticinitialization, initialization, handler, adviceexecution, withincode, cflow,cflowbelow, if, @this  @withincode)를 지원합니다. Spring AOP에서 해석되는 포인트컷 표현식에서 이러한 포인트컷 지정자를 사용하면 IllegalArgumentException이 발생하게 됩니다.

Spring AOP에서 지원하는 포인트컷 지정자 집합은 향후 릴리스에서 더 많은 AspectJ 포인트컷 지정자를 지원하도록 확장될 수 있습니다.

Spring AOP는 메서드 실행 조인 지점으로만 매칭을 제한하기 때문에, 포인트컷 지정자에 대한 앞의 논의는 AspectJ 프로그래밍 가이드에서 찾을 수 있는 것보다 더 좁은 정의를 제공합니다. 또한 AspectJ 자체에는 타입 기반 시맨틱이 있으며, 실행 조인 포인트에서  객체와 대상은 모두 동일한 객체, 즉 메서드를 실행하는 객체를 참조합니다. Spring AOP는 프록시 기반 시스템이며 프록시 객체 자체( 이에 바인딩된)와 프록시 뒤에 있는 대상 객체( 대상에 바인딩된)를 구분합니다.

 
Spring AOP 프레임워크의 프록시 기반 특성으로 인해 대상 객체 내의 호출은 정의상 인터셉트되지 않습니다. JDK 프록시의 경우 프록시의 공용 인터페이스 메서드 호출만 가로챌 수 있습니다. CGLIB를 사용하면 프록시의 공개 및 보호 메서드 호출(필요한 경우 패키지에서 볼 수 있는 메서드까지)도 가로챌 수 있습니다. 그러나 프록시를 통한 일반적인 상호 작용은 항상 공개 서명을 통해 설계해야 합니다.
포인트컷 정의는 일반적으로 모든 인터셉트된 메서드와 일치합니다. 프록시를 통한 잠재적인 비공개 상호작용이 있는 CGLIB 프록시 시나리오에서도 포인트컷이 엄격하게 공개 전용인 경우, 그에 따라 정의해야 합니다.
인터셉션에 대상 클래스 내의 메서드 호출이나 생성자까지 포함해야 하는 경우 Spring의 프록시 기반 AOP 프레임워크 대신 Spring 기반 네이티브 AspectJ 위빙을 사용하는 것을 고려하세요. 이는 서로 다른 특성을 가진 다른 AOP 사용 모드에 해당하므로 결정을 내리기 전에 위빙에 익숙해지도록 하세요.

Spring AOP는 bean이라는 추가 PCD도 지원합니다. 이 PCD를 사용하면 특정 명명된 Spring 빈 또는 명명된 Spring 빈 집합(와일드카드 사용 시)으로 조인 포인트의 매칭을 제한할 수 있습니다.  PCD의 형식은 다음과 같습니다:

bean(idOrNameOfBean)

IdOrNameOfBean 토큰은 모든 Spring 빈의 이름일 수 있습니다. 문자를 사용하는 와일드카드 지원은 제한적으로 제공되므로 Spring 빈에 대한 명명 규칙을 설정한 경우  PCD 표현식을 작성하여 해당 빈을 선택할 수 있습니다. 다른 포인트컷 지정자와 마찬가지로  PCD는 && (및), || (또는) 및 ! (부정) 연산자와 함께 사용할 수도 있습니다.

 
 PCD는 Spring AOP에서만 지원되며 기본 AspectJ 위빙에서는 지원되지 않습니다. 이는 AspectJ가 정의하는 표준 PCD에 대한 Spring 전용 확장이며, 따라서 @Aspect 모델에서 선언된 측면에는 사용할 수 없습니다.
 PCD는 유형 수준(위빙 기반 AOP는 제한됨)에서만 작동하는 것이 아니라 인스턴스 수준(Spring 빈 이름 개념을 기반으로 함)에서 작동합니다. 인스턴스 기반 포인트컷 지정자는 특정 빈을 이름으로 자연스럽고 간단하게 식별할 수 있는 Spring의 프록시 기반 AOP 프레임워크의 특별한 기능이며, Spring 빈 팩토리와의 긴밀한 통합을 통해 제공됩니다.

Combining Pointcut Expressions

&&, ||  !를 사용하여 포인트컷 표현식을 결합할 수 있습니다. 이름으로 포인트컷 표현식을 참조할 수도 있습니다. 다음 예는 세 가지 포인트컷 표현식을 보여줍니다:

package com.xyz;

public class Pointcuts {

	@Pointcut("execution(public * *(..))")
	public void publicMethod() {} 

	@Pointcut("within(com.xyz.trading..*)")
	public void inTrading() {} 

	@Pointcut("publicMethod() && inTrading()")
	public void tradingOperation() {} 
}
  메서드 실행 조인 포인트가 공개 메서드의 실행을 나타내는 경우publicMethod가 일치합니다.
  inTrading은 메서드 실행이 트레이딩 모듈에 있는 경우 일치합니다.
  tradingOperation은 메서드 실행이 트레이딩 모듈의 공용 메서드를 나타내는 경우 일치합니다.

위와 같이 작은 명명된 포인트컷으로 더 복잡한 포인트컷 표현식을 작성하는 것이 좋습니다. 이름으로 포인트컷을 참조할 때는 일반적인 Java 가시성 규칙이 적용됩니다(동일한 유형에서는 비공개 포인트컷을, 계층 구조에서는 보호된 포인트컷을, 어디서나 공개 포인트컷을 볼 수 있음). 가시성은 포인트컷 일치에 영향을 미치지 않습니다.

Sharing Named Pointcut Definitions

엔터프라이즈 애플리케이션으로 작업할 때 개발자는 애플리케이션의 모듈과 특정 작업 집합을 여러 측면에서 참조해야 하는 경우가 많습니다. 이러한 목적으로 일반적으로 사용되는 명명된 포인트컷표현식을 캡처하는 전용 클래스를 정의하는 것이 좋습니다. 이러한 클래스는 일반적으로 다음과 같은CommonPointcuts 예제와 유사합니다(클래스 이름은 사용자가 정할 수 있음):

package com.xyz;

import org.aspectj.lang.annotation.Pointcut;

public class CommonPointcuts {

	/**
	 * A join point is in the web layer if the method is defined
	 * in a type in the com.xyz.web package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.web..*)")
	public void inWebLayer() {}

	/**
	 * A join point is in the service layer if the method is defined
	 * in a type in the com.xyz.service package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.service..*)")
	public void inServiceLayer() {}

	/**
	 * A join point is in the data access layer if the method is defined
	 * in a type in the com.xyz.dao package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.dao..*)")
	public void inDataAccessLayer() {}

	/**
	 * A business service is the execution of any method defined on a service
	 * interface. This definition assumes that interfaces are placed in the
	 * "service" package, and that implementation types are in sub-packages.
	 *
	 * If you group service interfaces by functional area (for example,
	 * in packages com.xyz.abc.service and com.xyz.def.service) then
	 * the pointcut expression "execution(* com.xyz..service.*.*(..))"
	 * could be used instead.
	 *
	 * Alternatively, you can write the expression using the 'bean'
	 * PCD, like so "bean(*Service)". (This assumes that you have
	 * named your Spring service beans in a consistent fashion.)
	 */
	@Pointcut("execution(* com.xyz..service.*.*(..))")
	public void businessService() {}

	/**
	 * A data access operation is the execution of any method defined on a
	 * DAO interface. This definition assumes that interfaces are placed in the
	 * "dao" package, and that implementation types are in sub-packages.
	 */
	@Pointcut("execution(* com.xyz.dao.*.*(..))")
	public void dataAccessOperation() {}

}

이러한 클래스에 정의된 포인트컷은 포인트컷 표현식이 필요한 모든 곳에서@Pointcut 메서드의 이름과 결합된 클래스의 정규화된 이름을 참조하여 참조할 수 있습니다. 예를 들어 서비스 계층을 트랜잭션으로 만들려면com.xyz.CommonPointcuts.businessService() 라는 이름의 pointcut을 참조하는 다음과 같이 작성할 수 있습니다:

<aop:config>
	<aop:advisor
		pointcut="com.xyz.CommonPointcuts.businessService()"
		advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
	<tx:attributes>
		<tx:method name="*" propagation="REQUIRED"/>
	</tx:attributes>
</tx:advice>

<aop:config>  <aop:advisor> 요소는 스키마 기반 AOP 지원에서 설명합니다. 트랜잭션 요소는 트랜잭션 관리에서 설명합니다.

Examples

Spring AOP 사용자는 실행 포인트컷 지정자를 가장 자주 사용할 가능성이 높습니다. 실행 표현식의 형식은 다음과 같습니다:

실행(수정자-패턴? ret-타입-패턴 선언-타입-패턴?이름-패턴(매개변수-패턴) 던지기-패턴?)

반환 유형 패턴( 앞 스니펫의ret-type-pattern ), 이름 패턴 및 매개변수 패턴을 제외한 모든 부분은 선택 사항입니다. 반환 유형 패턴은 조인 포인트가 일치하기 위해 메서드의 반환 유형이 무엇인지 결정합니다. 반환 유형 패턴으로 가장 많이 사용되는 것은*입니다. 모든 반환 유형과 일치합니다. 정규화된 유형 이름은 메서드가 지정된 유형을 반환하는 경우에만 일치합니다. 이름 패턴은 메서드 이름과 일치합니다. 와일드카드를 이름 패턴의 전체 또는 일부로 사용할 수 있습니다. 선언형 패턴을 지정하는 경우 이름 패턴 구성 요소에 결합하려면 후행  포함하세요. 매개변수 패턴은 조금 더 복잡합니다. () 는 매개변수를 받지 않는 메서드와 일치하는 반면 (..)는 임의의 수(0 이상)의 매개변수와 일치합니다. (*) 패턴은 모든 유형의 매개변수를 받는 메서드와 일치하고(*,문자열 )은 두 개의 매개변수를 받는 메서드와 일치합니다. 첫 번째는 모든 유형이 가능하지만 두 번째는 문자열이어야 합니다. 자세한 내용은 AspectJ 프로그래밍 가이드의언어 의미론 섹션을 참조하세요.

다음 예제는 몇 가지 일반적인 포인트컷 표현식을 보여줍니다:

  • 모든 공용 메서드의 실행:
  • execution(public * *(..))
  • 이름이 set로 시작하는 모든 메서드 실행:
  • execution(* set*(..))
  • AccountService 인터페이스에 정의된 모든 메서드 실행:
  • execution(* com.xyz.service.AccountService.*(..))
  • 서비스 패키지에 정의된 모든 메서드 실행:
  • execution(* com.xyz.service.*.*(..))
  • 서비스 패키지 또는 그 하위 패키지 중 하나에 정의된 메서드 실행:
  • execution(* com.xyz.service..*.*(..))
  • 서비스 패키지 내의 모든 조인 지점(Spring AOP에서만 메서드 실행):
  • within(com.xyz.service.*)
  • 서비스 패키지 또는 그 하위 패키지 중 하나 내의 모든 조인 지점(Spring AOP에서만 메서드 실행):
  • within(com.xyz.service..*)
  • 프록시가AccountService 인터페이스를 구현하는 모든 조인 지점(Spring AOP에서만 메서드 실행):
      이것은 바인딩 형태로 더 일반적으로 사용됩니다. 조언 본문에서 프록시 객체를 사용할 수 있게 만드는 방법은 조언 선언하기섹션을 참조하세요
  • this(com.xyz.service.AccountService)
  • 대상 객체가 AccountService 인터페이스를 구현하는 모든 조인 지점(Spring AOP에서만 메서드 실행):
      대상은 바인딩 형태로 더 일반적으로 사용됩니다. 어드바이스 본문에서 대상 객체를 사용할 수 있게 만드는 방법은 어드바이스 선언하기 섹션을 참조하세요
  • target(com.xyz.service.AccountService)
  • 단일 매개 변수를 취하고 런타임에 전달된 인수가 직렬화 가능인 모든 조인 지점(Spring AOP에서만 메서드 실행)입니다:
      args는 바인딩 형태로 더 일반적으로 사용됩니다. 조언 본문에서 메서드 인수를 사용할 수 있게 만드는 방법은 조언 선언하기 섹션을 참조하세요

    이 예제에서 제공된 포인트컷은 실행(* *(java.io.Serializable))과 다르다는 점에 유의하세요. 런타임에 전달된 인수가Serializable인 경우 args 버전이 일치하고, 메서드 서명이 Serializable 타입의 단일 매개변수를 선언하는 경우 실행 버전이 일치합니다.

  • args(java.io.Serializable)
  • 대상 객체에@Transactional 어노테이션이 있는 모든 조인 지점(Spring AOP에서만 메서드 실행):
      바인딩 형식으로 @target을 사용할 수도 있습니다. 어노테이션 객체를 어드바이스 본문에서 사용할 수 있게 만드는 방법은 어드바이스 선언하기 섹션을 참조하세요
  • 대상(org.springframework.transaction.annotation.Transactional)
  • 대상 객체의 선언된 유형에 @Transactional 어노테이션이 있는 모든 조인 지점(Spring AOP에서만 메서드 실행)입니다:
      바인딩 형식으로 @within을 사용할 수도 있습니다. 어노테이션 객체를 어드바이스 본문에서 사용할 수 있게 만드는 방법은 어드바이스 선언하기 섹션을 참조하세요
  • @within(org.springframework.transaction.annotation.Transactional)
  • 실행 메서드에@Transactional 어노테이션이 있는 모든 조인 지점(Spring AOP에서만 메서드 실행):
      바인딩 형식으로 @annotation을 사용할 수도 있습니다. 어노테이션 객체를 어드바이스 본문에서 사용할 수 있게 만드는 방법은 어드바이스 선언하기 섹션을 참조하세요
  • @annotation(org.springframework.transaction.annotation.Transactional)
  • 단일 매개 변수를 취하고 전달된 인수의 런타임 유형에 @Classified 어노테이션이 있는 모든 조인 지점(Spring AOP에서만 메서드 실행)입니다:
      바인딩 형식으로 @args를 사용할 수도 있습니다. 어노테이션 객체를 어노테이션 본문에서 사용할 수 있게 만드는 방법은 어노테이션 선언하기 섹션을 참조하세요
  • @args(com.xyz.security.Classified)
  • TradeService라는 Spring 빈의 모든 조인 지점(Spring AOP에서만 메서드 실행):
  • bean(tradeService)
  • 와일드카드 표현식 *Service와 일치하는 이름을 가진 Spring 빈에 대한 모든 조인 지점(Spring AOP에서만 메서드 실행):
  • bean(*Service)

Writing Good Pointcuts

컴파일하는 동안 AspectJ는 일치 성능을 최적화하기 위해 포인트컷을 처리합니다. 코드를 검사하고 각 조인 지점이 주어진 포인트컷과 정적 또는 동적으로 일치하는지 확인하는 것은 비용이 많이 드는 프로세스입니다. (동적 일치란 정적 분석으로는 일치 여부를 완전히 확인할 수 없으며 코드가 실행 중일 때 실제 일치 여부를 확인하기 위해 코드에 테스트를 배치하는 것을 의미합니다. 포인트컷 선언을 처음 접하면 AspectJ는 매칭 프로세스에 최적화된 형태로 다시 작성합니다. 이것이 무엇을 의미할까요? 기본적으로 포인트컷은 DNF(분리형 정규 형식)로 다시 작성되며, 포인트컷의 구성 요소는 평가 비용이 더 저렴한 구성 요소를 먼저 검사하도록 정렬됩니다. 즉, 다양한 포인트컷 지정자의 성능을 이해하는 것에 대해 걱정할 필요가 없으며 포인트컷 선언에서 어떤 순서로든 지정자를 제공할 수 있습니다.

그러나 AspectJ는 지시된 것만 사용할 수 있습니다. 최적의 매칭 성능을 위해서는 무엇을 달성하고자 하는지를 생각하고 정의에서 매칭에 대한 검색 공간을 최대한 좁혀야 합니다. 기존의 지정자는 자연스럽게 분류, 범위 지정, 문맥 지정의 세 가지 그룹 중 하나에 속합니다:

  • 종류 지정자는실행, get, set, 호출, 핸들러 등 특정 종류의 조인 지점을 선택합니다.
  • 범위 지정자는 관심 있는 조인 지점 그룹(여러 종류일 수 있음)을 선택합니다: 코드   코드 내
  • 컨텍스트 지정자는 컨텍스트에 따라 일치(및 선택적으로 바인딩)합니다:this, target, @annotation

잘 작성된 포인트컷에는 적어도 처음 두 가지 유형(종류 및 범위 지정)이 포함되어야 합니다. 조인 포인트 컨텍스트에 따라 일치시킬 컨텍스트 지정자를 포함하거나 해당 컨텍스트를 바인딩하여 도움말에 사용할 수 있습니다. 종류 지정자만 제공하거나 컨텍스트 지정자만 제공하면 작동하지만 추가 처리 및 분석으로 인해 위빙 성능(사용 시간 및 메모리)에 영향을 줄 수 있습니다. 범위 지정자는 매칭 속도가 매우 빠르며, 이를 사용하면 더 이상 처리해서는 안 되는 조인 포인트 그룹을 매우 빠르게 해제할 수 있습니다. 좋은 포인트컷은 가능하면 항상 하나를 포함해야 합니다.

 

Declaring Advice

조언은 포인트컷 표현식과 연관되어 있으며 포인트컷과 일치하는 메서드 실행 전후에 실행됩니다. 포인트컷 표현식은 인라인 포인트컷이거나 명명된 포인트컷에 대한 참조일 수 있습니다.

Before Advice

비포어 어노테이션을 사용하여 측면에 비포어 어노테이션을 선언할 수 있습니다.

다음 예제에서는 인라인 포인트컷 표현식을 사용합니다.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

	@Before("execution(* com.xyz.dao.*.*(..))")
	public void doAccessCheck() {
		// ...
	}
}

명명된 포인트컷을 사용하는 경우 앞의 예제를 다음과 같이 다시 작성할 수 있습니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

	@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
	public void doAccessCheck() {
		// ...
	}
}

After Returning Advice

반환 후 조언은 일치하는 메서드 실행이 정상적으로 반환될 때 실행됩니다. @AfterReturning 어노테이션을 사용하여 선언할 수 있습니다.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

	@AfterReturning("execution(* com.xyz.dao.*.*(..))")
	public void doAccessCheck() {
		// ...
	}
}
  여러 개의 조언 선언(및 다른 멤버도 포함)을 모두 동일한 컴포넌트 안에 포함할 수 있습니다. 이 예에서는 각 조언 선언의 효과에 초점을 맞추기 위해 하나의 조언 선언만 표시합니다

때로는 반환된 실제 값에 대한 액세스 권한이 조언 본문에서 필요한 경우가 있습니다. 다음 예에서 보는 것처럼 반환 값을 바인딩하는 @AfterReturning 형식을 사용하여 액세스 권한을 얻을 수 있습니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

	@AfterReturning(
		pointcut="execution(* com.xyz.dao.*.*(..))",
		returning="retVal")
	public void doAccessCheck(Object retVal) {
		// ...
	}
}

반환 속성에 사용된 이름은 반드시 상담 메서드의 매개 변수 이름과 일치해야 합니다. 메서드 실행이 반환되면 반환 값은 해당 인자 값으로 조언 메서드에 전달됩니다. 또한 반환 절은 지정된 유형(이 경우 모든 반환 값과 일치하는 Object)의 값을 반환하는 메서드 실행으로만 매칭을 제한합니다.

조언을 반환한 후 사용할 때 완전히 다른 참조를 반환하는 것은 불가능하다는 점에 유의하세요.

After Throwing Advice

After Throwing 조언은 일치하는 메서드 실행이 예외를 던져 종료될 때 실행됩니다. 다음 예제에서 볼 수 있듯이 @AfterThrowing 어노테이션을 사용하여 선언할 수 있습니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

	@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
	public void doRecoveryActions() {
		// ...
	}
}

특정 유형의 예외가 발생했을 때만 조언이 실행되도록 하려는 경우가 많으며, 조언 본문에서 던진 예외에 대한 액세스도 필요한 경우가 많습니다. Throw 속성을 사용하여 매칭을 제한하고(원하는 경우 예외 유형으로 Throwable사용), 던져진 예외를 조언 매개 변수에 바인딩할 수 있습니다. 다음 예에서는 이를 수행하는 방법을 보여 줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

	@AfterThrowing(
		pointcut="execution(* com.xyz.dao.*.*(..))",
		throwing="ex")
	public void doRecoveryActions(DataAccessException ex) {
		// ...
	}
}

Throw 속성에 사용된 이름은 조언 메서드의 매개변수 이름과 일치해야 합니다. 메서드 실행이 예외를 throw하여 종료되면 예외는 해당 인수 값으로 조언 메서드에 전달됩니다. 또한 throw 절은 지정된 유형(이 경우DataAccessException)의 예외를 던지는 메서드 실행으로만 매칭을 제한합니다.

 
AfterThrowing은 일반적인 예외 처리 콜백을 나타내지 않습니다. 특히 @AfterThrowing 조언 메서드는 조인 지점(사용자가 선언한 대상 메서드) 자체에서만 예외를 수신하고 함께 제공되는 @After/@AfterReturning 메서드에서는 예외를 수신하지 않아야 한다는 점에 유의하세요.

After (Finally) Advice

After(최종적으로) 조언은 일치하는 메서드 실행이 종료될 때 실행됩니다. After 어노테이션을 사용하여 선언됩니다. After 조언은 일반 및 예외 반환 조건을 모두 처리할 수 있도록 준비해야 합니다. 일반적으로 리소스 릴리스 및 이와 유사한 목적으로 사용됩니다. 다음 예는 After advice를 사용하는 방법을 보여줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

	@After("execution(* com.xyz.dao.*.*(..))")
	public void doReleaseLock() {
		// ...
	}
}
 
AspectJ의 @After 조언은 try-catch 문에서 finally 블록과 유사하게 "마침내 조언 후"로 정의됩니다. 성공적인 정상 반환에만 적용되는 @AfterReturning과 달리 조인 지점(사용자가 선언한 대상 메서드)에서 던져진 모든 결과, 정상 반환 또는 예외에 대해 호출됩니다.

Around Advice

마지막 종류의 조언은 조언 주변입니다. 주변 조언은 일치하는 메서드의 실행 "주변"에서 실행됩니다. 메서드가 실행되기 전과 후에 모두 작업을 수행할 수 있으며, 언제, 어떻게, 심지어 메서드가 실제로 실행될지 여부를 결정할 수 있습니다. 타이머를 시작하고 중지하는 등 스레드 안전 방식으로 메서드 실행 전후의 상태를 공유해야 하는 경우 주변 조언이 자주 사용됩니다.

 
항상 요구 사항을 충족하는 가장 강력한 형태의 조언을 사용하세요.
예를 들어, 비포 어드바이저로 충분하다면 어라운드 어드바이저를 사용하지 마세요.

Around 조언은 메서드에 @Around 어노테이션을 주석 처리하여 선언합니다. 메서드는 반환 유형으로 Object를 선언해야 하며, 메서드의 첫 번째 매개 변수는 ProceedingJoinPoint 유형이어야 합니다. 조언 메서드의 본문에서 기본 메서드가 실행되도록 하려면 ProceedingJoinPoint에서 proceed() 를 호출해야 합니다. 인자 없이 proceed() 를 호출하면 호출 시 호출자의 원래 인수가 기본 메서드에 제공됩니다. 고급 사용 사례의 경우, 인수의 배열(Object[])을 허용하는 진행( ) 메서드의 오버로드된 변형이 있습니다. 배열의 값은 기본 메서드가 호출될 때 기본 메서드의 인자로 사용됩니다.

 
Object[] 로 호출될 때 proceed의 동작은 AspectJ 컴파일러에 의해 컴파일된 어라운드 어드바이스에 대한 proceed의 동작과 약간 다릅니다. 전통적인 AspectJ 언어를 사용하여 작성된 어라운드 어드바이스의 경우,proceed에 전달된 인수의 수는 기본 조인 포인트가 취하는 인수의 수가 아니라 어라운드 어드바이스에 전달된 인수의 수와 일치해야 하며, 주어진 인수의 위치에서 proceed에 전달된 값은 해당 값이 바인딩된 엔티티의 조인 포인트에서 원래 값을 대체합니다(지금 당장 이해되지 않아도 걱정하지 마세요).
Spring이 취하는 접근 방식은 더 간단하고 프록시 기반의 실행 전용 의미론에 더 잘 부합합니다. Spring용으로 작성된@AspectJ 측면을 컴파일하고 AspectJ 컴파일러와 위버로 인수를 사용하는 경우에만 이 차이를 인식하면 됩니다. Spring AOP와 AspectJ 모두에서 100% 호환되는 이러한 측면을 작성하는 방법이 있으며, 이는다음 섹션의 조언 매개변수에 대한 설명에서 설명합니다.

어라운드 어드바이저가 반환하는 값은 메서드 호출자가 볼 수 있는 반환값입니다. 예를 들어, 간단한 캐싱 측면은 캐시가 있는 경우 캐시에서 값을 반환하고, 캐시가 없는 경우 proceed() 를 호출하여 해당 값을 반환할 수 있습니다. 진행은어라운드 조언 본문 내에서 한 번, 여러 번 또는 전혀 호출되지 않을 수도 있습니다. 이 모든 것이 합법적입니다.

  어라운드 어드바이스 메서드의 반환 유형을 무효로 선언하면 호출자에게 항상 null이반환되어 진행() 호출의 결과를 사실상 무시하게 됩니다. 따라서 어라운드 어드바이스 메서드는 반환 유형을 Object로 선언하는 것이 좋습니다. 조언 메서드는 일반적으로 기본 메서드의 반환 유형이 무효인 경우에도 진행() 호출에서 반환된 값을 반환해야 합니다. 그러나 사용 사례에 따라 선택적으로 캐시된 값, 래핑된 값 또는 다른 값을 반환할 수도 있습니다

다음 예는 조언을 사용하는 방법을 보여줍니다:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

	@Around("execution(* com.xyz..service.*.*(..))")
	public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
		// start stopwatch
		Object retVal = pjp.proceed();
		// stop stopwatch
		return retVal;
	}
}

Advice Parameters

Spring은 완전한 타입의 조언을 제공하므로, 항상 Object[] 배열로 작업하는 대신 앞서 반환 및 던지기 예제에서 살펴본 것처럼 조언 시그니처에 필요한 매개변수를 선언합니다. 이 섹션의 뒷부분에서 인자 및 기타 컨텍스트 값을 조언 본문에서 사용할 수 있게 만드는 방법을 살펴보겠습니다. 먼저, 조언이 현재 조언하고 있는 메서드를 알아낼 수 있는 일반 조언을 작성하는 방법을 살펴보겠습니다.

현재 조인 포인트에 액세스

모든 조언 메서드는 첫 번째 매개 변수로org.aspectj.lang.JoinPoint 유형의 매개 변수를 선언할 수 있습니다. 주위의 조언은 JoinPoint의 서브 클래스인 ProceedingJoinPoint 유형의 첫 번째 매개 변수를 선언해야 합니다.

JoinPoint 인터페이스는 여러 가지 유용한 메서드를 제공합니다:

  • getArgs(): 메서드 인자를 반환합니다.
  • getThis(): 프록시 객체를 반환합니다.
  • getTarget(): 대상 객체를 반환합니다.
  • getSignature(): 조언 중인 메서드에 대한 설명을 반환합니다.
  • toString(): 조언 중인 메서드에 대한 유용한 설명을 출력합니다.

자세한 내용은 자바독을 참조하세요.

조언에 매개변수 전달하기

반환된 값이나 예외 값을 바인딩하는 방법(반환 후와 어드바이스를 던진 후 사용)을 이미 살펴봤습니다. 인자 값을 조언 본문에서 사용할 수 있도록 하려면 바인딩 형식의 인자를 사용하면 됩니다. Args 표현식에서 유형 이름 대신 매개변수 이름을 사용하면 도움말이 호출될 때 해당 인수의 값이 매개변수 값으로 전달됩니다. 예제를 통해 이를 더 명확하게 이해할 수 있습니다. Account개체를 첫 번째 매개 변수로 사용하는 DAO 작업의 실행을 조언하고 싶고 조언 본문에서 계정에 대한 액세스가 필요하다고 가정합니다. 다음과 같이 작성할 수 있습니다:

@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
public void validateAccount(Account account) {
	// ...
}

포인트컷 표현식의 args(account,..) 부분은 두 가지 용도로 사용됩니다. 첫째, 메서드가 적어도 하나의 매개 변수를 취하고 해당 매개 변수로 전달된 인수가 Account의 인스턴스인 메서드 실행으로만 일치하도록 제한합니다. 둘째, 계정매개 변수를 통해 실제 계정 개체를 조언에서 사용할 수 있게 합니다.

이를 작성하는 또 다른 방법은 조인 포인트와 일치할 때 계정개체 값을 "제공하는" 포인트컷을 선언한 다음, 조언에서 명명된 포인트컷을 참조하는 것입니다. 이것은 다음과 같이 보일 것입니다:

@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
	// ...
}

자세한 내용은 AspectJ 프로그래밍 가이드를 참조하세요.

프록시 객체(this), 대상 객체(target) 및 어노테이션(@within,@target, @annotation  @args)은 모두 비슷한 방식으로 바인딩할 수 있습니다. 다음 예제에서는@Auditable 어노테이션으로 어노테이션된 메서드의 실행을 일치시키고 감사 코드를 추출하는 방법을 보여 줍니다:

다음은 @Auditable 어노테이션의 정의를 보여줍니다:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
	AuditCode value();
}

다음은 @Auditable 메서드의 실행과 일치하는 조언을 보여줍니다:

@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") 
public void audit(Auditable auditable) {
	AuditCode code = auditable.value();
	// ...
}
  포인트컷 표현식 결합에 정의된 pointcut이라는 이름의 publicMethod를 참조합니다.

조언 매개변수 및 제네릭

Spring AOP는 클래스 선언과 메서드 매개변수에 사용되는 제네릭을 처리할 수 있습니다. 다음과 같은 제네릭 타입이 있다고 가정해 봅시다:

public interface Sample<T> {
	void sampleGenericMethod(T param);
	void sampleGenericCollectionMethod(Collection<T> param);
}

조언 매개변수를 메서드를 가로채려는 매개변수 유형에 연결하여 메서드 유형의 가로채기를 특정 매개변수 유형으로 제한할 수 있습니다:

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
	// Advice implementation
}

이 접근 방식은 일반 컬렉션에는 작동하지 않습니다. 따라서 다음과 같이 포인트컷을 정의할 수 없습니다:

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
	// Advice implementation
}

이 방법을 사용하려면 컬렉션의 모든 요소를 검사해야 하는데, 일반적으로  값을 어떻게 처리할지 결정할 수 없기 때문에 합리적이지 않습니다. 이와 비슷한 결과를 얻으려면 Collection<?>에 매개변수를 입력하고 요소의 유형을 수동으로 확인해야 합니다.

인수 이름 결정하기

조언 호출에서 매개변수 바인딩은 포인트컷 표현식에 사용된 이름을 조언 및 포인트컷 메서드 서명에 선언된 매개변수 이름과 일치시키는 데 의존합니다.

  이 섹션에서는 인수와 매개변수라는 용어를 혼용하여 사용하며, AspectJ API는 매개변수 이름을 인수 이름으로 참조하기 때문입니다

Spring AOP는 매개변수 이름을 결정하기 위해 다음과 같은 ParameterNameDiscoverer 구현을 사용합니다. 각 디스커버러는 매개변수 이름을 발견할 기회가 주어지며, 가장 먼저 성공한 디스커버러가 승리합니다. 등록된 디스커버러 중 파라미터 이름을 결정할 수 있는 디스커버러가 없는 경우 예외가 발생합니다.

AspectJAnnotation파라미터이름디스커버러

해당 조언 또는 포인트컷 어노테이션의 argNames 속성을 통해 사용자가 명시적으로 지정한 매개변수 이름을 사용합니다. 자세한 내용은 명시적 인수 이름을 참조하세요.

Kotlin 리플렉션 매개 변수 이름 검색기

Kotlin 리플렉션 API를 사용하여 매개 변수 이름을 확인합니다. 이 디스커버러는 해당 API가 클래스 경로에 있는 경우에만 사용됩니다.

표준 리플렉션 매개 변수 이름 디스커버러

표준 java.lang.reflect.ParameterAPI를 사용하여 매개변수 이름을 확인합니다. Javac에 대해 -parameters플래그를 사용하여 코드를 컴파일해야 합니다. Java 8 이상에서 권장되는 접근 방식입니다.

AspectJAdviceParameterNameDiscoverer

포인트컷 표현식, 반환  던지기 절에서 매개변수 이름을 추론합니다. 사용된 알고리즘에 대한 자세한 내용은javadoc을참조하세요.

명시적 인수 이름

애스펙트J 조언 및 포인트컷 어노테이션에는 어노테이션된 메서드의 인수 이름을 지정하는 데 사용할 수 있는 선택적 argNames 속성이 있습니다.

 
디버그 정보 없이도 @AspectJ 컴파일러(ajc)에 의해 컴파일된 경우 컴파일러가 필요한 정보를 보유하므로 argNames 속성을 추가할 필요가 없습니다.
마찬가지로, -parameters플래그를 사용하여 javac으로 @AspectJ 컴파일된 경우 컴파일러가 필요한 정보를 보유하므로 argNames 속성을 추가할 필요가 없습니다.

다음 예제는 argNames 속성을 사용하는 방법을 보여줍니다:

@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", 
	argNames = "bean,auditable") 
public void audit(Object bean, Auditable auditable) {
	AuditCode code = auditable.value();
	// ... use code and bean
}
  포인트컷 표현식 결합에 정의된 pointcut이라는 이름의 publicMethod를 참조합니다.
  인자 이름으로 bean과 auditable을 선언합니다.

첫 번째 매개변수 유형이 JoinPoint, ProceedingJoinPoint 또는JoinPoint.StaticPart인 경우argNames 속성 값에서 매개변수 이름을 생략할 수 있습니다. 예를 들어 조인 지점 개체를 수신하도록 앞의 조언을 수정하는 경우 argNames 속성에 해당 개체를 포함할 필요가 없습니다:

@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", 
	argNames = "bean,auditable") 
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
	AuditCode code = auditable.value();
	// ... use code, bean, and jp
}
  포인트 컷 표현식 결합에 정의된 publicMethod라는 포인트 컷을 참조합니다.
  인수 이름으로 bean  auditable을 선언합니다.

JoinPoint,ProceedingJoinPoint 또는 JoinPoint.StaticPart 유형의 첫 번째 매개변수에 대한 특별 취급은 다른 조인점 컨텍스트를 수집하지 않는 조언 메서드에 특히 편리합니다. 이러한 상황에서는 argNames 속성을 생략할 수 있습니다. 예를 들어 다음 조언은 argNames 속성을 선언할 필요가 없습니다:

@Before("com.xyz.Pointcuts.publicMethod()") 
public void audit(JoinPoint jp) {
	// ... use jp
}
  포인트컷 표현식 결합에 정의된 pointcut이라는 이름의 publicMethod를 참조합니다.

인수로 진행하기

앞서 Spring AOP와 AspectJ에서 일관되게 작동하는 인수를 사용하여 진행 호출을 작성하는 방법을 설명하겠다고 언급했습니다. 해결책은 조언 서명이 각 메서드 매개변수를 순서대로 바인딩하도록 하는 것입니다. 다음 예제는 이를 수행하는 방법을 보여줍니다:

@Around("execution(List<Account> find*(..)) && " +
		"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
		"args(accountHolderNamePattern)") 
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
		String accountHolderNamePattern) throws Throwable {
	String newPattern = preProcess(accountHolderNamePattern);
	return pjp.proceed(new Object[] {newPattern});
}
  명명된 포인트컷 정의 공유에 정의된 inDataAccessLayer 명명된 포인트컷을 참조합니다.

대부분의 경우 앞의 예제에서와 같이 이 바인딩을 수행합니다.

Advice Ordering

여러 개의 조언이 모두 동일한 조인 지점에서 실행되기를 원하면 어떻게 되나요? Spring AOP는 AspectJ와 동일한 우선순위 규칙에 따라 조언 실행 순서를 결정합니다. 우선 순위가 가장 높은 조언이 "들어오는 도중에" 먼저 실행됩니다(따라서 두 개의 전 조언이 주어지면 우선 순위가 가장 높은 조언이 먼저 실행됩니다). 조인 지점에서 "나가는 길"에는 우선 순위가 가장 높은 조언이 마지막으로 실행됩니다(따라서 두 개의 애프터 조언이 주어지면 우선 순위가 가장 높은 조언이 두 번째로 실행됩니다).

서로 다른 측면에서 정의된 두 개의 조언이 모두 동일한 조인 지점에서 실행되어야 하는 경우 달리 지정하지 않는 한 실행 순서는 정의되지 않습니다. 우선순위를 지정하여 실행 순서를 제어할 수 있습니다. 이는 일반적인 Spring 방식으로, aspect 클래스에서 org.springframework.core.Ordered 인터페이스를 구현하거나 @Order 어노테이션으로 어노테이션을 추가하여 수행합니다. 두 가지 측면이 주어지면 Ordered.getOrder() 에서 더 낮은 값(또는 어노테이션 값)을 반환하는 측면이 더 높은 우선순위를 갖습니다.

 
특정 측면의 각 고유한 조언 유형은 개념적으로 조인 지점에 직접 적용되도록 되어 있습니다. 따라서 @AfterThrowing 조언 메서드는 수반되는 @After/@AfterReturning 메서드에서 예외를 수신해서는 안 됩니다.
Spring 프레임워크 5.2.7부터 동일한 조인 포인트에서 실행되어야 하는 동일한 @Aspect 클래스에 정의된 조언 메서드는 조언 유형에 따라 우선순위가 높은 순서부터 낮은 순서로 우선순위가 할당됩니다: around, @Before, @After,@AfterReturning, @AfterThrowing. 그러나 @After 조언 메서드는 @After에 대한 AspectJ의 "최종 조언 후" 의미론에 따라 동일한 측면의 @AfterReturning 또는 @AfterThrowing 조언 메서드 뒤에 효과적으로 호출된다는 점에 유의하세요.
동일한 @Aspect 클래스에 정의된 동일한 유형의 조언(예: 두 개의 @After 조언 메서드)이 모두 동일한 조인 포인트에서 실행되어야 하는 경우 순서가 정의되지 않습니다(javac 컴파일 클래스에 대한 리플렉션을 통해 소스 코드 선언 순서를 검색할 방법이 없기 때문입니다). 이러한 조언 메서드를 각 @Aspect 클래스의 조인 지점당 하나의 조언 메서드로 축소하거나 Ordered 또는 @Order를 통해 측면 수준에서 순서를 지정할 수 있는 별도의 @Aspect 클래스로 조언 조각을 리팩터링하는 것을 고려하세요.

 

소개

인트로그레이션(AspectJ에서는 인터타입 선언이라고 함)을 사용하면 조언된 객체가 주어진 인터페이스를 구현한다고 선언하고 해당 객체를 대신하여 해당 인터페이스의 구현을 제공할 수 있습니다.

DeclareParents 어노테이션을 사용하여 도입할 수 있습니다. 이 어노테이션은 일치하는 유형에 새 부모(따라서 이름)가 있음을 선언하는 데 사용됩니다. 예를 들어 UsageTracked라는 인터페이스가 있고 그 인터페이스의 구현이DefaultUsageTracked라고 할 때, 다음 측면은 서비스 인터페이스의 모든 구현자가 UsageTracked 인터페이스도 구현한다고 선언합니다(예: JMX를 통한 통계의 경우):

@Aspect
public class UsageTracking {

	@DeclareParents(value="com.xyz.service.*+", defaultImpl=DefaultUsageTracked.class)
	public static UsageTracked mixin;

	@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
	public void recordUsage(UsageTracked usageTracked) {
		usageTracked.incrementUseCount();
	}

}

구현할 인터페이스는 주석이 달린 필드의 유형에 따라 결정됩니다. DeclareParents 어노테이션의값 속성은 AspectJ 유형 패턴입니다. 일치하는 유형의 모든 빈은 UsageTracked 인터페이스를 구현합니다. 앞의 예제에서 서비스 빈을 UsageTracked 인터페이스의 구현으로 직접 사용할 수 있다는 점에 유의하세요. 프로그래밍 방식으로 빈에 액세스하는 경우 다음과 같이 작성합니다:

UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);

 

액센트 인스턴스화 모델

  이것은 고급 주제입니다. AOP를 막 시작하는 경우 나중으로 건너뛰셔도 됩니다

기본적으로 애플리케이션 컨텍스트 내에는 각 측면의 단일 인스턴스가 있습니다. AspectJ에서는 이를 싱글톤 인스턴스화 모델이라고 부릅니다. 대체 수명 주기를 가진 측면을 정의할 수 있습니다. Spring은 인스턴스화 모델내에서 AspectJ의 perthis, pertarget,pertype을 지원하며, 현재 percflow와 percflowbelow는 지원되지 않습니다.

Aspect어노테이션에서 perthis 절을 지정하여 perthis 측면을 선언할 수 있습니다. 다음 예제를 살펴보세요:

@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
public class MyAspect {

	private int someState;

	@Before("execution(* com.xyz..service.*.*(..))")
	public void recordServiceUsage() {
		// ...
	}
}

앞의 예에서 perthis 절의 효과는 비즈니스 서비스를 수행하는 각 고유 서비스 개체(포인트컷 표현식과 일치하는 조인 지점에서 여기에 바인딩된 각 고유 개체)에 대해 하나의 측면 인스턴스가 생성된다는 것입니다. 서비스 객체에서 메서드가 처음 호출될 때 측면 인스턴스가 생성됩니다. 서비스 객체가 범위를 벗어나면 측면은 범위를 벗어납니다. 측면 인스턴스가 생성되기 전에는 그 안의 어떤 조언도 실행되지 않습니다. 측면 인스턴스가 생성되면 그 안에 선언된 조언은 일치하는 조인 지점에서 실행되지만 서비스 객체가 이 측면과 연관된 객체일 때만 실행됩니다. 절별 절에 대한 자세한 내용은 AspectJ 프로그래밍 가이드를 참조하세요.

대상별 인스턴스화 모델은 perthis와 정확히 동일한 방식으로 작동하지만 일치하는 조인 지점에서 각 고유한 대상 객체에 대해 하나의 측면 인스턴스를 생성합니다.

 

AOP 예제

이제 모든 구성 요소가 어떻게 작동하는지 살펴보았으니, 이를 조합하여 유용한 작업을 수행할 수 있습니다.

비즈니스 서비스의 실행은 때때로 동시성 문제(예: 교착 상태 패자)로 인해 실패할 수 있습니다. 작업을 다시 시도하면 다음 시도에서는 성공할 가능성이 높습니다. 이러한 조건에서 재시도하는 것이 적절한 비즈니스 서비스(충돌 해결을 위해 사용자에게 돌아갈 필요가 없는 무능력한 작업)의 경우, 클라이언트에게PessimisticLockingFailureException이 표시되지 않도록 작업을 투명하게 재시도하고 싶습니다. 이는 서비스 계층의 여러 서비스를 명확하게 구분하는 요구 사항이므로 측면을 통해 구현하는 데 이상적입니다.

작업을 다시 시도하고 싶기 때문에 진행을 여러 번 호출할 수 있도록 어라운드 어드바이스를 사용해야 합니다. 다음 목록은 기본적인 측면 구현을 보여줍니다:

@Aspect
public class ConcurrentOperationExecutor implements Ordered {

	private static final int DEFAULT_MAX_RETRIES = 2;

	private int maxRetries = DEFAULT_MAX_RETRIES;
	private int order = 1;

	public void setMaxRetries(int maxRetries) {
		this.maxRetries = maxRetries;
	}

	public int getOrder() {
		return this.order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Around("com.xyz.CommonPointcuts.businessService()") 
	public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
		int numAttempts = 0;
		PessimisticLockingFailureException lockFailureException;
		do {
			numAttempts++;
			try {
				return pjp.proceed();
			}
			catch(PessimisticLockingFailureException ex) {
				lockFailureException = ex;
			}
		} while(numAttempts <= this.maxRetries);
		throw lockFailureException;
	}
}
  명명된 포인트컷 정의 공유에 정의된 비즈니스 서비스 명명된 포인트컷을 참조합니다.

이 측면은 Ordered 인터페이스를 구현하므로 측면의 우선순위를 트랜잭션 조언보다 높게 설정할 수 있습니다(재시도할 때마다 새로운 트랜잭션을 원합니다). MaxRetries와 order 속성은 모두 Spring에 의해 구성됩니다. 주요 작업은 조언을 중심으로 doConcurrentOperation에서 발생합니다. 현재로서는 재시도 로직을 각 businessService에 적용합니다. 우리는 계속 진행하려고 시도하고, 모든 재시도 시도를 모두 소진하지 않는 한 PessimisticLockingFailureException으로 실패하면 다시 시도합니다.

해당 Spring 구성은 다음과 같습니다:

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor"
		class="com.xyz.service.impl.ConcurrentOperationExecutor">
	<property name="maxRetries" value="3"/>
	<property name="order" value="100"/>
</bean>

무능력한 연산만 재시도하도록 측면을 세분화하려면 다음과 같은Idempotent 어노테이션을 정의할 수 있습니다:

@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}

그런 다음 이 어노테이션을 사용하여 서비스 작업의 구현에 주석을 달 수 있습니다. 비강제 연산만 재시도하도록 측면을 변경하려면 다음과 같이 @Idempotent 연산만 일치하도록 포인트컷 표현식을 구체화해야 합니다:

@Around("execution(* com.xyz..service.*.*(..)) && " +
		"@annotation(com.xyz.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
	// ...
}

 

 

 

Schema-based AOP Support

XML 기반 형식을 선호하는 경우 Spring은 aop 네임스페이스 태그를 사용하여 측면을 정의하는 기능도 지원합니다. 애스펙트J 스타일을 사용할 때와 똑같은 포인트컷 표현식 및 조언 종류가 지원됩니다. 따라서 이 섹션에서는 해당 구문에 초점을 맞추고, 포인트컷 표현식 작성과 조언 매개변수의 바인딩에 대한 이해를 위해 이전 섹션(@AspectJ 지원)의 논의를 참조하시기 바랍니다.

이 섹션에 설명된 aop 네임스페이스 태그를 사용하려면 XML 스키마 기반 구성에 설명된 대로spring-aop 스키마를 가져와야 합니다. Aop 네임스페이스에서 태그를 가져오는 방법은 AOP 스키마를참조하세요.

Spring 구성 내에서 모든 측면 및 어드바이저 요소는 <aop:config> 요소 내에 배치되어야 합니다(애플리케이션 컨텍스트 구성에 둘 이상의 <aop:config> 요소를 가질 수 있음). <aop:config> 요소에는 포인트컷, 어드바이저 및 측면 요소가 포함될 수 있습니다(이러한 요소는 순서대로 선언되어야 함).

  <aop:config> 스타일의 구성은 Spring의자동 프록시 메커니즘을 많이 사용합니다.BeanNameAutoProxyCreator 또는 이와 유사한 것을 사용하여 이미 명시적 자동 프록시를 사용하는 경우 문제가 발생할 수 있습니다(예: 조언이 직조되지 않음). 권장되는 사용 패턴은 <aop:config> 스타일 또는 AutoProxyCreator 스타일 중 하나만 사용하고 절대 혼용하지 않는 것입니다

Declaring an Aspect

스키마 지원을 사용하는 경우, 측면은 Spring 애플리케이션 컨텍스트에서 빈으로 정의된 일반 Java 객체입니다. 상태와 동작은 객체의 필드와 메서드에 캡처되고, 포인트컷과 조언 정보는 XML에 캡처됩니다.

다음 예제에서 볼 수 있듯이 <aop:aspect> 요소를 사용하여 aspect를 선언하고 ref 속성을 사용하여 백킹 빈을 참조할 수 있습니다:

<aop:config>
	<aop:aspect id="myAspect" ref="aBean">
		...
	</aop:aspect>
</aop:config>

<bean id="aBean" class="...">
	...
</bean>

물론 다른 Spring 빈과 마찬가지로 측면을 뒷받침하는 빈( 이 경우aBean) 을 구성하고 종속성을 주입할 수 있습니다.

Declaring a Pointcut

<aop:config> 요소 내에 명명된 포인트컷을 선언하여 여러 측면과 어드바이저에서 포인트컷 정의를 공유할 수 있습니다.

서비스 계층에서 모든 비즈니스 서비스의 실행을 나타내는 포인트컷은 다음과 같이 정의할 수 있습니다:

<aop:config>

	<aop:pointcut id="businessService"
		expression="execution(* com.xyz.service.*.*(..))" />

</aop:config>

포인트컷 표현식 자체는 @AspectJ 지원에서 설명한 것과 동일한 AspectJ 포인트컷 표현식 언어를 사용한다는 점에 유의하세요. 스키마 기반 선언 스타일을 사용하는 경우, 포인트컷 표현식 내에서 @Aspect 유형으로 정의된 명명된 포인트컷을 참조할 수도 있습니다. 따라서 위의 포인트컷을 정의하는 또 다른 방법은 다음과 같습니다:

<aop:config>

	<aop:pointcut id="businessService"
		expression="com.xyz.CommonPointcuts.businessService()" /> 

</aop:config>
  명명된 포인트컷 정의 공유에 정의된 businessService 명명된 포인트컷을 참조합니다.

다음 예제에서 볼 수 있듯이, 컴포넌트 내부에서 포인트컷을 선언하는 것은 최상위 포인트컷을 선언하는 것과 매우 유사합니다:

<aop:config>

	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.service.*.*(..))"/>

		...
	</aop:aspect>

</aop:config>

스키마 기반 정의 스타일을 사용하여 선언된 포인트컷은 @AspectJ 측면과 거의 동일한 방식으로 조인 포인트 컨텍스트를 수집할 수 있습니다. 예를 들어, 다음 포인트컷은  개체를 조인 포인트 컨텍스트로 수집하여 조언에 전달합니다:

<aop:config>

	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.service.*.*(..)) &amp;&amp; this(service)"/>

		<aop:before pointcut-ref="businessService" method="monitor"/>

		...
	</aop:aspect>

</aop:config>

수집된 조인점 컨텍스트를 받으려면 다음과 같이 일치하는 이름의 매개 변수를 포함하여 조언을 선언해야 합니다:

public void monitor(Object service) {
	// ...
}

포인트컷 하위 표현식을 결합할 때 XML 문서 내에서 &amp;&amp; 는 어색하므로 키워드 대신 각각 &amp;&amp;,||, ! 대신 과, 또는, 및를 사용할 수 있습니다. 예를 들어 이전 포인트컷은 다음과 같이 작성하는 것이 좋습니다:

<aop:config>

	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.service.*.*(..)) and this(service)"/>

		<aop:before pointcut-ref="businessService" method="monitor"/>

		...
	</aop:aspect>

</aop:config>

이렇게 정의된 포인트컷은 XML ID로 참조되며 복합 포인트컷을 형성하기 위한 명명된 포인트컷으로 사용할 수 없습니다. 따라서 스키마 기반 정의 스타일의 명명된 포인트컷 지원은 @AspectJ 스타일에서 제공하는 것보다 더 제한적입니다.

Declaring Advice

스키마 기반 AOP 지원은 @AspectJ 스타일과 동일한 다섯 가지 종류의 조언을 사용하며, 그 의미는 완전히 동일합니다.

비포 어드바이스

일치하는 메서드 실행 전에 실행되는 비포 어드바이스입니다. 다음 예제에서 볼 수 있듯이 <aop:aspect> 안에 <aop :before> 요소를 사용하여 선언됩니다:

<aop:aspect id="beforeExample" ref="aBean">

	<aop:before
		pointcut-ref="dataAccessOperation"
		method="doAccessCheck"/>

	...

</aop:aspect>

위의 예에서 dataAccessOperation은 최상위(<aop:config>) 수준에서 정의된 명명된 포인트컷의 ID입니다 ( 포인트컷 선언하기 참조).

  명명된 포인트컷을 사용하면 코드의 가독성을 크게 향상시킬 수 있습니다(@AspectJ 스타일에 대한 설명에서 언급했듯이). 자세한 내용은 명명된 포인트컷 정의 공유를 참조하세요

대신 포인트컷을 인라인으로 정의하려면 다음과 같이 pointcut-ref 속성을포인트컷 속성으로 바꾸면 됩니다:

<aop:aspect id="beforeExample" ref="aBean">

	<aop:before
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doAccessCheck"/>

	...

</aop:aspect>

메서드 속성은 도움말 본문을 제공하는 메서드(doAccessCheck)를 식별합니다. 이 메서드는 조언이 포함된 측면 요소에서 참조하는 빈에 대해 정의되어야 합니다. 데이터 액세스 작업(포인트컷 표현식과 일치하는 메서드 실행 조인 지점)이 수행되기 전에 측면 빈의 doAccessCheck 메서드가 호출됩니다.

조언 반환 후

반환 후 조언은 일치하는 메서드 실행이 정상적으로 완료되면 실행됩니다. 이전 조언과 동일한 방식으로 <aop:aspect> 안에 선언됩니다. 다음 예제는 선언하는 방법을 보여줍니다:

<aop:aspect id="afterReturningExample" ref="aBean">

	<aop:after-returning
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doAccessCheck"/>

	...
</aop:aspect>

애스펙트J 스타일에서와 마찬가지로 조언 본문 내에서 반환 값을 가져올 수 있습니다. 이렇게 하려면 다음 예제와 같이 반환 속성을 사용하여 반환 값을 전달할 매개 변수의 이름을 지정합니다:

<aop:aspect id="afterReturningExample" ref="aBean">

	<aop:after-returning
		pointcut="execution(* com.xyz.dao.*.*(..))"
		returning="retVal"
		method="doAccessCheck"/>

	...
</aop:aspect>

DoAccessCheck 메서드는 retVal이라는 매개 변수를 선언해야 합니다. 이 매개변수의 유형은 @AfterReturning에 대해 설명한 것과 동일한 방식으로 일치를 제한합니다. 예를 들어 다음과 같이 메서드 서명을 선언할 수 있습니다:

public void doAccessCheck(Object retVal) {...

애프터 던지기 어드바이스

일치하는 메서드 실행이 예외를 던져 종료될 때 After Throwing 조언이 실행됩니다. 다음 예제에서 볼 수 있듯이 애프터 던지기 요소를 사용하여 <aop:aspect> 안에 선언됩니다:

<aop:aspect id="afterThrowingExample" ref="aBean">

	<aop:after-throwing
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doRecoveryActions"/>

	...
</aop:aspect>

@AspectJ 스타일에서와 같이 조언 본문 내에서 던져진 예외를 가져올 수 있습니다. 이렇게 하려면 다음 예제와 같이 throwing 속성을 사용하여 예외를 전달할 매개 변수의 이름을 지정합니다:

<aop:aspect id="afterThrowingExample" ref="aBean">

	<aop:after-throwing
		pointcut="execution(* com.xyz.dao.*.*(..))"
		throwing="dataAccessEx"
		method="doRecoveryActions"/>

	...
</aop:aspect>

DoRecoveryActions 메서드는 dataAccessEx라는 매개 변수를 선언해야 합니다. 이 매개 변수의 유형은@AfterThrowing에 대해 설명한 것과 같은 방식으로 일치를 제한합니다. 예를 들어 메서드 서명은 다음과 같이 선언할 수 있습니다:

public void doRecoveryActions(DataAccessException dataAccessEx) {...

After (Finally) Advice

After (Finally) Advice는 일치하는 메서드 실행이 어떻게 종료되든 상관없이 실행됩니다. 다음 예제에서 볼 수 있듯이 After 요소를 사용하여 선언할 수 있습니다:

<aop:aspect id="afterFinallyExample" ref="aBean">

	<aop:after
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doReleaseLock"/>

	...
</aop:aspect>

Around Advice

마지막 종류의 조언은 around 조언입니다. Around 조언은 일치하는 메서드의 실행 "주변"에서 실행됩니다. 메서드가 실행되기 전과 후에 모두 작업을 수행할 수 있으며, 언제, 어떻게, 심지어 메서드가 실제로 실행될지 여부를 결정할 수 있습니다. 타이머를 시작하고 중지하는 등 스레드에 안전한 방식으로 메서드 실행 전후의 상태를 공유해야 하는 경우 주변 조언이 자주 사용됩니다.

 
항상 요구 사항을 충족하는 가장 강력한 형태의 조언을 사용하세요.
예를 들어, 비포 어드바이저로 충분하다면 어라운드 어드바이저를 사용하지 마세요.

Aop:around 요소를 사용하여 주변 조언을 선언할 수 있습니다. 조언 메서드는 반환 유형으로 Object를 선언해야 하며, 메서드의 첫 번째 매개 변수는 ProceedingJoinPoint 유형이어야 합니다. 조언 메서드의 본문 내에서 ProceedingJoinPoint에 대해proceed() 를 호출해야 기본 메서드가 실행됩니다. 인자 없이 proceed()를 호출하면 호출 시 호출자의 원래 인수가 기본 메서드에 제공됩니다. 고급 사용 사례의 경우, 인수의 배열(Object[])을 허용하는 진행( ) 메서드의 오버로드된 변형이 있습니다. 배열의 값은 기본 메서드가 호출될 때 기본 메서드의 인수로 사용됩니다. Object[]로proceed를 호출할 때 참고할 사항은 주변 조언을 참조하세요.

다음 예제는 XML로 Around Advice를 선언하는 방법을 보여줍니다:

<aop:aspect id="aroundExample" ref="aBean">

	<aop:around
		pointcut="execution(* com.xyz.service.*.*(..))"
		method="doBasicProfiling"/>

	...
</aop:aspect>

다음 예제에서 볼 수 있듯이 doBasicProfiling 조언의 구현은 @AspectJ 예제와 완전히 동일할 수 있습니다(물론 어노테이션은 제외):

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
	// start stopwatch
	Object retVal = pjp.proceed();
	// stop stopwatch
	return retVal;
}

조언 매개변수

스키마 기반 선언 스타일은 @AspectJ 지원에 대해 설명한 것과 동일한 방식으로, 이름별로 포인트컷 매개변수를 조언 메서드 매개변수와 일치시키는 방식으로 완전 유형화된 조언을 지원합니다. 자세한 내용은 조언 매개변수를 참조하세요. 앞서 설명한 탐지 전략에 의존하지 않고 조언 메서드의 인수 이름을 명시적으로 지정하려는 경우, 조언 주석의 argNames속성과 동일한 방식으로 처리되는 조언 요소의 arg-names속성을 사용하면 됩니다( 인수 이름 결정에 설명된 대로). 다음 예는 XML에서 인수 이름을 지정하는 방법을 보여줍니다:

<aop:before
	pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" 
	method="audit"
	arg-names="auditable" />
  포인트컷 표현식 결합하기에서 정의된 publicMethod라는 이름의 pointcut을 참조합니다.

Arg-names 속성은 쉼표로 구분된 매개변수 이름 목록을 허용합니다.

XSD 기반 접근 방식의 약간 더 복잡한 다음 예제에서는 강력하게 입력된 여러 매개변수와 함께 사용되는 몇 가지 조언을 보여줍니다:

package com.xyz.service;

public interface PersonService {

	Person getPerson(String personName, int age);
}

public class DefaultPersonService implements PersonService {

	public Person getPerson(String name, int age) {
		return new Person(name, age);
	}
}

다음은 측면입니다. Profile(..) 메서드는 여러 개의 강력한 타입의 매개변수를 허용하며, 그 중 첫 번째가 메서드 호출을 진행하는 데 사용되는 조인 지점이라는 사실에 주목하세요. 이 매개변수가 있다는 것은 다음 예제에서 볼 수 있듯이profile(...)  어라운드 어드바이스로 사용된다는 것을 나타냅니다:

package com.xyz;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

	public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
		StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
		try {
			clock.start(call.toShortString());
			return call.proceed();
		} finally {
			clock.stop();
			System.out.println(clock.prettyPrint());
		}
	}
}

마지막으로, 다음 예제 XML 구성은 특정 조인 지점에 대한 앞의 조언 실행에 영향을 줍니다:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
	<bean id="personService" class="com.xyz.service.DefaultPersonService"/>

	<!-- this is the actual advice itself -->
	<bean id="profiler" class="com.xyz.SimpleProfiler"/>

	<aop:config>
		<aop:aspect ref="profiler">

			<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
				expression="execution(* com.xyz.service.PersonService.getPerson(String,int))
				and args(name, age)"/>

			<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
				method="profile"/>

		</aop:aspect>
	</aop:config>

</beans>

다음 드라이버 스크립트를 생각해 보세요:

public class Boot {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		PersonService person = ctx.getBean(PersonService.class);
		person.getPerson("Pengo", 12);
	}
}

이러한 Boot 클래스를 사용하면 표준 출력에서 다음과 유사한 출력을 얻을 수 있습니다:

StopWatch '프로파일링 'Pengo' 및 '12': 실행 시간(밀리초) = 0 ----------------------------------------- ms % 작업 이름 ----------------------------------------- 00000 ? execution(getFoo)

조언 순서 지정

여러 개의 조언이 동일한 조인 지점(실행 메서드)에서 실행되어야 하는 경우, 순서 지정 규칙은 조언 순서 지정에 설명된 대로입니다. 측면 간의 우선 순위는 <aop:aspect> 요소의 order 속성을 통해 결정되거나 측면을 뒷받침하는 빈에 @Order 어노테이션을 추가하거나 빈이 Ordered 인터페이스를 구현하도록 함으로써 결정됩니다.

 
동일한 @Aspect클래스에 정의된 조언 메서드의 우선 순위 규칙과 달리, 동일한 <aop:aspect> 요소에 정의된 두 개의 조언이 모두 동일한 조인 지점에서 실행되어야 하는 경우, 우선 순위는 둘러싸는 <aop:aspect> 요소 내에서 조언 요소가 선언된 순서에 따라 우선 순위가 높은 것부터 낮은 것 순으로 결정됩니다.
예를 들어, 동일한 조인 포인트에 적용되는 동일한<aop:aspect> 요소에 정의된 around 조언과 before 조언이 있는 경우, around조언이 before 조언보다 우선 순위가 높으려면 <aop:around> 요소가 <aop :before> 요소보다 먼저 선언되어야 합니다.
일반적으로 동일한 조인 포인트에 적용되는 동일한 <aop:aspect> 요소에 여러 개의 조언이 정의되어 있는 경우 이러한 조언 방법을 각 <aop:aspect> 요소의 조인 포인트당 하나의 조언 방법으로 축소하거나 조언을 측면 수준에서 정렬할 수 있는 별도의 <aop:aspect> 요소로 리팩터링하는 것을 고려하세요.

Introductions

인트로그레이션(AspectJ에서는 타입 간 선언이라고 함)을 사용하면 조언된 객체가 주어진 인터페이스를 구현하고 해당 객체를 대신하여 해당 인터페이스의 구현을 제공한다고 선언할 수 있습니다.

일치하는 유형에 새 부모(따라서 이름)가 있음을 선언하기 위해 aop :aspect 내에서 aop: declare-parents 요소를 사용하여 도입할 수 있습니다. 예를 들어 UsageTracked라는 인터페이스가 있고 그 인터페이스의 구현이DefaultUsageTracked라는 경우 다음 측면은 서비스 인터페이스의 모든 구현자가 UsageTracked 인터페이스도 구현한다고 선언합니다. (예를 들어 JMX를 통해 통계를 노출하기 위해)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

	<aop:declare-parents
		types-matching="com.xyz.service.*+"
		implement-interface="com.xyz.service.tracking.UsageTracked"
		default-impl="com.xyz.service.tracking.DefaultUsageTracked"/>

	<aop:before
		pointcut="execution(* com.xyz..service.*.*(..))
			and this(usageTracked)"
			method="recordUsage"/>

</aop:aspect>

그러면 usageTracking 빈을 뒷받침하는 클래스에는 다음 메서드가 포함됩니다:

public void recordUsage(UsageTracked usageTracked) {
	usageTracked.incrementUseCount();
}

구현할 인터페이스는 구현-인터페이스 속성에 의해 결정됩니다. 유형 일치 속성의 값은 AspectJ 유형 패턴입니다. 일치하는 유형의 모든 빈은 UsageTracked 인터페이스를 구현합니다. 앞의 예제에서 서비스 빈을 UsageTracked 인터페이스의 구현으로 직접 사용할 수 있다는 점에 유의하세요. 프로그래밍 방식으로 빈에 액세스하려면 다음과 같이 작성할 수 있습니다:

UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);

Aspect Instantiation Models

스키마 정의 측면에 대해 지원되는 인스턴스화 모델은 싱글톤 모델뿐입니다. 다른 인스턴스화 모델은 향후 릴리스에서 지원될 수 있습니다.

Advisors

"어드바이저"의 개념은 Spring에서 정의된 AOP 지원에서 유래했으며 AspectJ에는 직접적인 대응 개념이 없습니다. 어드바이저는 하나의 조언을 가진 작은 독립형 측면과 같습니다. 어드바이스 자체는 빈으로 표현되며Spring의 어드바이스 유형에 설명된 어드바이스 인터페이스 중 하나를 구현해야 합니다. 어드바이저는 AspectJ 포인트컷 표현식을 활용할 수 있습니다.

Spring은 <aop:advisor> 요소로 어드바이저 개념을 지원합니다. 가장 일반적으로 트랜잭션 조언과 함께 사용되는 것을 볼 수 있으며, Spring에서 자체 네임스페이스도 지원합니다. 다음 예제는 어드바이저를 보여줍니다:

<aop:config>

	<aop:pointcut id="businessService"
		expression="execution(* com.xyz.service.*.*(..))"/>

	<aop:advisor
		pointcut-ref="businessService"
		advice-ref="tx-advice" />

</aop:config>

<tx:advice id="tx-advice">
	<tx:attributes>
		<tx:method name="*" propagation="REQUIRED"/>
	</tx:attributes>
</tx:advice>

앞의 예제에서 사용된 pointcut-ref 속성뿐만 아니라pointcut 속성을 사용하여 포인트컷 식을 인라인으로 정의할 수도 있습니다.

어드바이저가 순서 지정에 참여할 수 있도록 어드바이저의 우선 순위를 정의하려면 order 속성을 사용하여 어드바이저의 Ordered 값을 정의합니다.

An AOP Schema Example

이 섹션에서는 스키마 지원으로 재작성된AOP 예제의 동시 잠금 실패 재시도 예제가 어떻게 보이는지 보여줍니다.

동시성 문제(예: 교착 상태 패자)로 인해 비즈니스 서비스 실행이 실패할 수 있습니다. 작업을 다시 시도하면 다음 시도에서는 성공할 가능성이 높습니다. 이러한 조건에서 재시도하는 것이 적절한 비즈니스 서비스(충돌 해결을 위해 사용자에게 돌아갈 필요가 없는 무능력한 작업)의 경우, 클라이언트에게PessimisticLockingFailureException이 표시되지 않도록 작업을 투명하게 재시도하고 싶습니다. 이는 서비스 계층의 여러 서비스를 명확하게 구분하는 요구 사항이므로 측면을 통해 구현하는 데 이상적입니다.

작업을 다시 시도하고 싶기 때문에 진행을 여러 번 호출할 수 있도록 어라운드 어드바이스를 사용해야 합니다. 다음 목록은 기본 측면 구현(스키마 지원을 사용하는 일반 Java 클래스)을 보여줍니다:

public class ConcurrentOperationExecutor implements Ordered {

	private static final int DEFAULT_MAX_RETRIES = 2;

	private int maxRetries = DEFAULT_MAX_RETRIES;
	private int order = 1;

	public void setMaxRetries(int maxRetries) {
		this.maxRetries = maxRetries;
	}

	public int getOrder() {
		return this.order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
		int numAttempts = 0;
		PessimisticLockingFailureException lockFailureException;
		do {
			numAttempts++;
			try {
				return pjp.proceed();
			}
			catch(PessimisticLockingFailureException ex) {
				lockFailureException = ex;
			}
		} while(numAttempts <= this.maxRetries);
		throw lockFailureException;
	}
}

이 측면은 Ordered 인터페이스를 구현하여 측면의 우선순위를 트랜잭션 조언보다 높게 설정할 수 있습니다(재시도할 때마다 새로운 트랜잭션이 필요하기 때문입니다). MaxRetries와 order 속성은 모두 Spring에 의해 구성됩니다. 주요 작업은 조언 메서드 주변의 doConcurrentOperation에서 발생합니다. 우리는 계속 진행하려고 합니다. 실패하면 모든 재시도 시도를 다 사용하지 않는 한 PessimisticLockingFailureException이 발생하고 다시 시도합니다.

  이 클래스는 @AspectJ 예제에서 사용된 것과 동일하지만 어노테이션이 제거되어 있습니다

해당 Spring 구성은 다음과 같습니다:

<aop:config>

	<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

		<aop:pointcut id="idempotentOperation"
			expression="execution(* com.xyz.service.*.*(..))"/>

		<aop:around
			pointcut-ref="idempotentOperation"
			method="doConcurrentOperation"/>

	</aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
	class="com.xyz.service.impl.ConcurrentOperationExecutor">
		<property name="maxRetries" value="3"/>
		<property name="order" value="100"/>
</bean>

당분간은 모든 비즈니스 서비스가 무능하다고 가정합니다. 그렇지 않은 경우, 다음 예제에서 볼 수 있듯이 Idempotent 어노테이션을 도입하고 어노테이션을 사용하여 서비스 연산 구현에 주석을 달아 진정한 무능력한 연산만 재시도하도록 측면을 세분화할 수 있습니다:

@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}

비강제 연산만 재시도하도록 측면을 변경하려면 다음과 같이 @Idempotent 연산만 일치하도록 포인트컷 표현식을 구체화해야 합니다:

<aop:pointcut id="idempotentOperation"
		expression="execution(* com.xyz.service.*.*(..)) and
		@annotation(com.xyz.service.Idempotent)"/>

Choosing which AOP Declaration Style to Use

주어진 요구 사항을 구현하기 위한 최선의 접근 방식이 애스펙트라고 결정한 후에는 Spring AOP 또는 AspectJ를 사용할지, Aspect 언어(코드) 스타일, @AspectJ 주석 스타일 또는 Spring XML 스타일 중에서 어떻게 결정해야 할까요? 이러한 결정은 애플리케이션 요구 사항, 개발 도구, AOP에 대한 팀의 친숙도 등 여러 가지 요소에 의해 영향을 받습니다.

Spring AOP or Full AspectJ?

작동할 수 있는 가장 간단한 것을 사용하세요. Spring AOP는 개발 및 빌드 프로세스에 AspectJ 컴파일러/위버를 도입할 필요가 없기 때문에 전체 AspectJ를 사용하는 것보다 더 간단합니다. Spring 빈에 대한 작업 실행만 조언해야 하는 경우 Spring AOP가 올바른 선택입니다. Spring 컨테이너에서 관리하지 않는 객체(일반적으로 도메인 객체 등)에 대해 조언해야 하는 경우, AspectJ를 사용해야 합니다. 또한 단순한 메서드 실행(예: 필드 가져오기 또는 설정 조인 지점 등) 이외의 조인 지점을 알려주려면 AspectJ를 사용해야 합니다.

AspectJ를 사용할 때는 AspectJ 언어 구문("코드 스타일"이라고도 함) 또는 @AspectJ 주석 스타일 중에서 선택할 수 있습니다. 디자인에서 측면이 큰 역할을 하고 Eclipse용 AspectJ 개발 도구(AJDT) 플러그인을 사용할 수 있는 경우 AspectJ 언어 구문이 선호되는 옵션입니다. 이 언어는 의도적으로 측면을 작성하기 위해 설계되었기 때문에 더 깔끔하고 간단합니다. Eclipse를 사용하지 않거나 애플리케이션에서 중요한 역할을 하지 않는 몇 가지 측면만 있는 경우 @AspectJ 스타일을 사용하고 IDE에서 일반 Java 컴파일을 고수하며 빌드 스크립트에 측면 위빙 단계를 추가하는 것을 고려할 수 있습니다.

@AspectJ or XML for Spring AOP?

Spring AOP를 사용하기로 선택한 경우 @AspectJ 또는 XML 스타일 중 하나를 선택할 수 있습니다. 고려해야 할 다양한 장단점이 있습니다.

XML 스타일은 기존 Spring 사용자에게 가장 친숙할 수 있으며, 정품 POJO에 의해 지원됩니다. 엔터프라이즈 서비스를 구성하는 도구로 AOP를 사용하는 경우 XML이 좋은 선택이 될 수 있습니다(포인트컷 표현식을 독립적으로 변경할 수 있는 구성의 일부로 간주하는지 여부를 테스트하는 것이 좋은 방법입니다). XML 스타일을 사용하면 구성에서 시스템에 어떤 측면이 존재하는지 더 명확하게 알 수 있습니다.

XML 스타일에는 두 가지 단점이 있습니다. 첫째, 다루는 요구 사항의 구현을 한 곳에 완전히 캡슐화하지 못한다는 점입니다. DRY 원칙에 따르면 시스템 내의 모든 지식은 모호하지 않고 권위 있는 단일 표현이 있어야 합니다. XML 스타일을 사용하는 경우 요구 사항이 어떻게 구현되는지에 대한 지식은 백킹 빈 클래스의 선언과 구성 파일의 XML로 나뉩니다. 하지만 @AspectJ 스타일을 사용하면 이 정보가 단일 모듈인 측면에 캡슐화됩니다. 둘째, XML 스타일은 @AspectJ 스타일보다 표현할 수 있는 내용이 약간 더 제한적입니다: "싱글톤" 측면 인스턴스화 모델만 지원되며, XML로 선언된 명명된 포인트컷을 결합할 수 없습니다. 예를 들어 @AspectJ 스타일에서는 다음과 같은 내용을 작성할 수 있습니다:

@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(com.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}

XML 스타일에서는 처음 두 개의 포인트컷을 선언할 수 있습니다:

<aop:pointcut id="propertyAccess"
		expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
		expression="execution(com.xyz.Account+ *(..))"/>

XML 접근 방식의 단점은 이러한 정의를 결합하여계정PropertyAccess 포인트컷을 정의할 수 없다는 것입니다.

애스펙트J 스타일은 추가 인스턴스화 모델과 더 풍부한 포인트컷 구성을 지원합니다. 측면을 모듈 단위로 유지할 수 있다는 장점이 있습니다. 또한 @AspectJ 측면은 Spring AOP와 AspectJ 모두에서 이해할 수 있다는(따라서 사용할 수 있다는) 장점이 있습니다. 따라서 나중에 추가 요구 사항을 구현하기 위해 AspectJ의 기능이 필요하다고 결정하면 클래식 AspectJ 설정으로 쉽게 마이그레이션할 수 있습니다. 일반적으로 Spring 팀은 엔터프라이즈 서비스의 단순한 구성 이상의 사용자 정의 측면에 대해 @AspectJ 스타일을 선호합니다.

액센트 타입 혼합하기

자동 프록시 지원, 스키마 정의된 <aop:aspect> 측면, <aop:advisor> 선언된 어드바이저, 심지어 동일한 구성에서 다른 스타일의 프록시와 인터셉터를 사용하여 @AspectJ 스타일 측면을 혼합하는 것은 완벽하게 가능합니다. 이 모든 것은 동일한 기본 지원 메커니즘을 사용하여 구현되며 아무런 어려움 없이 공존할 수 있습니다.

Proxying Mechanisms

Spring AOP는 JDK 동적 프록시 또는 CGLIB를 사용하여 지정된 대상 객체에 대한 프록시를 생성합니다. JDK 동적 프록시는 JDK에 내장되어 있는 반면, CGLIB는 일반적인 오픈 소스 클래스 정의 라이브러리( 스프링 코어로 재패키징)입니다.

프록시할 대상 객체가 하나 이상의 인터페이스를 구현하는 경우 JDK 동적 프록시가 사용됩니다. 대상 유형이 구현하는 모든 인터페이스가 프록시됩니다. 대상 객체가 인터페이스를 구현하지 않는 경우 CGLIB 프록시가 생성됩니다.

CGLIB 프록시를 강제로 사용하려는 경우(예: 인터페이스에서 구현된 메서드뿐만 아니라 대상 객체에 대해 정의된 모든 메서드를 프록시하려는 경우), 그렇게 할 수 있습니다. 하지만 다음과 같은 문제를 고려해야 합니다:

  • CGLIB를 사용하면 런타임에 생성된 서브클래스에서 재정의할 수 없으므로 최종 메서드를 권장할 수 없습니다.
  • Spring 4.0부터는 CGLIB 프록시 인스턴스가 Objenesis를 통해 생성되므로 프록시된 객체의 생성자가 더 이상 두 번 호출되지 않습니다. JVM이 생성자 우회를 허용하지 않는 경우에만 Spring의 AOP 지원에서 이중 호출 및 해당 디버그 로그 항목이 표시될 수 있습니다.
  • JDK 9+ 플랫폼 모듈 시스템에서는 CGLIB 프록시 사용에 제한이 있을 수 있습니다. 일반적인 경우로, 모듈 경로에 배포할 때 java.lang패키지에서 클래스에 대한 CGLIB 프록시를 생성할 수 없습니다. 이러한 경우에는 모듈에 사용할 수 없는 JVM 부트스트랩 플래그--add-opens=java.base/java.lang=ALL-UNNAMED가 필요합니다.

CGLIB 프록시를 강제로 사용하려면 다음과 같이 <aop:config> 요소의 proxy-target-class 속성 값을 true로 설정합니다:

<aop:config proxy-target-class="true">
	<!-- other beans defined here... -->
</aop:config>

다음과 같이 @AspectJ 자동 프록시 지원을 사용할 때 CGLIB 프록시를 강제로 사용하려면 다음과 같이 <aop:aspectj-autoproxy> 요소의proxy-target-class 속성을 true로 설정합니다:

<aop:aspectj-autoproxy proxy-target-class="true"/>
 
여러 개의 <aop:config/> 섹션이 런타임에 하나의 통합된 자동 프록시 생성자로 축소되어<aop:config/> 섹션(일반적으로 다른 XML 빈 정의 파일에서)에 지정된 가장 강력한 프록시 설정이 적용됩니다. 이는 <tx:annotation-driven/>  <aop:aspectj-autoproxy/>요소에도 적용됩니다.
명확하게 말하면, <tx:annotation-driven/>,<aop:aspectj-autoproxy/> 또는 <aop:config/> 요소에 proxy-target-class="true "를 사용하면 세 요소 모두에 CGLIB 프록시를 강제로 사용하게 됩니다.

Understanding AOP Proxies

Spring AOP는 프록시 기반입니다. 자체적인 측면을 작성하거나 Spring 프레임워크와 함께 제공되는 Spring AOP 기반 측면을 사용하기 전에 마지막 문장이 실제로 무엇을 의미하는지 파악하는 것이 매우 중요합니다.

먼저 다음 코드 스니펫에서 볼 수 있듯이 프록시되지 않은, 특별한 것이 없는 평범한 직선 객체 참조가 있는 시나리오를 생각해 보세요:

public class SimplePojo implements Pojo {

	public void foo() {
		// this next method invocation is a direct call on the 'this' reference
		this.bar();
	}

	public void bar() {
		// some logic...
	}
}

객체 참조에서 메서드를 호출하면 다음 이미지와 목록에서 볼 수 있듯이 해당 객체 참조에서 직접 메서드가 호출됩니다:

public class Main {

	public static void main(String[] args) {
		Pojo pojo = new SimplePojo();
		// this is a direct method call on the 'pojo' reference
		pojo.foo();
	}
}

클라이언트 코드의 참조가 프록시인 경우에는 상황이 약간 달라집니다. 다음 다이어그램과 코드 스니펫을 살펴보세요:

public class Main {

	public static void main(String[] args) {
		ProxyFactory factory = new ProxyFactory(new SimplePojo());
		factory.addInterface(Pojo.class);
		factory.addAdvice(new RetryAdvice());

		Pojo pojo = (Pojo) factory.getProxy();
		// this is a method call on the proxy!
		pojo.foo();
	}
}

여기서 이해해야 할 핵심 사항은 Main 클래스의 main(..) 메서드 안에 있는 클라이언트 코드가 프록시에 대한 참조를 가지고 있다는 것입니다. 즉, 해당 객체 참조에 대한 메서드 호출은 프록시에 대한 호출입니다. 결과적으로 프록시는 특정 메서드 호출과 관련된 모든 인터셉터(조언)에 위임할 수 있습니다. 그러나 호출이 최종적으로 대상 객체(이 경우 SimplePojo 참조)에 도달하면 this.bar() 또는this.foo()와 같이 자체적으로 수행할 수 있는 모든 메서드 호출은 프록시가 아닌  참조에 대해 호출됩니다. 이는 중요한 의미를 갖습니다. 즉, 자체 호출은 메서드 호출과 관련된 조언이 실행될 기회를 얻지 못한다는 것을 의미합니다.

그렇다면 어떻게 해야 할까요? 가장 좋은 접근 방식(여기서 "최선"이라는 용어는 느슨하게 사용됨)은 자체 호출이 발생하지 않도록 코드를 리팩터링하는 것입니다. 여기에는 약간의 작업이 수반되지만 가장 좋고 가장 덜 침습적인 접근 방식입니다. 다음 접근 방식은 완전히 끔찍하며, 너무 끔찍해서 지적하는 것을 주저합니다. 다음 예제에서 볼 수 있듯이 (우리에게는 고통스럽지만) 클래스 내의 로직을 Spring AOP에 완전히 연결할 수 있습니다:

public class SimplePojo implements Pojo {

	public void foo() {
		// this works, but... gah!
		((Pojo) AopContext.currentProxy()).bar();
	}

	public void bar() {
		// some logic...
	}
}

이렇게 하면 코드가 Spring AOP에 완전히 연결되고 클래스 자체가 AOP 컨텍스트에서 사용되고 있다는 사실을 인식하게 되므로 AOP에 정면으로 위배됩니다. 또한 다음 예제에서 볼 수 있듯이 프록시가 생성될 때 몇 가지 추가 구성이 필요합니다:

public class Main {

	public static void main(String[] args) {
		ProxyFactory factory = new ProxyFactory(new SimplePojo());
		factory.addInterface(Pojo.class);
		factory.addAdvice(new RetryAdvice());
		factory.setExposeProxy(true);

		Pojo pojo = (Pojo) factory.getProxy();
		// this is a method call on the proxy!
		pojo.foo();
	}
}

마지막으로, AspectJ는 프록시 기반 AOP 프레임워크가 아니기 때문에 이러한 자체 호출 문제가 없다는 점에 유의해야 합니다.

프로그래밍 방식으로 @AspectJ 프록시 생성하기

<aop:config>또는 <aop:aspectj-autoproxy>를 사용하여 구성에서 측면을 선언하는 것 외에도 프로그래밍 방식으로 대상 객체에 조언하는 프록시를 생성할 수도 있습니다. Spring의 AOP API에 대한 자세한 내용은다음 장을 참조하세요. 여기서는 @AspectJ 측면을 사용하여 프록시를 자동으로 생성하는 기능에 중점을 두고자 합니다.

Org.springframework.aop.aspectj.annotation.AspectJProxyFactory 클래스를 사용하여 하나 이상의 @AspectJ 측면에 의해 조언되는 대상 객체에 대한 프록시를 만들 수 있습니다. 이 클래스의 기본 사용법은 다음 예제에서 볼 수 있듯이 매우 간단합니다:

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied
// must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

자세한 내용은 자바독을 참조하세요.

Using AspectJ with Spring Applications

이 장에서 지금까지 다룬 모든 내용은 순수한 Spring AOP입니다. 이 섹션에서는 Spring AOP에서만 제공하는 기능을 넘어서는 요구 사항이 있는 경우 Spring AOP 대신 또는 추가로 AspectJ 컴파일러 또는 위버를 사용하는 방법에 대해 살펴봅니다.

Spring은 배포에서 spring-aspects.jar로 독립적으로 사용할 수 있는 작은 AspectJ 액센트 라이브러리와 함께 제공됩니다. 이 라이브러리의 측면을 사용하려면 클래스 경로에 추가해야 합니다. AspectJ를 사용하여 Spring에 종속성 주입 도메인 객체  기타 스프링의 AspectJ용 측면 에서는 이 라이브러리의 내용과 사용 방법에 대해 설명합니다. Spring IoC를 사용하여 AspectJ 컴포넌트 구성하기에서는 AspectJ 컴파일러를 사용하여 직조된 AspectJ 컴포넌트를 종속성 주입하는 방법에 대해 설명합니다. 마지막으로Spring 프레임워크에서 AspectJ를 사용한 로드 타임 위빙에서는 AspectJ를 사용하는 Spring 애플리케이션의 로드 타임 위빙에 대해 소개합니다.

Using AspectJ to Dependency Inject Domain Objects with Spring

Spring 컨테이너는 애플리케이션 컨텍스트에서 정의된 빈을 인스턴스화하고 구성합니다. 또한 적용할 구성이 포함된 빈 정의의 이름이 주어지면 빈 팩토리에 기존 객체를 구성하도록 요청할 수 있습니다.spring-aspects.jar에는 이 기능을 활용하여 모든 객체의 종속성 주입을 허용하는 어노테이션 기반 측면이 포함되어 있습니다. 이 지원은 컨테이너의 제어 외부에서 생성된 객체에 사용하기 위한 것입니다. 도메인 객체는 종종 데이터베이스 쿼리의 결과로새 연산자나 ORM 도구를 사용하여 프로그래밍 방식으로 생성되기 때문에 이 범주에 속합니다.

구성 가능 어노테이션은 클래스가 Spring 기반 구성을 사용할 수 있는 것으로 표시합니다. 가장 간단한 경우에는 다음 예제에서 볼 수 있듯이 마커 어노테이션으로 순수하게 사용할 수 있습니다:

package com.xyz.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
	// ...
}

이러한 방식으로 마커 인터페이스로 사용하는 경우, Spring은 정규화된 유형 이름(com.xyz.domain.Account)과 동일한 이름의 빈 정의(일반적으로 프로토타입 범위)를 사용하여 주석이 달린 유형(이 경우Account)의 새 인스턴스를 구성합니다. 빈의 기본 이름은 해당 유형의 정규화된 이름이므로 다음 예제에서 볼 수 있듯이 프로토타입 정의를 선언하는 편리한 방법은 id 속성을 생략하는 것입니다:

<bean class="com.xyz.domain.Account" scope="prototype">
	<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

사용할 프로토타입 빈 정의의 이름을 명시적으로 지정하려면 다음 예제와 같이 어노테이션에서 직접 지정할 수 있습니다:

package com.xyz.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
	// ...
}

이제 Spring은 계정이라는 이름의 빈 정의를 찾고 이를 정의로 사용하여 새 계정 인스턴스를 구성합니다.

또한 자동 배선을 사용하여 전용 빈 정의를 전혀 지정하지 않아도 됩니다. Spring이 자동 배선을 적용하도록 하려면 @Configurable어노테이션의 자동 와이어 속성을 사용합니다. 유형별 또는 이름별 자동 배선을 위해 @Configurable(autowire=Autowire.BY_TYPE) 또는@Configurable(autowire=Autowire.BY_NAME )을 각각 지정할 수 있습니다. 또는 필드 또는 메서드 수준에서 @Autowired 또는 @Inject를통해 @Configurable 빈에 대한 명시적인 어노테이션 기반 종 속성 주입을 지정하는 것이 좋습니다(자세한 내용은 어노테이션 기반 컨테이너 구성을 참조하세요).

마지막으로, 의존성 검사 속성을 사용하여 새로 생성 및 구성된 객체의 객체 참조에 대해 Spring 의존성 검사를 활성화할 수 있습니다(예:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true). 이 속성이 true로 설정되면 Spring은 구성 후 모든 속성(프리미티브나 컬렉션이 아닌)이 설정되었는지 유효성을 검사합니다.

어노테이션을 단독으로 사용하면 아무 일도 일어나지 않는다는 점에 유의하세요. 어노테이션의 존재 여부에 따라 작동하는 것은 spring-aspects.jar의AnnotationBeanConfigurerAspect입니다. 본질적으로 이 측면은 " @Configurable로 어노테이션된 유형의 새 객체의 초기화에서 돌아온 후 어노테이션의 속성에 따라 Spring을 사용하여 새로 생성된 객체를 구성한다"고 말합니다. 이 문맥에서 "초기화"는 새로 인스턴스화된 객체(예:  연산자로 인스턴스화된 객체)와 역직렬화 중인 직렬화 가능 객체(예:readResolve()를 통해)를 의미합니다.

 
위 단락의 핵심 문구 중 하나는 "본질적으로"입니다. 대부분의 경우 "새 객체의 초기화에서 돌아온 후"의 정확한 의미는 괜찮습니다. 이 문맥에서 "초기화 후"는 객체가 생성된 후에 종속성이 주입된다는 의미입니다. 즉, 종속성은 클래스의 생성자 본문에서 사용할 수 없습니다. 생성자 본문이 실행되기 전에 종속성을 주입하여 생성자 본문에서 사용할 수 있도록 하려면 다음과 같이@Configurable 선언에서 이를 정의해야 합니다:
 
AspectJ의 다양한 포인트컷 유형의 언어 의미에 대한 자세한 내용은 AspectJ프로그래밍 가이드의이 부록에서 확인할 수 있습니다.

이 기능을 사용하려면 주석이 달린 타입을 AspectJ 위버로 직조해야 합니다. 이를 위해 빌드 타임에 Ant 또는 Maven 작업을 사용하거나(예:AspectJ 개발 환경 가이드 참조), 로드 타임에 위빙할 수 있습니다( Spring 프레임워크에서 AspectJ를 사용한 로드 타임 위빙 참조). 새 객체를 구성하는 데 사용할 빈 팩토리에 대한 참조를 얻으려면 Spring에서AnnotationBeanConfigurerAspect 자체를 구성해야 합니다(새 객체를 구성하는 데 사용할 빈 팩토리에 대한 참조를 얻으려면). Java 기반 구성을 사용하는 경우, 다음과 같이@Configuration 클래스에 @EnableSpringConfigured를 추가하면 됩니다:

@Configuration
@EnableSpringConfigured
public class AppConfig {
}

XML 기반 구성을 선호하는 경우, Spring컨텍스트 네임스페이스는다음과 같이 사용할 수 있는 편리한 context:spring-configured 요소를 정의합니다:

<context:spring-configured/>

측면이 구성되기 전에 생성된 @Configurable 객체의 인스턴스는 디버그 로그에 메시지가 발행되고 객체의 구성이 수행되지 않습니다. 예를 들어 Spring 구성에서 Spring에 의해 초기화될 때 도메인 객체를 생성하는 빈이 있을 수 있습니다. 이 경우의존하는 빈 속성을 사용하여 빈이 구성 측면에 의존하도록 수동으로 지정할 수 있습니다. 다음 예제는 depends-on 속성을 사용하는 방법을 보여줍니다:

<bean id="myService"
		class="com.xyz.service.MyService"
		depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

	<!-- ... -->

</bean>
  런타임에 해당 시맨틱에 의존하려는 경우가 아니라면 @Configurable 처리를 빈 컨피규레이터 측면을 통해 활성화하지 마세요. 특히 컨테이너와 함께 일반 Spring 빈으로 등록된 빈 클래스에는 @Configurable을 사용하지 않도록 하세요. 그렇게 하면 컨테이너를 통해 한 번, 측면을 통해 한 번, 두 번 초기화됩니다

구성 가능한 객체 단위 테스트

컨피규러블 지원의 목표 중 하나는 하드코딩된 조회와 관련된 어려움 없이 도메인 객체의 독립적인 단위 테스트를 가능하게 하는 것입니다. 컨피규러블 유형이 AspectJ에 의해 직조되지 않은 경우, 단위 테스트 중에 어노테이션은 영향을 미치지 않습니다. 테스트 대상 오브젝트에 모의 또는 스텁 프로퍼티 참조를 설정하고 정상적으로 진행할 수 있습니다. 구성 가능 유형이 AspectJ에 의해 생성된 경우 컨테이너 외부에서 정상적으로 유닛 테스트를 수행할 수 있지만, @Configurable 객체를 구성할 때마다 Spring에서 구성되지 않았음을 나타내는 경고 메시지가 표시됩니다.

여러 애플리케이션 컨텍스트로 작업하기

구성 가능 지원을 구현하는 데 사용되는 AnnotationBeanConfigurerAspect는 AspectJ 싱글톤 측면입니다. 싱글톤 측면의 범위는 정적 멤버의 범위와 동일합니다: 클래스로더당 유형을 정의하는 하나의 aspect 인스턴스가 있습니다. 즉, 동일한 클래스로더계층 구조 내에서 여러 애플리케이션 컨텍스트를 정의하는 경우 @EnableSpringConfigured 빈을 정의할 위치와 클래스 경로에서 spring-aspects.jar를 어디에 배치할지 고려해야 합니다.

일반적인 비즈니스 서비스를 정의하는 공유 상위 애플리케이션 컨텍스트와 이러한 서비스를 지원하는 데 필요한 모든 것, 그리고 각 서블릿에 대한 하나의 하위 애플리케이션 컨텍스트(해당 서블릿에 대한 특정 정의가 포함됨)가 있는 일반적인 Spring 웹 애플리케이션 구성을 생각해 보세요. 이러한 모든 컨텍스트는 동일한 ClassLoader 계층 구조 내에 공존하므로 AnnotationBeanConfigurerAspect는 이 중 하나에 대한 참조만 보유할 수 있습니다. 이 경우 공유(부모) 애플리케이션 컨텍스트에서 @EnableSpringConfigured 빈을 정의하는 것이 좋습니다. 이는 도메인 개체에 주입할 가능성이 높은 서비스를 정의합니다. 결과적으로 @Configurable 메커니즘을 사용하여 자식(서블릿별) 컨텍스트에 정의된 빈에 대한 참조로 도메인 객체를 구성할 수 없습니다(어차피 원치 않는 작업일 수 있습니다).

동일한 컨테이너 내에 여러 웹 애플리케이션을 배포하는 경우, 각 웹 애플리케이션이 자체 ClassLoader를사용하여 spring-aspects.jar의 유형을 로드하는지 확인하세요(예: WEB-INF/lib에 spring-aspects.jar를 배치하는 방식). Spring-aspects.jar가컨테이너 전체 클래스 경로에만 추가되면(따라서 공유 부모ClassLoader에 의해 로드되면) 모든 웹 애플리케이션이 동일한 aspect 인스턴스를 공유하게 됩니다(이는 원하는 것이 아닐 수 있습니다).

Other Spring aspects for AspectJ

Spring-aspects.jar에는 @Configurable 어스펙트 외에도 @Transactional 어노테이션으로 주석이 달린 유형과 메서드에 대한 Spring의 트랜잭션 관리를 구동하는 데 사용할 수 있는 AspectJ 어스펙트가 포함되어 있습니다. 이것은 주로 Spring 컨테이너 외부에서 Spring 프레임워크의 트랜잭션 지원을 사용하려는 사용자를 위한 것입니다.

트랜잭션 어노테이션을 해석하는 어스펙트는AnnotationTransactionAspect입니다. 이 측면을 사용할 때는 클래스가 구현하는 인터페이스(있는 경우)가 아니라 구현 클래스(또는 해당 클래스 내의 메서드 또는 둘 다)에 주석을 달아야 합니다. AspectJ는 인터페이스에 대한 어노테이션은 상속되지 않는다는 Java의 규칙을 따릅니다.

클래스의 @Transactional 어노테이션은 클래스의 공용 연산 실행에 대한 기본 트랜잭션 시맨틱을 지정합니다.

클래스 내의 메서드에 대한 @Transactional 어노테이션은 클래스 어노테이션(있는 경우)에 의해 지정된 기본 트랜잭션 의미를 재정의합니다. 비공개 메서드를 포함하여 모든 가시성의 메서드에 주석을 달 수 있습니다. 비공개 메서드에 직접 주석을 다는 것이 그러한 메서드의 실행을 위한 트랜잭션 경계를 얻는 유일한 방법입니다.

  스프링 프레임워크 4.2부터 스프링-aspects는 표준 jakarta.transaction.Transactional 어노테이션에 대해 정확히 동일한 기능을 제공하는 유사한 측면을 제공합니다. 자세한 내용은JtaAnnotationTransactionAspect를 참조하세요

Spring 구성 및 트랜잭션 관리 지원을 사용하고 싶지만 어노테이션을 사용하고 싶지 않거나 사용할 수 없는 AspectJ 프로그래머를 위해 spring-aspects.jar에는자신만의 포인트컷 정의를 제공하기 위해 확장할 수 있는 추상적인 측면도 포함되어 있습니다. 자세한 내용은 AbstractBeanConfigurerAspect 및AbstractTransactionAspect 측면의 소스를 참조하세요. 예를 들어, 다음 발췌문은 정규화된 클래스 이름과 일치하는 프로토타입 빈 정의를 사용하여 도메인 모델에 정의된 객체의 모든 인스턴스를 구성하는 측면을 작성하는 방법을 보여줍니다:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

	public DomainObjectConfiguration() {
		setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
	}

	// the creation of a new bean (any object in the domain model)
	protected pointcut beanCreation(Object beanInstance) :
		initialization(new(..)) &&
		CommonPointcuts.inDomainModel() &&
		this(beanInstance);
}

Configuring AspectJ Aspects by Using Spring IoC

Spring 애플리케이션에서 AspectJ 측면을 사용하는 경우, 이러한 측면을 Spring으로 구성할 수 있기를 원하고 기대하는 것은 당연합니다. AspectJ 런타임 자체는 측면 생성을 담당하며, Spring을 통해 AspectJ로 생성된 측면을 구성하는 방법은 측면이 사용하는 AspectJ 인스턴스화 모델( per-xxx 절)에 따라 달라집니다.

대부분의 AspectJ 측면은 싱글톤 측면입니다. 이러한 측면의 구성은 쉽습니다. 정상적으로 측면 유형을 참조하는 빈 정의를 생성하고 factory-method="aspectOf" 빈 속성을 포함하면 됩니다. 이렇게 하면 Spring이 직접 인스턴스를 생성하지 않고 AspectJ에 요청하여 aspect 인스턴스를 가져올 수 있습니다. 다음 예제는 factory-method="aspectOf" 속성을 사용하는 방법을 보여줍니다:

<bean id="profiler" class="com.xyz.profiler.Profiler"
		factory-method="aspectOf"> 

	<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
  Factory-method="aspectOf" 속성에 주목하세요

싱글톤이 아닌 측면은 구성하기가 더 어렵습니다. 그러나 프로토타입 빈 정의를 생성하고spring-aspects.jar의 @Configurable 지원을 사용하여 AspectJ 런타임에서 빈을 생성한 후 측면 인스턴스를 구성하는 것이 가능합니다.

AspectJ로 위빙하려는 일부 @AspectJ 측면(예: 도메인 모델 유형에 로드 시간 위빙 사용)과 Spring AOP로 사용하려는 다른 @AspectJ 측면이 있고 이러한 측면이 모두 Spring에서 구성된 경우, 구성에 정의된 @AspectJ 측면의 정확한 하위 집합을 자동 프록시에 사용해야 하는지를 Spring AOP @AspectJ 자동 프록시링 지원에 알려주어야 합니다. 이 작업은 <aop:aspectj-autoproxy/>선언 내에 하나 이상의 <include/> 요소를 사용하여 수행할 수 있습니다. 각 <include/> 요소는 이름 패턴을 지정하며, 패턴 중 하나 이상과 일치하는 이름을 가진 빈만 Spring AOP 자동 프록시 구성에 사용됩니다. 다음 예제는 <include/> 요소를 사용하는 방법을 보여줍니다:

<aop:aspectj-autoproxy>
	<aop:include name="thisBean"/>
	<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
  <aop:aspectj-autoproxy/> 요소의 이름에 현혹되지 마세요. 이 요소를 사용하면 Spring AOP 프록시가 생성됩니다. 여기에서는 @AspectJ 스타일의 aspect 선언이 사용되었지만 AspectJ 런타임은 관여하지 않습니다

Load-time Weaving with AspectJ in the Spring Framework

로드 시간 위빙(LTW)은 애플리케이션의 클래스 파일이 Java 가상 머신(JVM)으로 로드될 때 AspectJ 측면을 엮어내는 프로세스를 말합니다. 이 섹션에서는 Spring 프레임워크의 특정 상황에서 LTW를 구성하고 사용하는 데 중점을 둡니다. 이 섹션은 LTW에 대한 일반적인 소개가 아닙니다. LTW의 세부 사항과 Spring이 전혀 관여하지 않은 상태에서 AspectJ만으로 LTW를 구성하는 방법에 대한 자세한 내용은AspectJ 개발 환경 가이드의 LTW 섹션을 참조하세요.

Spring 프레임워크가 AspectJ LTW에 제공하는 가치는 위빙 프로세스를 훨씬 더 세밀하게 제어할 수 있게 해준다는 데 있습니다. '바닐라' AspectJ LTW는 Java(5+) 에이전트를 사용하여 적용되며, 이 에이전트는 JVM을 시작할 때 VM 인수를 지정하여 켜집니다. 따라서 JVM 전체에 적용되는 설정으로, 일부 상황에서는 괜찮을 수 있지만 너무 거칠게 설정되는 경우가 많습니다. Spring을 사용하면 ClassLoader별로 LTW를 설정할 수 있으며, 이는 보다 세분화되어 일반적인 애플리케이션 서버 환경과 같은 '단일 JVM-다중 애플리케이션' 환경에서 더 적합할 수 있습니다.

또한 특정 환경에서는 이 지원을 통해 -javaagent:path/to/aspectjweaver.jar 또는 (이 섹션의 뒷부분에서 설명하는 것처럼) -javaagent:path/to/spring-instrument.jar를 추가하는 데 필요한 애플리케이션 서버의 시작 스크립트를 수정하지 않고도 로드 시간 위빙을 수행할 수 있습니다. 개발자는 일반적으로 실행 스크립트와 같은 배포 구성을 담당하는 관리자에게 의존하는 대신 로드 시 위빙을 활성화하도록 애플리케이션 컨텍스트를 구성합니다.

이제 세일즈 피치가 끝났으니 먼저 Spring을 사용하는 AspectJ LTW의 간단한 예제를 살펴본 다음 예제에 소개된 요소에 대한 자세한 내용을 살펴보겠습니다. 전체 예제는펫클리닉 샘플 애플리케이션을 참조하세요.

첫 번째 예제

시스템에서 일부 성능 문제의 원인을 진단하는 임무를 맡은 애플리케이션 개발자가 있다고 가정해 보겠습니다. 프로파일링 도구를 사용하는 대신 몇 가지 성능 메트릭을 빠르게 얻을 수 있는 간단한 프로파일링 측면을 사용하겠습니다. 그런 다음 바로 해당 특정 영역에 더 세분화된 프로파일링 도구를 적용할 수 있습니다.

  여기에 제시된 예제에서는 XML 구성을 사용합니다. Java 구성으로@AspectJ를 구성하여 사용할 수도 있습니다. 특히, <context:load-time-weaver/>대신@EnableLoadTimeWeaving 어노테이션을 사용할 수 있습니다(자세한 내용은 아래 참조)

다음 예제는 @AspectJ 스타일의 측면 선언을 사용하는 시간 기반 프로파일러로, 화려하지는 않지만 프로파일링 측면을 보여줍니다:

package com.xyz;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

	@Around("methodsToBeProfiled()")
	public Object profile(ProceedingJoinPoint pjp) throws Throwable {
		StopWatch sw = new StopWatch(getClass().getSimpleName());
		try {
			sw.start(pjp.getSignature().getName());
			return pjp.proceed();
		} finally {
			sw.stop();
			System.out.println(sw.prettyPrint());
		}
	}

	@Pointcut("execution(public * com.xyz..*.*(..))")
	public void methodsToBeProfiled(){}
}

또한 AspectJ 위버에게 ProfilingAspect를 클래스에 위빙할 것을 알리기 위해 META-INF/aop.xml 파일을 만들어야 합니다. 이 파일 규칙, 즉 Java 클래스 경로에 META-INF/aop.xml이라는 파일이 존재하는 것은 표준 AspectJ입니다. 다음 예제는 aop.xml 파일을 보여줍니다:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

	<weaver>
		<!-- only weave classes in our application-specific packages and sub-packages -->
		<include within="com.xyz..*"/>
	</weaver>

	<aspects>
		<!-- weave in just this aspect -->
		<aspect name="com.xyz.ProfilingAspect"/>
	</aspects>

</aspectj>
  AspectJ 덤프 파일 및 경고와 같은 부작용을 피하기 위해 특정 클래스(일반적으로 애플리케이션 패키지에 있는 클래스)만 위빙하는 것이 좋습니다. 이는 효율성 관점에서도 모범 사례입니다

이제 구성의 Spring 관련 부분으로 넘어가겠습니다. 로드 타임 위 버를 구성해야 합니다(나중에 설명). 이 로드 타임 위버는 하나 이상의 META-INF/aop.xml 파일에서 애플리케이션의 클래스에 대한 측면 구성을 직조하는 필수 구성 요소입니다. 좋은 점은 다음 예제에서 볼 수 있듯이 많은 구성이 필요하지 않다는 것입니다(지정할 수 있는 옵션이 몇 가지 더 있지만 나중에 자세히 설명합니다):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- a service object; we will be profiling its methods -->
	<bean id="entitlementCalculationService"
			class="com.xyz.StubEntitlementCalculationService"/>

	<!-- this switches on the load-time weaving -->
	<context:load-time-weaver/>
</beans>

이제 필요한 모든 아티팩트(aspect, META-INF/aop.xml파일, Spring 구성)가 준비되었으므로, 메인(..) 메서드를 사용하여 다음과 같은 드라이버 클래스를 생성하여 LTW를 실제로 시연할 수 있습니다:

package com.xyz;

// imports

public class Main {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

		EntitlementCalculationService service =
				ctx.getBean(EntitlementCalculationService.class);

		// the profiling aspect is 'woven' around this method execution
		service.calculateEntitlement();
	}
}

마지막으로 할 일이 하나 남았습니다. 이 섹션의 소개에서 Spring을 사용하여 ClassLoader별로 LTW를 선택적으로 켤 수 있다고 설명했으며 이는 사실입니다. 그러나 이 예제에서는 Spring과 함께 제공되는 Java 에이전트를 사용하여 LTW를 켭니다. 다음 명령을 사용하여 앞서 표시된 Main 클래스를 실행합니다:

java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main

-javaagent는에이전트가 JVM에서 실행되는 프로그램을 계측하도록 지정하고 활성화하기 위한 플래그입니다. Spring 프레임워크는 앞의 예제에서 -javaagent 인수의 값으로 제공된spring-instrument.jar에 패키지되어 있는 이러한 에이전트인 InstrumentationSavingAgent와 함께 제공됩니다.

Main 프로그램 실행의 출력은 다음 예제와 비슷합니다. (저는 계산자격() 구현에 Thread.sleep(..) 문을 도입하여 프로파일러가 실제로 0밀리초가 아닌 다른 것을 캡처하도록 했습니다( 01234밀리초는 AOP에서 도입한 오버헤드가 아닙니다). 다음 목록은 프로파일러를 실행했을 때 얻은 출력을 보여줍니다:

자격 계산 중 StopWatch 'ProfilingAspect': 실행 시간(밀리초) = 1234 ------ ----- ---------------------------- ms % 작업 이름 ------ ----- ---------------------------- 01234 100% calculateEntitlement

이 LTW는 완전한 AspectJ를 사용함으로써 영향을 받기 때문에, 스프링 빈을 조언하는 것에만 국한되지 않습니다. 메인 프로그램을 다음과 같이 약간 변형해도 동일한 결과를 얻을 수 있습니다:

package com.xyz;

// imports

public class Main {

	public static void main(String[] args) {
		new ClassPathXmlApplicationContext("beans.xml");

		EntitlementCalculationService service =
				new StubEntitlementCalculationService();

		// the profiling aspect will be 'woven' around this method execution
		service.calculateEntitlement();
	}
}

앞의 프로그램에서 Spring 컨테이너를 부트스트랩한 다음 Spring의 컨텍스트에서 완전히 벗어난 StubEntitlementCalculationService의 새 인스턴스를 생성하는 방법을 주목하세요. 프로파일링 조언이 여전히 적용됩니다.

물론 이 예제는 단순합니다. 그러나 Spring의 LTW 지원의 기본 사항은 앞선 예제에서 모두 소개했으며, 이 섹션의 나머지 부분에서는 각 구성 및 사용법의 '이유'에 대해 자세히 설명합니다.

  이 예제에서 사용된 ProfilingAspect는 기본적일 수 있지만 매우 유용합니다. 개발자가 개발 중에 사용한 다음 UAT 또는 프로덕션에 배포되는 애플리케이션의 빌드에서 쉽게 제외할 수 있는 개발 시간 측면의 좋은 예입니다

측면

LTW에서 사용하는 측면은 AspectJ 측면이어야 합니다. AspectJ 언어 자체로 작성하거나 @AspectJ 스타일로 작성할 수 있습니다. 그러면 여러분의 측면은 유효한 AspectJ 및 Spring AOP 측면이 됩니다. 또한 컴파일된 측면 클래스는 클래스 경로에서 사용할 수 있어야 합니다.

META-INF/aop.xml

AspectJ LTW 인프라는 Java 클래스 경로에 있는 하나 이상의 META-INF/aop.xml파일을 사용하여 구성됩니다(직접 또는 더 일반적으로 jar 파일에 있음):

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

	<weaver>
		<!-- only weave classes in our application-specific packages and sub-packages -->
		<include within="com.xyz..*"/>
	</weaver>

</aspectj>
  AspectJ 덤프 파일 및 경고와 같은 부작용을 피하기 위해 특정 클래스(일반적으로 애플리케이션 패키지에 있는 클래스, 위의 aop.xml 예시 참조)만 위빙하는 것이 좋습니다. 이는 효율성 측면에서도 모범 사례입니다

이 파일의 구조와 내용은AspectJ 참조 문서의 LTW 부분에 자세히 설명되어 있습니다. Aop.xml 파일은 100% AspectJ 파일이므로 여기서는 더 이상 설명하지 않습니다.

필수 라이브러리(JARS)

스프링 프레임워크의 AspectJ LTW 지원을 사용하려면 최소한 다음 라이브러리가 필요합니다:

  • spring-aop.jar
  • aspectjweaver.jar
  • spring-instrument.jar

Spring 구성

Spring의 LTW 지원의 핵심 구성 요소는 (org.springframework.instrument.classloading 패키지에 있는) LoadTimeWeaver 인터페이스와 Spring 배포와 함께 제공되는 수많은 구현입니다. LoadTimeWeaver는 런타임에 하나 이상의 java.lang.instrument.ClassFileTransformer를 ClassLoader에 추가하는 역할을 담당하며, 이는 모든 종류의 흥미로운 애플리케이션의 문을 열어주는데, 그 중 하나가 바로 LTW의 측면입니다.

  런타임 클래스 파일 변환에 대한 개념이 익숙하지 않은 경우 계속하기 전에 java.lang.instrument 패키지에 대한 javadoc API 설명서를 참조하세요. 이 설명서는 포괄적이지는 않지만 적어도 주요 인터페이스와 클래스를 확인할 수 있습니다(이 섹션을 읽으면서 참고하세요)

특정 ApplicationContext에 대한 LoadTimeWeaver를 구성하는 것은 한 줄을 추가하는 것만큼이나 간단할 수 있습니다. (거의 확실하게 Spring 컨테이너로ApplicationContext를 사용해야 합니다. 일반적으로 LTW 지원은 BeanFactoryPostProcessors를 사용하기 때문에 BeanFactory로는 충분하지 않습니다)

Spring 프레임워크의 LTW 지원을 활성화하려면 일반적으로 다음과 같이 @EnableLoadTimeWeaving 어노테이션을 사용하여 LoadTimeWeaver를 구성해야 합니다:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

또는 XML 기반 구성을 선호하는 경우<context:load-time-weaver/> 요소를 사용합니다. 이 요소는컨텍스트 네임스페이스에 정의된다는 점에 유의하세요. 다음 예는 <context:load-time-weaver/> 사용 방법을 보여줍니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:load-time-weaver/>

</beans>

앞의 구성은 LoadTimeWeaver  AspectJWeavingEnabler와 같은 여러 LTW 관련 인프라 빈을 자동으로 정의하고 등록합니다. 기본 LoadTimeWeaver는 자동으로 감지된 LoadTimeWeaver를 꾸미려고 시도하는 DefaultContextLoadTimeWeaver 클래스입니다. "자동으로 감지"되는 정확한 LoadTimeWeaver유형은 런타임 환경에 따라 달라집니다. 다음 표에는 다양한 LoadTimeWeaver 구현이 요약되어 있습니다:

표 1. 기본 컨텍스트 로드 타임 위버 로드 타임 위버런타임 환경LoadTimeWeaver 구현
Apache Tomcat에서 실행 TomcatLoadTimeWeaver
GlassFish에서 실행(EAR 배포로 제한됨) GlassFishLoadTimeWeaver
Red Hat의 JBoss AS 또는 WildFly에서 실행 중 JBossLoadTimeWeaver
Spring InstrumentationSavingAgent(java -javaagent:경로/to/spring-instrument.jar)로 시작된 JVM InstrumentationLoadTimeWeaver
폴백, 기본 클래스 로더가 일반적인 규칙(즉, addTransformer 및 선택적으로 getThrowawayClassLoader 메서드)을 따르기를 기대합니다 ReflectiveLoadTimeWeaver

이 표에는 DefaultContextLoadTimeWeaver를 사용할 때 자동 감지되는 LoadTimeWeaver만 나열되어 있습니다. 사용할 LoadTimeWeaver구현을 정확히 지정할 수 있습니다.

Java 구성으로 특정 LoadTimeWeaver를 지정하려면LoadTimeWeavingConfigurer 인터페이스를 구현하고 getLoadTimeWeaver() 메서드를 재정의합니다. 다음 예는 ReflectiveLoadTimeWeaver를 지정하는 예제입니다:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

	@Override
	public LoadTimeWeaver getLoadTimeWeaver() {
		return new ReflectiveLoadTimeWeaver();
	}
}

XML 기반 구성을 사용하는 경우, 정규화된 클래스 이름을 <context:load-time-weaver/>요소의 weaver-class 속성 값으로 지정할 수 있습니다. 다음 예시에서는 ReflectiveLoadTimeWeaver를 지정합니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:load-time-weaver
			weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

구성에 의해 정의되고 등록되는 LoadTimeWeaver는 나중에 잘 알려진 이름인 loadTimeWeaver를 사용하여 Spring 컨테이너에서 검색할 수 있습니다. LoadTimeWeaver는 Spring의 LTW 인프라가 하나 이상의 ClassFileTransformer를 추가하기 위한 메커니즘으로만 존재한다는 것을 기억하세요. LTW를 수행하는 실제클래스파일트랜스포머는 ( org.aspectj.weaver.loadtime 패키지의) ClassPreProcessorAgentAdapter 클래스입니다. 위빙이 실제로 어떻게 적용되는지에 대한 구체적인 내용은 이 문서의 범위를 벗어나므로 자세한 내용은ClassPreProcessorAgentAdapter 클래스의 클래스 수준 자바독을 참조하세요.

마지막으로 설명할 구성 속성이 하나 더 있는데, 바로 aspectjWeaving속성(또는 XML을 사용하는 경우 aspectj-weaving )입니다. 이 속성은 LTW의 활성화 여부를 제어합니다. 이 속성은 세 가지 가능한 값 중 하나를 사용할 수 있으며, 기본값은 속성이 없는 경우자동 감지입니다. 다음 표에는 세 가지 가능한 값이 요약되어 있습니다:

표 2. AspectJ 위빙 속성 값어노테이션 값XML 값설명
ENABLED 켜짐 AspectJ 위빙이 켜져 있으며 로드 시점에 적절하게 패싯이 위빙됩니다.
DISABLED 꺼짐 LTW가 꺼져 있습니다. 로드 시 패싯이 직조되지 않습니다.
자동 감지 autodetect Spring LTW 인프라가 하나 이상의 META-INF/aop.xml 파일을 찾을 수 있으면 AspectJ 위빙이 켜져 있습니다. 그렇지 않으면 꺼져 있습니다. 이것이 기본값입니다.

환경별 구성

이 마지막 섹션에는 애플리케이션 서버 및 웹 컨테이너와 같은 환경에서 Spring의 LTW 지원을 사용할 때 필요한 추가 설정 및 구성이 포함되어 있습니다.

Tomcat, JBoss, WildFly

Tomcat과 JBoss/WildFly는 로컬 계측이 가능한 일반 앱 ClassLoader를 제공합니다. Spring의 기본 LTW는 이러한 ClassLoader 구현을 활용하여 AspectJ 위빙을 제공할 수 있습니다. 앞서 설명한 대로 로드 시간 위빙을 활성화하기만 하면 됩니다. 특히,-javaagent:path/to/spring-instrument.jar를 추가하기 위해 JVM 시작 스크립트를 수정할 필요는 없습니다.

JBoss에서는 애플리케이션이 실제로 시작되기 전에 클래스를 로드하지 못하도록 앱 서버 검색을 비활성화해야 할 수도 있습니다. 빠른 해결 방법은 아티팩트에 다음 내용이 포함된 WEB-INF/jboss-scanning.xml이라는 파일을 추가하는 것입니다:

<scanning xmlns="urn:jboss:scanning:1.0"/>

일반 Java 애플리케이션

특정 LoadTimeWeaver 구현에서 지원되지 않는 환경에서 클래스 계측이 필요한 경우, JVM 에이전트가 일반적인 솔루션입니다. 이러한 경우 Spring은 일반적인 @EnableLoadTimeWeaving  <context:load-time-weaver/> 설정으로 자동 감지되는 Spring 전용(그러나 매우 일반적인) JVM 에이전트인 spring-instrument.jar를 필요로 하는 InstrumentationLoadTimeWeaver를 제공합니다.

이를 사용하려면 다음 JVM 옵션을 제공하여 Spring 에이전트로 가상 머신을 시작해야 합니다:

-javaagent:/path/to/spring-instrument.jar

이렇게 하려면 JVM 시작 스크립트를 수정해야 하므로 애플리케이션 서버 환경에서는 사용하지 못할 수도 있습니다(서버 및 운영 정책에 따라 다름). 하지만 독립형 Spring Boot 애플리케이션과 같은 JVM당 하나의 앱을 배포하는 경우에는 일반적으로 어떤 경우에도 전체 JVM 설정을 제어할 수 있습니다.

추가 리소스

AspectJ에 대한 자세한 정보는 AspectJ 웹사이트에서 확인할 수 있습니다.

애드리언 콜리어 외 저자의Eclipse AspectJ (애디슨-웨슬리, 2005)는 AspectJ 언어에 대한 포괄적인 소개와 참고 자료를 제공합니다.

Ramnivas Laddad(Manning, 2009)의AspectJ in Action, 2nd Edition을 적극 권장합니다. 이 책의 초점은 AspectJ에 맞춰져 있지만, 일반적인 AOP 테마도 많이 다루고 있습니다(어느 정도 깊이 있게).

 

반응형
profile

while(1) work();

@유호건

❤️댓글은 언제나 힘이 됩니다❤️ 궁금한 점이나 잘못된 내용이 있다면 댓글로 남겨주세요.

검색 태그