1. Aspect Oriented Programming
- 관점 지향 프로그래밍(AOP)은 객체 지향 프로그래밍(OOP)을 보완하는 프로그램 구조에 대한 또 다른 사고 방식을 제공합니다.
- OOP에서의 핵심 모듈화 단위는 클래스인 반면, AOP에서는 관점(Aspect)이 모듈화 단위입니다.
- 관점은 여러 타입과 객체에 걸쳐 있는 관심사(예: 트랜잭션 관리)의 모듈화를 가능하게 합니다.
- 이러한 관심사는 AOP 문헌에서 종종 "횡단 관심사(crosscutting concerns)"라고 불립니다.
- Spring의 핵심 구성 요소 중 하나는 AOP 프레임워크입니다.
- Spring IoC 컨테이너는 AOP에 의존하지 않지만(즉, 원하지 않으면 AOP를 사용할 필요가 없음), AOP는 Spring IoC를 보완하여 매우 강력한 미들웨어 솔루션을 제공합니다.
1.1 횡단 관심사(Crosscutting Concerns)
- 횡단 관심사는 여러 모듈에서 발생하는 관심사입니다.
- 예를 들어서 로그 추적 로직, 트랜잭션 기능이 있습니다.
- 이러한 부가 기능은 단독으로 사용되지 않고, 핵심 기능과 함께 사용됩니다.
- 예를 들어서 로그 추적 기능은 어떤 핵심 기능이 호출되었는지 로그를 남기기 위해 사용됩니다.
- 부가 기능은 이름 그대로 핵심 기능을 보조하기 위해 존재합니다.
- 보통 부가 기능은 여러 클래스에 걸쳐서 함께 사용됩니다.
- 예를 들어서 모든 애플리케이션 호출을 로깅 해야 하는 요구사항을 생각해봅시다.
- 이러한 부가 기능은 횡단 관심사(cross-cutting concerns)가 됩니다.
- 쉽게 이야기해서 하나의 부가 기능이 여러 곳에 동일하게 사용된다는 뜻입니다.
- 그런데 이런 부가 기능을 여러 곳에 적용하려면 너무 번거롭습니다.
- 예를 들어서 부가 기능을 적용해야 하는 클래스가 100개면 100개 모두에 동일한 코드를 추가해야 합니다.
- 부가 기능을 별도의 유틸리티 클래스로 만든다고 해도, 해당 유틸리티 클래스를 호출하는 코드가 결국 필요합니다.
- 만약 부가 기능에 수정이 발생하면, 100개의 클래스 모두를 하나씩 찾아가면서 수정해야 합니다.
- 부가 기능처럼 특정 로직을 애플리케이션 전반에 적용하는 문제는 일반적인 OOP 방식으로는 해결이 어렵습니다.
- 이러한 문제를 해결하기 위해 등장한 것이 AOP입니다.
2. AOP Concepts
- AOP 개념과 용어를 정의하는 것으로 시작하겠습니다. 이 용어들은 Spring에만 국한된 것이 아닙니다.
- 불행하게도, AOP 용어는 특별히 직관적이지 않습니다. 그러나 Spring이 자체 용어를 사용한다면 더욱 혼란스러울 것입니다.
- 따라서 자체 용어가 아닌 일반적인 용어를 사용하여 AOP를 설명하겠습니다.
2.1 Aspect
- 여러 클래스에 걸쳐 있는 관심사의 모듈화입니다.
- 트랜잭션 관리는 엔터프라이즈 Java 애플리케이션에서 횡단 관심사의 좋은 예입니다.
- Spring AOP에서 관점은 일반 클래스(스키마 기반 접근 방식) 또는 @Aspect 어노테이션이 달린 일반 클래스(@AspectJ 스타일)를 사용하여 구현 됩니다.
- 스프링 AOP를 사용할 때는
@Aspect
애노테이션을 주로 사용하는데, 이 애노테이션도 AspectJ가 제공하는 애노테이션입니다. - 스프링에서는 AspectJ가 제공하는 애노테이션이나 관련 인터페이스만 사용하는 것이고, 실제 AspectJ가 제공하는 컴파일, 로드타임 위버 등을 사용하는 것은 아닙니다.
- 스프링 AOP를 사용할 때는
2.2 Advisor
- 해당 용어는 스프링 AOP에서만 사용되는 특별한 용어입니다.
- Pointcut과 Advice의 결합입니다.
- 즉 어드바이스가 언제 실행되어야 하는지를 결정하는 포인트컷과 횡단 관심사에 대한 구체적인 동작을 나타내는 어드바이스를 결합합니다.
2.3 Joinpoint
- 어드바이스가 적용될 수 있는 위치를 의미합니다.
- 메소드 실행, 생성자 호출, 필드 값 접근, static 메서드 접근 같은 프로그램 실행 중 지점입니다.
- 조인 포인트는 추상적인 개념입니다. AOP를 적용할 수 있는 모든 지점이라 생각하면 됩니다.
- Spring AOP에서 조인 포인트는 항상 메서드 실행을 나타냅니다.
- 즉 Spring AOP에서는 조인 포인트는 메서드와 같다고 생각할 수 있습니다.
- 일반적으로는 생성자, 필드 값 접근, static 메서드 접근, 메서드 실행을 포함합니다.
- AspectJ를 사용해서 컴파일 시점과 클래스 로딩 시점에 적용하 는 AOP는 바이트코드를 실제 조작하기 때문에 해당 기능을 모든 지점에 다 적용할 수 있습니다.
2.4 Advice
- 특정 조인 포인트에서 관점이 취하는 행동입니다. 쉽게 말해 부가 로직입니다.
- 예를들어, 트랜잭션 관리, 로깅, 보안, 캐싱 등이 있습니다.
- 어드바이스의 다양한 유형에는 "around", "before", "after" 어드바이스가 있습니다.
- Spring을 포함한 많은 AOP 프레임워크는 어드바이스를 인터셉터로 모델링하고 조인 포인트 주변에 인터셉터 체인을 유지합니다.
2.5 Pointcut
- 조인 포인트와 일치하는 조건식입니다.
- 조인 포인트 중에서 어드바이스가 적용될 위치를 선별하는 기능을 합니다.
- 즉 Pointcut은 Advice가 적용될 Joinpoint를 결정합니다.
- 어드바이스는 포인트컷 표현식과 연결되어 해당 포인트컷과 일치하는 모든 조인 포인트에서 실행됩니다(예: 특정 이름을 가진 메서드의 실행).
- 포인트컷 표현식과 일치하는 조인 포인트의 개념은 AOP의 핵심이며, Spring은 기본적으로 AspectJ 포인트컷 표현식 언어를 사용합니다.
2.6 Introduction
- 타입을 대신하여 추가 메서드나 필드를 선언하는 것입니다.
- Spring AOP를 사용하면 어떤 어드바이스된 객체에도 새로운 인터페이스(및 해당 구현)를 도입할 수 있습니다.
- 예를 들어, 캐싱을 단순화하기 위해 인트로덕션을 사용하여 빈이 IsModified 인터페이스를 구현하도록 할 수 있습니다. (인트로덕션은 AspectJ 커뮤니티에서 inter-type 선언으로 알려져 있습니다.)
2.7 Target object
- 하나 이상의 관점에 의해 어드바이스되는 객체입니다.
- "어드바이스된 객체"라고도 합니다. Spring AOP는 런타임 프록시를 사용하여 구현되므로 이 객체는 항상 프록시된 객체입니다.
- 프록시에 대해서 더 알고 싶다면 아래 글을 읽어보세요.
2.8 AOP proxy
- 관점 계약(어드바이스 메서드 실행 등)을 구현하기 위해 AOP 프레임워크에 의해 생성된 객체입니다.
- Spring 프레임워크에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시입니다.
- JDK 동적 프록시와 CGLIB 프록시에 대해 더 알고 싶다면 아래 글을 읽어보세요.
- Proxy 참고
2.9 Weaving
- 관점을 다른 애플리케이션 타입이나 객체와 연결하여 어드바이스된 객체를 만드는 과정입니다.
- 이는 컴파일 시간(예: AspectJ 컴파일러 사용), 클래스 로딩 시점, 런타임에 수행될 수 있습니다.
- 컴파일 시점
- AspectJ 컴파일러를 사용하여
.java
소스 코드를.class
파일로 컴파일할 때 부가 기능 로직을 추가할 수 있습니다. - 이해하기 쉽게 풀어서 이야기하면 부가 기능 코드가 핵심 기능이 있는 컴파일된 코드 주변에 실제로 붙어 버린다고 생각하면 됩니다.
- 컴파일 시점에 부가 기능을 적용하려면 특별한 컴파일러도 필요하고 복잡한 설정도 필요합니다.
- AspectJ 컴파일러를 사용하여
- 클래스 로딩 시점
- 클래스 로더를 사용하여 클래스를 로딩할 때 부가 기능을 적용합니다.
.class
를 JVM에 저장하기 전에 조작할 수 있는 기능을 사용합니다.(java Instrumentation)- 참고로 수 많은 모니터링 툴들이 이 방식을 사용합니다.
- 이 시점에 애스펙트를 적용하는 것을 로드 타임 위빙이라 합니다.
- 로드 타임 위빙은 자바를 실행할 때 특별한 옵션(
java -javaagent
)을 통해 클래스 로더 조작기를 지정해야 합니다.
- 런타임 시점
- 런타임에 부가 기능을 적용합니다.
- Spring AOP는 다른 순수 Java AOP 프레임워크와 마찬가지로 런타임에 위빙을 수행합니다.
- 런타임에 부가 기능을 적용하려면 프록시 객체를 만들어야 합니다.
- 해당 과정에 대해 알고싶다면 아래 글을 읽어보세요.
- BeanPostProcessor 참고
3 AOP Advice
- Spring AOP는 다음과 같은 유형의 어드바이스를 제공합니다.
- Before 어드바이스: 조인 포인트 전에 실행되지만 조인 포인트로의 실행 흐름을 방지할 수 없는 어드바이스입니다(예외를 던지지 않는 한).
- After returning 어드바이스: 조인 포인트가 정상적으로 완료된 후 실행될 어드바이스입니다(예: 메서드가 예외를 던지지 않고 반환하는 경우).
- After throwing 어드바이스: 메서드가 예외를 던져 종료되는 경우 실행될 어드바이스입니다.
- After (finally) 어드바이스: 조인 포인트가 종료되는 방식(정상 또는 예외적 반환)에 관계없이 실행될 어드바이스입니다.
- Around 어드바이스
- 메서드 호출과 같은 조인 포인트를 둘러싸는 어드바이스입니다.
- 이는 가장 강력한 종류의 어드바이스입니다.
- Around 어드바이스는 메서드 호출 전후에 사용자 정의 동작을 수행할 수 있습니다.
- 또한 조인 포인트로 진행할지 또는 자체 반환 값을 반환하거나 예외를 던져 어드바이스된 메서드 실행을 우회할지 선택할 책임이 있습니다.
3.1 Before Advice
- @Before 어노테이션을 사용하여 애스펙트에서 before 어드바이스를 선언할 수 있습니다.
@Around
와 다르게 작업 흐름을 변경할 수는 없습니다.- @Before는 메서드 종료 시 자동으로 target이 호출됩니다.
- 예외가 발생하면 다음 코드가 호출되지는 않습니다.
3.2 After Returning Advice
- After returning 어드바이스는 일치하는 메서드 실행이 정상적으로 반환될 때 실행됩니다.
- @AfterReturning 어노테이션을 사용하여 선언할 수 있습니다.
@Around
와 다르게 반환되는 객체를 변경할 수는 없습니다.- 반환 객체를 변경하려면
@Around
를 사용해야 합니다. - 참고로 반환 객체를 조작할 수 는 있습니다.
- 반환 객체를 변경하려면
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) {
// ...
}
}
- 어드바이스 본문에서 실제로 반환된 값에 접근해야 할 필요가 있습니다.
- returning 속성에 사용된 이름은 어드바이스 메서드의 파라미터 이름과 일치해야 합니다.
- 서드 실행이 반환될 때, 반환 값은 해당 인자 값으로 어드바이스 메서드에 전달됩니다.
- returning 절은 또한 지정된 타입의 값을 반환하는 메서드 실행만 일치하도록 제한합니다