10/30

졸용·2025년 10월 30일

TIL

목록 보기
104/144

🔹 AOP란?

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

공통된 부가 기능을 모듈화하여 핵심 비즈니스 로직과 분리하는 프로그래밍 기법을 말한다.

Spring 프레임워크에서 매우 자주 사용되며, 특히

  • 로그 관리
  • 트랜잭션 처리
  • 예외 처리
  • 보안 검사

등과 같은 횡단 관심사(cross-cutting concerns)를 분리하는 데 사용된다.


🔸 AOP의 핵심 개념

용어설명
Aspect (관점)공통 기능(예: 로깅, 트랜잭션)을 모듈화한 단위
Join Point (조인 포인트)Advice(공통 로직)가 적용될 수 있는 코드 실행 지점 (ex. 메서드 호출 시점)
Pointcut (포인트컷)Advice를 실제로 적용할 대상 메서드를 지정하는 표현식
Advice (어드바이스)실제로 실행되는 공통 기능 코드 (ex. 메서드 실행 전/후에 로그 출력)
Weaving (위빙)핵심 로직 코드에 공통 관심사를 삽입하는 과정 (Spring에서는 런타임 프록시 기반)

🔸 AOP 적용 예시 상황

상황설명
로깅 (Logging)모든 API 요청/응답 로그 기록
트랜잭션 관리메서드 실행 전 트랜잭션 시작 → 정상 종료 시 커밋, 예외 시 롤백
성능 측정메서드 실행 시간 측정
보안 검사메서드 실행 전 권한 체크
예외 처리공통 예외 로깅 및 응답 포맷 통일


🔹 AOP 사용 시 장단점

✅ 장점

  • 중복 코드 제거: 로깅, 트랜잭션 등 공통 로직을 분리
  • 유지보수성 향상: 핵심 로직과 부가 로직이 분리되어 가독성 상승
  • 관심사 분리(SoC) 실현

❌ 단점

  • 디버깅 어려움: 실제 실행 경로가 Proxy를 거치기 때문에 추적이 복잡할 수 있음
  • 성능 오버헤드: 프록시 생성 및 메서드 호출 래핑 과정에서 약간의 비용 발생
  • 과도한 남용 위험: 비즈니스 로직까지 AOP로 처리하면 코드 추적이 어렵다


🔹 AOP 요약

구분내용
핵심 목적공통 관심사를 분리하여 코드 중복 제거
핵심 구성요소Aspect, Advice, Pointcut, JoinPoint, Weaving
구현 방법프록시 기반 런타임 AOP
주요 사용 예시로깅, 트랜잭션, 보안, 성능 측정
핵심 어노테이션@Aspect, @Around, @Before, @After


🔹 대표적인 Advice 유형

Advice 유형실행 시점설명
@Before메서드 실행 이전실행 전 로깅, 인증 검사 등에 사용
@AfterReturning메서드가 정상 종료 후결과값 로깅, 리턴 데이터 후처리
@AfterThrowing예외 발생 시예외 로깅, 에러 전송 처리
@After무조건 실행finally 블록 역할
@Around메서드 전/후 모두 제어가장 강력한 형태, 메서드 실행 자체를 감쌈 (ProceedingJoinPoint)

🔸 예시 코드

@Aspect
@Component
public class LoggingAspect {

    // 서비스 계층의 모든 메서드 실행 전/후에 적용
    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        System.out.println("[AOP] 실행 메서드: " + joinPoint.getSignature());

        Object result = joinPoint.proceed();  // 실제 메서드 실행

        long end = System.currentTimeMillis();
        System.out.println("[AOP] 실행 시간: " + (end - start) + "ms");

        return result;
    }
}

Pointcut 표현식 예시:

  • execution(* com.example..*(..)) : 특정 패키지 이하 모든 메서드
  • @annotation(org.springframework.transaction.annotation.Transactional) : 특정 어노테이션이 붙은 메서드
  • within(@org.springframework.stereotype.Service *) : @Service가 붙은 클래스 내 모든 메서드


🔹 Spring AOP의 동작 방식

Spring은 프록시(Proxy) 기반의 AOP를 사용한다.
→ 런타임 시점에 프록시 객체를 생성하여 메서드 호출 전후에 부가 로직을 삽입한다.

ClientProxy(부가기능 + 실제 객체)Target Bean

예시 흐름:

  1. 사용자가 Service 메서드를 호출
  2. Spring이 해당 Bean에 대한 Proxy 객체를 가로채서 실행
  3. Proxy가 Before Advice → 실제 메서드 → After Advice 순으로 실행
  4. 결과를 반환

Spring은 기본적으로 두 가지 프록시 전략을 사용한다:

  • JDK Dynamic Proxy: 인터페이스 기반 프록시 (기본값)
  • CGLIB Proxy: 클래스 상속 기반 프록시 (proxyTargetClass=true 설정 시)


🔹 Spring Boot 프로젝트에서 로깅 AOP를 구현하는 간단한 예시

🔸 의존성 추가

// build.gradle
dependencies {
    implementation "org.springframework.boot:spring-boot-starter-aop"
}

🔸 AOP 설정 클래스

package com.example.demo.common.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class LoggingAspect {

    // Controller, Service 패키지 모두 적용
    @Around("execution(* com.example.demo..controller..*(..)) || execution(* com.example.demo..service..*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {

        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        long startTime = System.currentTimeMillis();
        log.info("▶ START [{}#{}] args={}", className, methodName, joinPoint.getArgs());

        Object result = null;
        try {
            result = joinPoint.proceed(); // 실제 메서드 실행
            return result;
        } finally {
            long endTime = System.currentTimeMillis();
            log.info("◀ END   [{}#{}] time={}ms result={}", className, methodName, (endTime - startTime), result);
        }
    }
}
  • @Aspect: AOP 클래스임을 명시
  • @Around: 메서드 전/후 모두 제어 가능
  • joinPoint.proceed(): 실제 비즈니스 로직 실행
  • execution( com.example.demo..): 패키지 하위 모든 메서드 지정

