[번역] Spring 공식문서 - Aspect Oriented Programming

공혁준·2022년 3월 15일
0
post-thumbnail
post-custom-banner

Ref. Spring Framework Documentation v.5.3.16
📌 이 글에서는 공식문서의 Aspect Oriented Programming 부분만 다룹니다.

Aspect Oriented Programming with Spring

  • AOP(Aspect-Oriented Programming)는 프로그램 구조에 대한 또 다른 사고 방식을 제공하여 객체 지향 프로그래밍(OOP)을 보완합니다.
  • OOP에서 모듈화의 핵심 단위는 클래스인 반면, AOP에서 모듈화의 단위는 관점입니다.
  • Spring의 핵심 구성요소 중 하나는 AOP 프레임워크입니다.
  • Spring IoC 컨테이너가 AOP에 의존하지 않는 반면(원하지 않으면 AOP를 사용할 필요가 없다는 의미), AOP는 Spring IoC를 보완하여 매우 유능한 미들웨어 솔루션을 제공합니다.

AOP Concepts

몇 가지 AOP 핵심 개념과 용어를 정의하는 것으로 시작합니다. 이 용어는 Spring에만 국한되지 않습니다.

  • Aspect: 여러 클래스에 흩어진 관심사의 모듈화. 트랜잭션 관리는 엔터프라이즈 Java 애플리케이션에서 크로스 커팅 문제의 좋은 예입니다. Spring AOP에서 Aspect는 일반 클래스(스키마 기반 접근) 또는 @Aspect 어노테이션으로 주석이 달린 일반 클래스(@AspectJ 스타일)를 사용하여 구현됩니다.
  • Join point: 메소드 실행이나 예외 처리와 같은 프로그램 실행 중 포인트. Spring AOP에서 조인 포인트는 항상 메소드 실행을 나타냅니다.
  • Advice: 특정 조인 포인트에서 aspect에 의해 취해진 조치입니다. 다양한 유형의 advice에는 "around", "before" 및 "after" advice가 포함됩니다. Spring을 포함한 많은 AOP 프레임워크는 advice를 인터셉터로 모델링하고 조인 포인트 주변에 인터셉터 체인을 유지합니다.
  • Pointcut: 조인 포인트와 일치하는 술어입니다. Advice는 pointcut 표현식과 연관되며 pointcut과 일치하는 모든 조인 포인트에서 실행됩니다(예: 특정 이름의 메소드 실행). pointcut 표현식에 의해 매칭되는 조인 포인트의 개념은 AOP의 핵심이고 Spring은 기본적으로 AspectJ pointcut 표현식 언어를 사용합니다.
  • Introduction: 타입을 대신하여 추가적인 메소드나 필드를 선언합니다. Spring AOP를 사용하면 advice된 객체에 새로운 인터페이스(및 해당 구현)를 도입할 수 있습니다.
  • Target object: 하나 이상의 aspect에 의해 advice되는 객체입니다. "advised object"라고도 합니다. Spring AOP는 런타임 프록시를 사용하여 구현되므로 이 객체는 항상 프록시 객체입니다.
  • AOP proxy: aspect contracts(메소드 실행에 advice 등)를 구현하기 위해 AOP 프레임워크에 의해 생성된 객체입니다. Spring Framework에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시입니다.
  • Weaving: aspect를 다른 애플리케이션 유형 또는 객체와 연결하여 advice된 객체를 생성합니다. 이것은 컴파일 시간, 로드 시간 또는 런타임에 수행할 수 있습니다. Spring AOP는 다른 순수 Java AOP 프레임워크와 마찬가지로 런타임에 weaving을 수행합니다.

Spring AOP에는 다음과 같은 advice가 포함됩니다.

  • Before advice: 조인 포인트 이전에 실행되지만 조인 포인트로 진행하는 실행 흐름을 방지하는 기능이 없는 advice입니다(예외가 발생하지 않는 한).
  • After returning advice: 조인 포인트가 정상적으로 완료된 후 실행할 advice입니다(예: 메서드가 예외를 throw 하지 않고 return 되는 경우).
  • After throwing advice: 예외를 throw하여 메서드가 종료되는 경우 실행할 advice입니다.
  • After (finally) advice: 조인 포인트가 종료되는 수단(정상 또는 예외 반환)에 관계없이 실행할 advice입니다.
  • Around advice: 메소드 호출과 같은 조인 포인트를 둘러싸는 advice입니다. 이것은 가장 강력한 종류의 advice입니다. around advice는 메서드 호출 전후에 사용자 정의 동작을 수행할 수 있습니다. 또한 자체 return 값을 반환하거나 예외를 throw하여 조인 지점으로 진행할지 또는 권장된 메서드 실행을 단축할지 여부를 선택할 책임이 있습니다.

