본문 바로가기
이직&취업/Spring Framework

Spring Framework AOP 란?

by journeylabs 2025. 3. 20.
728x90
반응형

목차

    Spring Framework AOP (Aspect-Oriented Programming) 상세 가이드

    1. AOP (Aspect-Oriented Programming) 개요

    AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 소프트웨어 개발에서 핵심 로직(Core Logic)과 공통 기능(Cross-Cutting Concerns)을 분리하여 코드의 모듈성을 향상시키는 프로그래밍 패러다임이다. 이를 통해 코드 중복을 줄이고 유지보수를 쉽게 만들 수 있다.

    2. AOP 주요 개념

    AOP를 이해하기 위해 몇 가지 핵심 개념을 살펴보자.

    • Aspect (관점): 공통적인 기능을 모듈화한 객체로, 여러 클래스에서 공통으로 사용되는 기능(예: 로깅, 트랜잭션 관리 등)을 정의한다.
    • Join Point (조인 포인트): 실행 중에 Aspect가 적용될 수 있는 지점(메서드 실행, 예외 처리 등)을 의미한다.
    • Advice (어드바이스): 특정 Join Point에서 실행될 코드로, Aspect가 수행할 동작을 정의한다.
    • Pointcut (포인트컷): 어떤 Join Point에서 Advice를 적용할지 결정하는 표현식이다.
    • Weaving (위빙): Advice를 대상 객체(Target Object)에 적용하는 과정으로, 컴파일, 클래스 로딩, 런타임 중에 수행될 수 있다.
    • Target Object (대상 객체): AOP의 적용을 받는 객체이다.
    • Proxy (프록시): AOP가 적용된 후 생성되는 객체로, 실제 대상 객체의 기능을 확장하여 Advice를 실행한다.

    3. 주요 용어 및 어노테이션 설명

    Spring AOP에서는 다양한 어노테이션을 제공하여 AOP를 쉽게 적용할 수 있도록 한다.

    • @Aspect : 해당 클래스가 AOP의 Aspect(관점)임을 선언하는 어노테이션이다. 이 클래스 내에서 여러 Advice를 정의할 수 있다.
    @Aspect
    @Component
    public class LoggingAspect {
        // Advice 정의 가능
    }
    • @Pointcut : 특정 Join Point를 지정하는 표현식을 정의하는 어노테이션이다. 이를 활용하여 다양한 메서드 실행 패턴을 지정할 수 있다.
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceMethods() {}

    위 코드는 com.example.service 패키지 내의 모든 메서드 실행을 Pointcut으로 설정한다.

    • @Before : 특정 Join Point가 실행되기 전에 Advice를 실행하도록 지정하는 어노테이션이다.
    @Before("execution(* com.example.service..*(..))")
    public void beforeAdvice() {
        System.out.println("메서드 실행 전에 수행됩니다.");
    }

    위 코드에서 beforeAdvice 메서드는 서비스 메서드 실행 전에 호출된다.

    • @After : 특정 Join Point 실행 후 실행될 Advice를 정의하는 어노테이션이다.
    @After("execution(* com.example.service..*(..))")
    public void afterAdvice() {
        System.out.println("메서드 실행 후 수행됩니다.");
    }

    위 코드에서 afterAdvice 메서드는 서비스 메서드 실행이 끝난 후 실행된다.

    • @AfterReturning : 메서드가 정상적으로 실행된 후 실행될 Advice를 정의하는 어노테이션이다.
    @AfterReturning(pointcut = "execution(* com.example.service..*(..))", returning = "result")
    public void afterReturningAdvice(Object result) {
        System.out.println("메서드가 정상적으로 실행된 후 수행됩니다. 결과: " + result);
    }

    위 코드에서 afterReturningAdvice 메서드는 대상 메서드가 예외 없이 실행된 후 실행되며, 결과 값을 받아서 사용할 수도 있다.

    • @AfterThrowing : 예외가 발생했을 때 실행될 Advice를 정의하는 어노테이션이다.
    @AfterThrowing(pointcut = "execution(* com.example.service..*(..))", throwing = "ex")
    public void afterThrowingAdvice(Exception ex) {
        System.out.println("예외가 발생했습니다: " + ex.getMessage());
    }

    위 코드에서 afterThrowingAdvice 메서드는 대상 메서드에서 예외가 발생했을 때 실행된다.

    • @Around : 메서드 실행 전후에 Advice를 실행하도록 지정하는 어노테이션으로, 메서드 실행을 제어할 수도 있다.
    @Around("execution(* com.example.service..*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println(joinPoint.getSignature() + " 실행 시간: " + (end - start) + "ms");
        return result;
    }

    aroundAdvice 메서드는 메서드 실행 전후의 시간을 측정하고 실행 시간을 출력한다. 또한 joinPoint.proceed()를 호출하여 실제 대상 메서드를 실행할 수도 있다.

    4. AOP의 특징

    AOP를 사용하면 다음과 같은 장점을 얻을 수 있다.

    1. 관심사의 분리 (Separation of Concerns): 핵심 비즈니스 로직과 공통 기능을 분리하여 코드 가독성을 향상시킨다.
    2. 코드 중복 감소: 로깅, 보안, 트랜잭션 관리 등의 공통 기능을 중앙에서 관리할 수 있어 코드 중복을 줄일 수 있다.
    3. 유지보수 용이성: 공통 기능이 한 곳에서 관리되므로 유지보수가 쉽다.
    4. 비침투성 (Non-Intrusiveness): 기존 코드에 최소한의 변경만으로 AOP를 적용할 수 있다.

    5. AOP 예제 및 예제 상세 설명

    AOP를 실제 프로젝트에 적용하는 방법을 예제와 함께 상세히 설명한다.

    5.1. AOP 설정 및 예제 코드

    다음은 Spring AOP를 사용하여 서비스 메서드 실행 전에 로그를 남기는 예제이다.

    5.1.1. 의존성 추가 (Spring Boot 기준)

    먼저, spring-boot-starter-aop 의존성을 추가해야 한다.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    5.1.2. 서비스 클래스 생성

    비즈니스 로직을 담당하는 UserService 클래스를 만든다.

    @Service
    public class UserService {
        public void getUserById(Long id) {
            System.out.println("사용자 ID: " + id);
        }
    }

    5.1.3. AOP Aspect 클래스 생성

    이제 LoggingAspect를 만들어 메서드 실행 전에 로그를 출력하도록 한다.

    @Aspect
    @Component
    public class LoggingAspect {
    
        @Before("execution(* com.example.service.UserService.getUserById(..))")
        public void beforeAdvice(JoinPoint joinPoint) {
            System.out.println("[로그] 메서드 실행: " + joinPoint.getSignature().getName());
        }
    }

    5.2 실행 및 결과 확인

    Spring Boot 애플리케이션을 실행한 후 UserServicegetUserById() 메서드를 호출하면 다음과 같은 출력이 나타난다.

    [로그] 메서드 실행: getUserById
    사용자 ID: 1

    5.3 @Around 어노테이션을 활용한 실행 시간 측정

    메서드 실행 시간을 측정하려면 @Around 어노테이션을 사용할 수 있다.

    @Aspect
    @Component
    public class ExecutionTimeAspect {
    
        @Around("execution(* com.example.service..*(..))")
        public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println(joinPoint.getSignature() + " 실행 시간: " + (end - start) + "ms");
            return result;
        }
    }

    위 코드를 적용하면, UserService의 메서드 실행 시간이 자동으로 측정된다.

    5.4 예외 처리 예제 (@AfterThrowing 활용)

    메서드 실행 중 예외가 발생했을 때 로깅하는 AOP를 추가할 수도 있다.

    @Aspect
    @Component
    public class ExceptionHandlingAspect {
    
        @AfterThrowing(pointcut = "execution(* com.example.service..*(..))", throwing = "ex")
        public void logException(JoinPoint joinPoint, Exception ex) {
            System.out.println("[예외 발생] 메서드: " + joinPoint.getSignature().getName() + ", 메시지: " + ex.getMessage());
        }
    }

    5.5 트랜잭션 관리와 AOP 적용 예제

    AOP는 트랜잭션 관리에도 사용할 수 있다.

    @Aspect
    @Component
    public class TransactionAspect {
        
        @Around("execution(* com.example.service..*(..))")
        public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("트랜잭션 시작");
            try {
                Object result = joinPoint.proceed();
                System.out.println("트랜잭션 커밋");
                return result;
            } catch (Exception ex) {
                System.out.println("트랜잭션 롤백");
                throw ex;
            }
        }
    }

    위 AOP를 적용하면, 메서드 실행 전후로 트랜잭션을 관리할 수 있다.

    6. AOP 적용 시 주의사항

    AOP 적용 시 몇 가지 고려해야 할 사항이 있다.

            1. AOP는 Spring 프록시 기반으로 동작하므로, 프록시 객체를 통해서만 Advice가 적용된다.
              • 같은 클래스 내에서 직접 메서드를 호출하면 AOP가 적용되지 않는다.
            2. Spring AOP는 기본적으로 메서드 실행(Join Point)만 지원한다.
              • 필드 접근, 생성자 호출 등의 Join Point는 지원하지 않는다.
            3. @Around Advice는 반드시 joinPoint.proceed()를 호출해야 한다.
              • 호출하지 않으면 원래 메서드가 실행되지 않는다.
            4. Pointcut 표현식의 범위를 정확하게 지정해야 한다.
              • 잘못된 설정으로 인해 원치 않는 메서드에 AOP가 적용될 수 있다.
            5. 과도한 사용을 피하라.
              • AOP는 강력한 기능이지만, 남용하면 디버깅이 어려워질 수 있다
            6. Pointcut 범위를 신중하게 설정하라.
              • 불필요한 메서드에도 Advice가 적용되지 않도록 정확한 범위를 지정해야 한다.
            7. Proxy 기반 한계를 이해하라.
              • Spring AOP는 기본적으로 JDK 동적 프록시 또는 CGLIB을 사용하며, final 메서드에는 적용되지 않는다

    Spring AOP는 로깅, 보안, 트랜잭션 관리 등 공통 관심사를 쉽게 분리하여 코드의 유지보수성과 확장성을 높여준다.

    Spring AOP를 활용하면 코드의 모듈성을 향상시키고 공통 기능을 효율적으로 관리할 수 있다. 적절한 AOP 적용을 통해 유지보수성을 높이고, 개발 생산성을 향상시킬 수 있다.

    728x90
    반응형