legacy/Spring

[Spring] Spring AOP(Aspect-Oriented Programming)

heemang.dev 2024. 9. 28. 17:54

1. 스프링 AOP란?

AOP는 관점 지향 프로그래밍(Aspect-Oriented Programming)이라고 부르며, 프로그램의 핵심 기능 외에 부가적인 기능을 분리해서 모듈화 하는 기법이다. 핵심 비즈니스 로직과는 직접적인 관계가 없는 로직들(ex. 로깅, 트랜잭션)이 코드의 여러 부분에서 반복될 수 있다. AOP는 비즈니스 로직과 상관없는 반복적인 코드들을 별도의 모듈로 관리하여 코드의 유지 보수를 쉽게 하고 핵심 비즈니스 로직에 집중할 수 있도록 한다.

ex) @Transactional은 트랜잭션 처리라는 부가적인 기능을 애너테이션으로 적용하는 방식이다. 원래는 트랜잭션을 사용하려면 transaction.begin(), transaction.commit(), transaction.rollback() 같은 코드를 비즈니스 로직마다 작성해야 하지만, AOP를 사용하면 트랜잭션 처리를 애너테이션으로 간단하게 적용할 수 있다.

 

핵심 관심사와 횡단 관심사

@Transactional은 비즈니스 로직의 앞뒤에 트랜잭션을 자동으로 설정한다. 그래서 개발자는 비즈니스 로직에만 집중하고 트랜잭션과 관련된 코드를 신경 쓸 필요가 없다. 즉, AOP는 횡단 관심사(Cross-Cutting Concerns)를 비즈니스 로직에서 분리하여 관리할 수 있다.

횡단 관심사란 핵심 비즈니스 로직이 아닌, 여러 곳에서 공통적으로 필요한 부가 기능을 의미한다. 우리는 트랜잭션을 시작하고 종료하기 위해 @Transactional을 사용할 수 있다.

  • 핵심 관심사(Core Concerns) : 비즈니스 로직에 직접적으로 관련된 부분
    • ex) 입금, 출금, 이체와 같은 실제 업무 로직
  • 횡단 관심사(Cross-Cutting Concerns) : 비즈니스 로직과는 독립적인 기능
    • ex) 로깅, 보안, 트랜잭션과 같은 코드가 각 비즈니스 로직에 걸쳐 반복적으로 사용된다.

 

트랜잭션이라는 관심사는 @Transactional 덕분에 비즈니스 로직 안에 직접 넣을 필요 없이 자동으로 처리된다. 그러나 관심사가 분리되어 있지 않다면 DB에 접근할 때마다 begin(), commit(), rollback()과 같은 트랜잭션 코드를 반복하여 직접 작성해야 한다.

  • AOP가 적용된 코드
@Transactional
public void save() {
	memberRepository.save(member);
}

@Transactional
public void update() {
	member.updateUsername(username);
}
  • AOP가 적용되지 않은 코드
public void save() {
	transaction.begin();	
	memberRepository.save(member);
	transaction.commit();
}

public void update() {
	transaction.begin();	
	member.updateUsername(username);
	transaction.commit();
}

 

2. Spring AOP 동작 방식

Spring AOP는 프록시(Proxy) 기반으로 동작하며 메서드 호출 전후에 부가 기능을 적용한다. 프록시 기반 AOP는 런타임 시점에 동작하며, 메서드 레벨에서만 AOP를 적용한다.

2-1. 프록시(Proxy)란?

프록시는 실제 객체를 대신하여 메서드 호출을 처리하는 대리 객체이다. Spring AOP에서 프록시는 비즈니스 로직의 실행 전후로 부가적인 기능을 추가할 수 있도록 한다.

예를 들어, @Transactional이 붙어있는 메서드가 호출될 때, 프록시 객체는 메서드를 호출하기 전에 트랜잭션을 시작하고, 메서드 실행 후에 트랜잭션을 커밋하거나 롤백하는 역할을 한다. 프록시는 비즈니스 로직을 실행하기 전후로 부가 기능을 실행하는 방식으로 동작한다.

2-2. 프록시 생성 방식

Spring AOP에서는 JDK 동적 프록시 또는 CGLIB(Code Generator Library) 프록시를 사용하여 프록시 객체를 생성한다.

  • JDK 동적 프록시 : 인터페이스를 구현한 객체에 대해 프록시 객체를 생성한다. 인터페이스를 구현한 클래스에서만 동작한다.
  • CGLIB 프록시 : 클래스 상속을 통해 프록시 객체를 생성하며, 주로 인터페이스가 없는 클래스에 사용된다.

