AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)

2025. 11. 10. 23:41·Development/개발 공부

AOP란?

AOP를 알기 위해서는 먼저 횡단 관심사에 대해 알아야 한다.

횡단 관심사란 "애플리케이션의 여러 모듈(클래스·메소드)에 걸쳐 나타나는 부가적인 기능"을 의미한다.

가령 로깅, 보안, 트랜잭션 관리, 성능 모니터링 등등.. 처럼 말이다.

 

기존의 OOP 방식은 횡단 관심사 코드가 여러 비즈니스 로직에 반복적으로 삽입되었다.

메소드 내에 지속적으로 로그(log) 같은 것을 삽입하게 되면, 코드의 중복이 발생하며 가독성이 떨어진다.

 

AOP는 이런 횡단 관심사를 모듈로 분리하기 위해 등장하였다.

즉, 애플리케이션의 핵심 비즈니스 로직과 여러 모듈에 걸쳐 공통적으로 사용되는 부가 기능(횡단 관심사)을 분리화하여 모듈화하는 프로그래밍 방식이다.

 

AOP의 장점

1. 모듈성 향상: 비즈니스 로직만 존재하기 때문에 로직 코드가 깔끔해지며, 부가 기능은 하나의 Aspect에서 관리한다.

2. 코드 재사용성 증가: 분리된 aspect를 필요한 곳에 쉽게 적용할 수 있다.

3. 유지보수 용이: 기능 수정이 필요할 때, 비즈니스 로직을 건드리지 않고 aspect만 따로 수정하면 된다.

 


 

AOP의 구성 요소

AOP의 주요 구성 요소는 Aspect, Advice, JointPoint, PointCut, Target Object가 있다!

개념을 알아본 뒤, 예제를 통해 더 파악하도록 해보자:D

  특징
Aspect - 공통 부가 기능(횡단 관심사)을 모듈화한 단위
- 보통 @Aspect 어노테이션이 붙은 자바 클래스로 구현됨
- Advice + PointCut
Advice - 공통 부가 기능이 구현된 코드 블록 {}
JointPoint - Advice를 적용할 수 있는 실행 지점(메소드 실행 / 생성자 호출 / 필드 접근 등) in Target Object
- Spring AOP에서는 항상 메소드 실행을 의미함
PointCut - Advice를 적용할 JointPoint를 선별하는 표현식
- Execution 표현식: execution([수식어] 리턴 타입 [클래스 타입].메소드 이름(파라미터))
- 수식어는 public/private이 가능
- Spring AOP에서는 private만 가능하며, 생략 가능
Target Object - 하나 이상의 Aspect에 의해 Advice가 적용되는 객체
   즉, 모듈이 실행될 수 있는 비즈니스 로직을 담고 있는 객체

 

예제로 확인해보자!

우선 Target Object는 비즈니스 로직을 담고 있는 Service 클래스이다.

@Service
public class OrderService {
    public String createOrder(String itemId, int count) { // joint point
        if (count < 1) {
            throw new IllegalArgumentException("주문 수량은 1개 이상이어야 합니다.");
        }
        return orderId;
    }
}

 

OrderService 내에 createOrder 메소드는 비즈니스 로직으로, 부가 기능(Aspect)이 들어올 수 있는 JointPoint에 해당한다.

 

이제 분리 모듈을 살펴보자.

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.practice.service.OrderService.*(..))")
    private void orderPointcut() {}

    @Around("orderPointcut()")
    public Object applicationLogger(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        Object targetObject = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        
        System.out.println("\n*** Advice: [BEFORE] " + methodName + " 시작 ***");
        System.out.println("JoinPoint.getTarget() -> Target Object 클래스: " + targetObject.getClass().getSimpleName());
        System.out.print("JoinPoint.getArgs() -> 메서드 인자: ");
        for (Object arg : args) {
            System.out.print(arg + " ");
        }
        System.out.println();

        Object result;
        try {
            result = joinPoint.proceed();
            System.out.println("Advice: [AFTER RETURNING] " + methodName + " 정상 종료. 반환 값: " + result);
        } catch (Throwable e) {
            System.out.println("Advice: [AFTER THROWING] " + methodName + " 예외 발생: " + e.getMessage());
            throw e;
        } finally {
            System.out.println("Advice: [AFTER] " + methodName + " 종료 (정상 또는 예외 발생 여부와 무관)\n");
        }

        return result;
    }
}

 