Spring AOP Capabilities and Goals

  • Spring AOP는 순수 자바로 구현되었기 때문에 특별한 컴파일 과정이 필요하지 않습니다.
  • Spring AOP는 클래스 로더 계층 구조를 제어할 필요가 없으므로 서블릿 컨테이너 또는 애플리케이션 서버에서 사용하기에 적합합니다.
  • Spring AOP는 현재 메소드 실행 조인 포인트만 지원합니다(Spring Bean에서 메소드 실행 권장).
  • 필드 액세스를 advice하고 조인 포인트를 업데이트해야 하는 경우 AspectJ와 같은 언어를 고려해야 합니다.
  • AOP에 대한 Spring AOP의 접근 방식은 대부분의 다른 AOP 프레임워크의 접근 방식과 다릅니다.
  • 목표는 가장 완전한 AOP 구현을 제공하는 것이 아니고 AOP 구현과 Spring IoC 간의 긴밀한 통합을 제공하여 엔터프라이즈 애플리케이션의 일반적인 문제를 해결하는 데 도움이 되는 것입니다.

Spring Framework의 핵심 신조 중 하나는 비침투성입니다. 이것은 프레임워크 특정 클래스와 인터페이스를 비즈니스 또는 도메인 모델에 강제로 도입해서는 안 된다는 생각입니다. 그러나 어떤 곳에서는 Spring Framework가 코드베이스에 Spring Framework 관련 종속성을 도입하는 옵션을 제공합니다. 이러한 옵션을 제공하는 근거는 특정 시나리오에서 이러한 방식으로 특정 기능 부분을 읽거나 코딩하는 것이 훨씬 더 쉬울 수 있기 때문입니다. 그러나 Spring Framework(거의)는 항상 선택을 제공합니다. 특정 사용 사례 또는 시나리오에 가장 적합한 옵션에 대해 정보에 입각한 결정을 내릴 수 있는 자유가 있습니다.

이 장과 관련된 그러한 선택 중 하나는 AOP 프레임워크(및 AOP 스타일)를 선택하는 것입니다. AspectJ, Spring AOP 를 모두 선택할 수 있습니다. @AspectJ 주석 스타일 접근 방식 또는 Spring XML 구성 스타일 접근 방식 중 하나를 선택할 수도 있습니다. 이 장에서 @AspectJ 스타일 접근 방식을 먼저 도입하기로 선택했다는 사실은 Spring 팀이 Spring XML 구성 스타일보다 @AspectJ 주석 스타일 접근 방식을 선호한다는 표시로 간주되어서는 안 됩니다.

각 스타일의 "whys and wherefores"에 대한 자세한 내용은 사용할 AOP 선언 스타일 선택하기를 참조하세요.

@AspectJ support

  • @AspectJ는 어노테이션이 있는 일반 Java 클래스로 관점을 선언하는 스타일을 나타냅니다.
  • @AspectJ 스타일은 AspectJ 5 릴리스의 일부로 AspectJ 프로젝트에 의해 도입되었습니다.
  • Spring은 pointcut 구문 분석 및 일치를 위해 AspectJ에서 제공하는 라이브러리를 사용하여 AspectJ 5와 동일한 주석을 해석합니다.
  • AOP 런타임은 여전히 순수한 Spring AOP이고 AspectJ 컴파일러 또는 위버에 대한 종속성이 없습니다.

Enabling @AspectJ Support

  • Spring 설정에서 @AspectJ aspect를 사용하기 위해서는 @AspectJ aspect에 기반한 Spring AOP 설정과 이러한 aspect에 의해 조언되는 자동 프록시 빈에 대한 Spring 지원을 활성화해야 한다.
  • @AspectJ 지원은 XML 또는 Java 스타일 구성으로 활성화할 수 있습니다.

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/>

Declaring an Aspect

@AspectJ 지원이 활성화되면 @AspectJ 관점(@Aspect 어노테이션이 있음)이 있는 클래스를 사용하여 애플리케이션 컨텍스트에 정의된 모든 빈이 Spring에서 자동으로 감지되고 Spring AOP를 구성하는 데 사용됩니다.

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