2-3. @Transactional에서 프록시의 동작 흐름

  1. 클라이언트가 @Transactional이 적용된 메서드를 호출한다.
  2. 프록시 객체가 호출을 가로챈다. 이때, AOP의 부가 기능이 필요하다면 프록시에서 처리한다.
  3. 프록시는 메서드 실행 전에 트랜잭션을 실행한다. 트랜잭션이 시작된 상태에서 프록시 객체가 Target 객체의 메서드를 실제로 호출한다.
  4. 메서드의 실행 결과에 따라 트랜잭션을 커밋하거나 롤백한다.

프록시 객체 덕분에 개발자는 트랜잭션 관리에 신경 쓰지 않고 비즈니스 로직만 작성하면 된다.

 

3. 스프링 AOP의 핵심 개념

3-1. Aspect

Aspect는 AOP에서 횡단 관심사(Cross-Cutting Concern)를 모듈화 한 것을 의미한다. 트랜잭션 관리(시작, 종료), 로깅 등 비즈니스 로직과는 별개로 여러 곳에서 반복하여 사용될 수 있는 부가적인 기능에 해당한다. Aspect는 Advice + PointCut으로 구성되며, 무슨 메서드(PointCut)에 언제(Advice) 부가 기능(Asepct)을 적용할지 정의한다.

ex) @Trasnactional은 트랜잭션 관리를 위한 Aspect로, 트랜잭션의 시작, 커밋, 롤백 등의 부가 기능을 제공한다. 트랜잭션은 비즈니스 로직의 전후로 적용된다.

3-2. JoinPoint

JointPoint는 부가 기능(Aspect)이 적용될 수 있는 특정 실행 지점을 의미한다. 스프링 AOP에서는 주로 메서드 실행 지점을 JoinPoint로 취급한다. 따라서 메서드 호출 시 그 지점에서 부가 기능이 실행될 수 있다. Spring AOP는 메서드 호출만 JointPoint로 지원한다.

ex) @Transactional이 적용되는 JointPoint는 메서드 실행 시점이다. 트랜잭션이 메서드 호출 전에 시작되고, 메서드 실행이 끝나면 트랜잭션이 커밋 또는 롤백된다.

3-3. Advice

Advice는 부가 기능(Aspect)이 언제 실행될지를 정의한다. JointPoint에서 부가 기능이 적용될 수 있는 지점을 정의하고, Advice에서 부가 기능을 언제 실행할지를 결정한다.

  • Before Advice : 메서드 실행 전에 실행된다.
  • After Advice : 메서드 실행 후에 실행된다. (비즈니스 로직 성공 또는 예외 여부 상관없이 실행됨)
  • AfterReturning Advice : 메서드가 성공적으로 리턴된 후 실행된다.
  • AfterThrowing Advice : 메서드 실행 중 예외가 발생했을 때 실행된다.
  • Around Advice : 메서드 실행 전후에 모두 실행되며, 메서드 자체를 제어할 수 있다.

ex) @Transactional은 Around Advice 방식으로 동작한다. 트랜잭션의 시작과 종료를 메서드 실행 전후로 제어할 수 있다.

3-4. Pointcut

PointCut은 JointPoint 중에서 어떤 지점에 부가 기능(Aspect)을 적용할지 결정하는 필터 역할을 한다. AspectJ 표현식을 사용하여 PointCut를 정의한다.

ex) execution(* com.example.service.*.*(..))는 특정 패키지 내의 모든 메서드에 Aspect를 적용하는 PointCut이다. 여기서 *는 모든 메서드를 의미하며, (..)는 매개변수의 타입과 개수에 관계없이 메서드를 선택하는 표현이다.

3-5. Weaving

Weaving은 부가 기능(Aspect)핵심 비즈니스 로직에 적용하는 과정이다. 런타임, 컴파일, 클래스 로딩 시점 등 다양한 시점에서 Weaving이 가능하다. 스프링 AOP는 Proxy 기반으로 동작하므로 런타임에 Proxy 객체를 생성하여 Weaving이 이루어진다.

ex) @Transcational은 Weaving을 통해 트랜잭션 관리를 제공한다. 메서드 호출 시 스프링 AOP Proxy 객체가 런타임 시점에 생성되고, 이 객체가 비즈니스 로직 실행 전후에 트랜잭션을 시작하고 종료한다.

3-6. 결론

  1. @Transactional은 트랜잭션 관리라는 Aspect를 제공한다.
  2. 메서드가 호출되는 지점에서 트랜잭션을 관리하는 JoinPoint가 발생한다.
  3. Advice는 메서드 실행 전후에 트랜잭션을 시작하고, 성공 시 커밋, 예외 시 롤백을 담당한다.
  4. 트랜잭션이 적용될 메서드를 Pointcut으로 지정한다.
  5. Weaving 과정을 통해 트랜잭션 관리가 실행된다.