먼저 부가 기능 모듈임을 표현하기 위해 클래스 위에 @Aspect라는 어노테이션을 붙인다.

→ @PointCut으로 Advice를 적용할 JointPoint 범위를 특정한다.

→  Aspect가 특정 JoinPoint에서 취할 동작을 applicationLogger 내에 구현한다.

    이때, @Around는 Advice의 실행 시점을 의미한다.

 

Advice의 실행 시점은 @Around 포함, 여러 가지가 존재한다.

  • @Around: proceed() 앞/뒤에 메소드 실행 전/후 시 Advice 기능을 각각 수행한다. 
  • @Before: Advice 타겟 메소드(JointPoint)가 호출되기 전에 Advice 기능을 수행한다.
  • @After: Target Object의 메소드 결과(성공/예외)와 관계없이 Target Object의 메소드가 완료되면 Advice 기능을 수행한다.
  • @AfterReturning: Target Object의 메소드 결과가 성공적으로 반환된 후에, Advice 기능을 수행한다.
  • @AfterThrowing: Target Object의 메소드 수행 중 예외를 던지게 되면, Advice 기능을 수행한다.

 


 

AOP 동작 방식

Aspect(모듈)를 Target Object에 결합하여 안전하게 부가 기능을 수행하려면 Weaving(위빙)이라는 과정이 필요하다.

 

위빙(Weaving) 시점은 총 3가지로 분류된다.

  • 컴파일 타임
    • 컴파일 과정 중 AspectJ 컴파일러가 .class 파일 자체에 Advice 코드를 삽입해 새로운 .class 파일을 생성한다.
    • AOP 적용을 위해, 위와 같이 전용 컴파일러를 사용해야 하므로 빌드 환경이 복잡해질 수 있다.
  • 클래스 로딩 타임
    • 컴파일은 일반 컴파일러를 사용한다.
    • JVM의 클래스 로더로 중간 파일(.class)을 적재할 때, Weaver(위버)라는 도구를 사용하여 동적으로 코드 내에 Advice를 삽입한다.
    • -javaagent 설정이 추가로 필요하다.
  • 런타임 위빙
    • Spring AOP에서 사용하는 방식으로, AOP Proxy를 이용한다.

 

AOP Proxy

Spring AOP에서 사용하는 AOP Proxy 객체가 어떠한 방식으로 위빙을 진행하는지 살펴보자.

 

  • Spring 컨테이너가 Target Object를 생성할 때, 이 객체를 감싸는 AOP Proxy 객체를 동적으로 생성한다.
  • 클라이언트가 Target Object의 메소드, 즉 JointPoint를 호출하면 프록시 객체가 이 호출을 가로챈다.
  • 프록시 객체는 가로챈 호출이 PointCut이 지정한 JointPoint에 해당되는지 확인한다.
  • JointPoint라면 프록시 객체는 Target Object 메소드를 실행하기 전/후에 Aspect에 정의된 Advice 코드들을 자동으로 실행한다.

※ 프록시 객체 내의 메소드는 실제 Target Object 메소드를 호출하여 위임한다.

※ 프록시 객체 생성 시점은 스프링 컨테이너가 Aspect → PointCut의 메소드가 포함된 클래스(Target Object)를 프록시 객체로 만들어 Spring Bean으로 등록한다.

 

Proxy 객체 생성(JDK Dynamic Proxy, CGLIB)

위빙 과정에서 Target Object의 비즈니스 로직 구현 방식에 따라 Proxy 객체 생성 방식 또한 달라진다.

 

JDK Dynamic Proxy

JDK 동적 프록시는 Target Object가 인터페이스 기반으로 구현이 되었을 때 사용 가능한 프록시 생성 방식이다.

프록시 생성 방식은 다음과 같다.