🔸 적용 대상 예시

  • UserController
package com.example.demo.controller;

import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id) {
        return userService.getUserInfo(id);
    }
}
  • UserService
package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public String getUserInfo(Long id) {
        // DB 조회 로직 대신 단순 예시
        return "User#" + id;
    }
}

🔸 실행 로그 예시

  • 요청:
GET /users/10
  • 로그 출력:
INFOSTART [UserController#getUser] args=[10]
INFOSTART [UserService#getUserInfo] args=[10]
INFOEND   [UserService#getUserInfo] time=5ms result=User#10
INFOEND   [UserController#getUser] time=7ms result=User#10



🔹 Spring AOP vs AspectJ AOP

구분Spring AOPAspectJ AOP
설명Spring 프레임워크에서 제공하는 프록시 기반 AOP 구현체Java 언어 수준에서 지원되는 컴파일/바이트코드 조작 기반 AOP 프레임워크
제공 주체Spring FrameworkEclipse 재단 (AspectJ 프로젝트)
핵심 목적런타임 프록시로 빠르고 간편하게 부가 기능 적용더 정밀하고 강력한 AOP 제어 (모든 join point 지원)

🔸 동작 시점 비교

항목Spring AOPAspectJ AOP
Weaving(위빙) 시점런타임 (Runtime)컴파일/로딩 시점 (Compile-time, Load-time)
Weaving 방식프록시 객체 생성 (Proxy 기반)바이트코드 조작 (Bytecode Instrumentation)
필요한 설정Spring Bean에 @Aspect 붙이면 끝별도의 컴파일러(ajc) 또는 LTW(Load-Time Weaving) 설정 필요
  • Spring AOP → Bean 실행 시점에 프록시로 감쌈
  • AspectJ AOP → 클래스 바이트코드를 직접 수정해, 프록시 없이 AOP 적용

🔸 적용 범위 (Join Point)

구분Spring AOPAspectJ AOP
지원 Join Point메서드 실행(execution())만 가능생성자, 필드 접근, static 블록, 예외 처리 등 모든 join point 지원
적용 대상Spring IoC 컨테이너에서 관리되는 Bean모든 Java 객체 (일반 POJO 포함)

예를 들면,

  • Spring AOP → @Service, @Repository 등 Bean에만 적용됨
  • AspectJ → new 키워드, 필드 접근(get/set), 클래스 초기화 등에도 적용 가능

🔸 구현/설정 비교

구분Spring AOPAspectJ AOP
설정 난이도간단 (@Aspect + @EnableAspectJAutoProxy)복잡 (ajc 컴파일러, LTW 설정 필요)
의존성Spring AOP 라이브러리만으로 충분AspectJ Runtime + Compiler 필요
프록시 기반✅ (JDK Dynamic Proxy / CGLIB)❌ (바이트코드 직접 조작)
런타임 오버헤드약간 있음 (프록시 객체 경유)거의 없음 (직접 바이트코드 변경)
디버깅 난이도쉬움상대적으로 어려움 (바이트코드 변환으로 인한 추적 어려움)

🔹 Spring AOP 예시 vs AspectJ 예시

🔸 Spring AOP 예시

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Spring AOP 실행 전: " + joinPoint.getSignature());
    }
}
  • Spring Bean으로 등록된 객체에만 적용
  • 런타임 시 프록시 객체로 감쌈

🔸 AspectJ 예시

@Aspect
public class LoggingAspect {

    @Before("execution(* com.example..*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("AspectJ 실행 전: " + joinPoint.getSignature());
    }
}

추가 설정 필요 (예: Maven 플러그인)

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>aspectj-maven-plugin</artifactId>
  <version>1.14.0</version>
  <configuration>
    <complianceLevel>17</complianceLevel>
    <source>17</source>
    <target>17</target>
  </configuration>
</plugin>
  • ajc 컴파일러가 .class 파일을 직접 변환해 AOP 삽입


🔹 비교 요약

항목Spring AOPAspectJ AOP
구현 방식Proxy 기반 (JDK / CGLIB)Bytecode Weaving
적용 시점런타임컴파일 / 로드타임
지원 Join Point메서드 실행만모든 Join Point
적용 대상Spring Bean만모든 Java 객체
성능상대적으로 느림빠름
설정 난이도쉬움복잡함
주요 사용 목적트랜잭션, 로깅, 보안 등 간단한 AOP성능 분석, 시스템 단위 로깅 등 고급 AOP

🔸 실제 실무 선택 기준

상황추천
Spring 기반 애플리케이션✅ Spring AOP (기본 제공, 간단, 유지보수 용이)
비-Spring 환경 or 정교한 Join Point 필요⚙️ AspectJ AOP (성능 최적화, 깊은 수준의 AOP 제어 필요 시)


🔹 Spring AOP vs AspectJ AOP 결론

  • Spring AOP: “간단하고 실용적인 AOP”
    → 프록시 기반, Spring Bean에만 적용, 설정 간단, 실무 대부분에서 사용
  • AspectJ AOP: “정밀하고 강력한 AOP”
    → 바이트코드 조작 기반, 성능 우수하지만 설정 복잡
profile
꾸준한 공부만이 답이다

0개의 댓글