Declaring a Pointcut

  • 포인트컷은 관심 조인 포인트를 결정하므로 어드바이스가 실행되는 시기를 제어할 수 있습니다.
  • Spring AOP는 Spring Bean에 대한 메소드 실행 조인 포인트만 지원하므로 Pointcut은 Spring Bean의 메소드 실행과 일치하는 것으로 생각할 수 있습니다.
  • pointcut 선언은 이름과 매개변수를 포함하는 서명과 우리가 관심 있는 메소드 실행을 정확히 결정하는 pointcut 표현식의 두 부분으로 구성됩니다.
  • pointcut 표현식은 @Pointcut 어노테이션을 사용하여 표시됩니다.

아래 예는 pointcut 서명과 pointcut 표현식 사이의 이러한 구별을 명확하게 하는 데 도움이 될 수 있습니다. 다음 예제는 transfer라는 메서드의 실행과 일치하는 anyOldTransfer라는 포인트컷을 정의합니다.

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

Declaring Advice

어드바이스는 포인트컷 표현식과 연관되며 포인트컷과 일치하는 메서드 실행 전후 또는 전후에 실행됩니다. pointcut 표현식은 명명된 pointcut에 대한 단순 참조이거나 제자리에 선언된 pointcut 표현식일 수 있습니다.

Before Advice

@Before 어노테이션을 사용하여 aspect에서 before advice를 선언할 수 있습니다.

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

@Aspect
public class BeforeExample {

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

After Returning Advice

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

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

@Aspect
public class AfterReturningExample {

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

After Throwing Advice

After throwing Advice는 일치하는 메서드 실행이 예외를 throw하여 종료될 때 실행됩니다. @AfterThrowing 어노테이션을 사용하여 선언할 수 있습니다.

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

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
}

After (Finally) Advice

일치하는 메서드 실행이 종료되면 after(최종) advice가 실행됩니다. @After 어노테이션을 사용하여 선언합니다. 애프터 어드바이스는 정상 및 예외 반환 조건을 모두 처리할 수 있도록 준비해야 합니다. 일반적으로 리소스 및 유사한 목적을 해제하는 데 사용됩니다.

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

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }
}

Around Advice

around advice는 일치하는 메서드가 실행되는 동안 실행합니다. 메서드 실행 전후에 작업을 수행하고 메서드가 실제로 실행되는 시기와 방법, 실행 여부를 결정할 수 있는 기회가 있습니다. 어라운드 어드바이스는 스레드로부터 안전한 방식(예: 타이머 시작 및 중지)으로 메서드 실행 전후의 상태를 공유해야 하는 경우에 자주 사용됩니다.

항상 요구사항을 충족하는 가장 강력한 형태의 advice를 사용하십시오.
예를 들어, before advice가 당신의 요구에 충분하다면 around advice를 사용하지 마십시오.

어라운드 어드바이스는 @Around 어노테이션으로 메소드에 어노테이션을 작성하여 선언됩니다. 메서드는 반환 형식으로 Object를 선언해야 하며 메서드의 첫 번째 매개 변수는 ProceedingJoinPoint 형식이어야 합니다. 어드바이스 메서드의 본문 내에서 기본 메서드를 실행하려면 ProceedingJoinPoint에 대해 proceed()를 호출해야 합니다. 인수 없이 proceed()를 호출하면 호출자의 원래 인수가 호출될 때 기본 메서드에 제공됩니다. 고급 사용 사례의 경우 인수 배열(Object[])을 허용하는 proceed() 메서드의 오버로드된 변형이 있습니다. 배열의 값은 호출될 때 기본 메서드에 대한 인수로 사용됩니다.

around advice에 의해 반환된 값은 메서드 호출자가 본 반환 값입니다. 예를 들어, 간단한 캐싱 aspect는 캐시에 값이 있는 경우 캐시에서 값을 반환하거나 없는 경우 proceed()을 호출하고 해당 값을 반환할 수 있습니다. proceed는 around advice의 본문 내에서 한 번, 여러 번 호출되거나 전혀 호출되지 않을 수 있습니다. 이 모든 것이 합법입니다.

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

@Aspect
public class AroundExample {

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

Introductions

Introductions(AspectJ에서 유형 간 선언으로 알려짐)를 통해 Aspect는 조언된 객체가 주어진 인터페이스를 구현한다고 선언하고 해당 객체를 대신하여 해당 인터페이스의 구현을 제공할 수 있습니다.

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

@Aspect
public class UsageTracking {

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

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}
profile
몰입을 즐기는 개발자입니다.
post-custom-banner

0개의 댓글