Target Object가 구현한 인터페이스를 → 프록시 클래스 또한 런타임에 가져와 JointPoint를 동적으로 생성한다.

 

만약 클라이언트의 요청으로 인해 Target Object의 비즈니스 로직을 호출하게 되면, 프록시 객체가 호출을 가로챈다.

이후 JointPoint 내에서 Advice를 실행한 다음, invoke() 메소드로 실제 Target Object의 메소드를 호출하게 된다!

 

※ 다만, invoke()를 호출할 때 메소드의 접근 권한 or 인자가 유효한지 확인하기 위해 Reflection을 사용하므로 오버헤드가 발생할 수 있다.

 

CGLIB

CGLIB는 Target Object가 클래스 기반으로 구현이 되었을 때 사용 가능한 프록시 생성 방식이다.

프록시 생성 방식은 다음과 같다.

CGLIB 라이브러리를 사용하여 Target Object의 클래스를 상속 받는 프록시 클래스를 동적으로 생성한다.

생성된 프록시 객체는 Target Object의 JointPoint를 재정의한다.

 

이후 클라이언트의 요청으로 인해 Target Object의 비즈니스 로직을 호출하게 되면, 프록시 객체가 호출을 가로챈다.

오버라이드된 프록시 메소드 내에서 Advice를 실행한 다음, super.메소드()로 부모(Target Object)의 원본 메소드에게 위임(호출)한다.

(JPA의 지연 로딩을 위한 프록시 객체 역시 CGLIB를 기반으로 생성된다!)

 


 

지금까지 AOP에 대해 알아보았다.!

AOP는 횡단 관심사가 비즈니스 로직에 드러나지 않는 프로그래밍 방식이다.

부가 기능을 한 번에 모아 실행할 수 있다는 점, 유지보수에 좋다는 점을 이유로 유용하게 쓰이고 있다.

또한 위빙 과정에서 사용되는 프록시 객체는 Target Object를 감싸서 호출을 가로챈 후, 원본 Target Object로 위임한다.

유의하도록 하자 하하하하😊😊

 

👏

 

참고

https://dgjinsu.tistory.com/31

https://engkimbs.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81AOP

'Development > 개발 공부' 카테고리의 다른 글

[JPA] JPA(Java Persistence API)란? (1)  (0) 2026.02.08
[Git] 깃 브랜치 전략(Git Flow, Github Flow)(+TBD)  (0) 2025.12.14
[NoSQL] Redis란?  (0) 2025.10.17
[Test] Mock을 이용한 단위 테스트(with Mockito)  (0) 2025.10.01
[Test] Stub을 이용한 단위 테스트(with JUnit)  (0) 2025.09.30
'Development/개발 공부' 카테고리의 다른 글
  • [JPA] JPA(Java Persistence API)란? (1)
  • [Git] 깃 브랜치 전략(Git Flow, Github Flow)(+TBD)
  • [NoSQL] Redis란?
  • [Test] Mock을 이용한 단위 테스트(with Mockito)
jjangsudiary
jjangsudiary
jjangsudiary 님의 블로그 입니다.
  • jjangsudiary
    jjangsudiary 님의 블로그
    jjangsudiary
  • 전체
    오늘
    어제
    • 분류 전체보기 (81) N
      • 이모저모 (0)
        • 회고 (0)
      • Development (17) N
        • 개발 공부 (14) N
        • 프로젝트 (2)
      • Android (10)
        • Compose (1)
      • AI (15)
      • Computer Science (25)
        • 네트워크 (8)
        • 데이터베이스 (10)
        • 운영체제 (6)
        • 자료구조 (0)
        • 컴퓨터구조 (1)
      • Java (9)
        • 디자인패턴 (2)
      • Spring (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    안드로이드
    java
    인공지능
    baekjoon
    os
    CS
    Python
    운영체제
    백준
    자바
    Ai
    db
    TensorFlow
    딥러닝
    database
    프로그래머스
    파이썬
    android
    코딩 테스트
    머신러닝
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
jjangsudiary
AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)
상단으로

티스토리툴바