티스토리 뷰
개념
- 관점 지향 프로그래밍(AOP)은 애플리케이션 전반에 걸쳐 반복적으로 등장하는 공통 기능, 즉 횡단 관심사를 개별 모듈(Aspect)로 분리하여 관리하는 프로그래밍 패러다임
- 예를 들어, 로깅, 보안, 트랜잭션 관리와 같은 기능은 여러 클래스에 중복해서 들어가기 쉬운데, AOP를 사용하면 이들 기능을 한 곳에 모아 정의한 후 필요한 곳에 자동으로 적용할 수 있음
- 이렇게 함으로써 코드의 중복을 줄이고, 핵심 비즈니스 로직과 부가기능을 분리하여 모듈성과 유지보수성을 크게 향상시킬 수 있음
장점
관심사의 분리 및 모듈화
- 횡단 관심사를 하나의 Aspect로 모아 관리하므로, 핵심 로직은 깔끔하게 유지되고 부가기능은 중앙집중적으로 관리
중복 코드 감소 및 재사용성
- 공통 기능을 한 번만 구현하여 여러 클래스에서 재사용할 수 있으므로 코드 중복을 줄이고, 유지보수가 쉬워짐
유지보수성 향상
- 핵심 코드와 부가 기능이 분리되어 있어, 나중에 부가기능(예: 로깅 정책 변경 등)을 수정할 때 전체 코드에 영향을 주지 않음
읽기 쉬운 코드
- 부가 기능이 Aspect로 분리되어 핵심 비즈니스 로직이 더욱 직관적이고 이해하기 쉬워짐
기능 확장의 유연성
- 새로운 횡단 관심사를 추가할 때 기존 코드 수정 없이 별도의 Aspect를 추가하면 되므로, 확장성이 뛰어남
단점
디버깅 어려움
- AOP 적용으로 인해 프로그램 흐름에 부가기능이 숨겨져 나타나므로, 실제 호출 순서를 파악하기 어렵고 디버깅 시 혼란을 야기할 수 있음
코드 가독성 저하 (오용 시)
- 너무 많은 Aspect가 사용되거나 복잡한 포인트컷이 남발되면 오히려 코드의 흐름 파악이 어려워질 수 있음
러닝 커브 및 복잡도 증가
- AOP의 개념과 관련 용어(Advice, Pointcut, JoinPoint 등)를 익히는 데 추가 학습이 필요하며, 잘못 사용하면 전체 시스템 복잡도가 증가할 위험이 있음
성능 오버헤드
- 프록시를 통한 메소드 호출 방식 때문에 약간의 성능 저하가 발생할 수 있으며, 특히 잘못 설계된 포인트컷이 넓은 범위에 적용될 경우 문제가 될 수 있음
적용 범위의 한계
- Spring AOP의 경우 프록시 기반으로 동작하기 때문에, public 메소드에 한정되며, private, final, static 메소드에는 적용이 어려워 일부 횡단 관심사를 완벽하게 처리하지 못할 수 있음
핵심 개념
횡단 관심사 (Cross-cutting Concern)
- 애플리케이션 전반에 걸쳐 공통적으로 나타나는 부가기능을 말함
- 예를 들어 로깅, 보안, 트랜잭션 등이 있으며, 이것들은 여러 모듈에 산재(흩어져) 있어 한 곳에 모으기 어려운 코드들
Aspect
- 공통 기능(부가기능)을 하나의 모듈로 캡슐화한 것으로, 여러 Advice와 Pointcut을 포함
- 쉽게 말해, “어떤 부가기능을, 언제 적용할 것인가”를 캡슐화한 모듈
- Aspect는 보통 클래스처럼 정의
- 예를 들어 “서비스 메소드 실행 전에 로깅하기”라는 횡단 관심사를 Aspect로 구현할 수 있음
Advice
- 실제 부가기능 코드로, 메소드 실행 전, 후, 예외 발생 시 등 특정 시점에 실행 (예: Before, After, Around 등)
- 스프링 AOP에서는 Advice의 종류로 @Before, @After, @AfterReturning, @AfterThrowing, @Around 등이 있으며, 이것을 통해 메소드 실행 전(before), 후(after), 예외 발생 시(after-throwing), 정상 반환 후(after-returning), 둘러싸서 실행(around) 등의 시점을 지정할 수 있음
JoinPoint
- Advice가 적용될 수 있는 실행 지점으로, 일반적으로 메소드 호출이 해당
- 프로그램 실행 흐름상의 특정 순간이나 이벤트로, 메소드 호출이나 객체 생성, 예외 발생 등이 JoinPoint가 될 수 있음
- 스프링 AOP에서 JoinPoint는 항상 메소드 실행 지점을 가리키며, 그 외의 지점(필드 접근 등)은 지원하지 않
Pointcut
- 여러 JoinPoint 중에서 어떤 것에 Advice를 적용할지를 결정하는 조건이나 표현식
- “어떤 JoinPoint에 Aspect를 적용할 것인가”를 결정하는 역할
- 보통 클래스 이름이나 메소드 이름 패턴, 애노테이션, 접근 제어자 등으로 지정할 수 있으며, AspectJ 표현식 언어를 사용하여 정의
- 예를 들어 execution(* com.example.service..*(..))라는 포인트컷 표현식은 com.example.service 패키지 아래의 모든 메소드 실행을 JoinPoint로 선택한다는 의미
- Pointcut을 통해 특정 메소드나 클래스에만 부가기능을 적용하거나, 특정 애노테이션이 붙은 메소드에만 적용하는 등 세밀한 제어가 가능합니다
Weaving
- 정의된 Aspect를 대상 객체에 결합하는 과정으로, 컴파일 타임, 클래스 로드 타임, 또는 런타임에 이루어질 수 있음
- 위빙을 통해 애플리케이션 코드에 부가기능이 연결되며, 그 결과 Advice가 설정된 JoinPoint에서 동작하게 됨
Target 객체
- AOP에서는 대상 객체를 가리킴
- 부가기능이 적용될 원래의 핵심 로직 객체
프록시
- AOP를 적용하기 위해 대상 객체를 감싸는 대리 객체로, 메소드 호출 시 부가기능(Advice)를 먼저 수행한 후 실제 메소드를 호출
- AOP 적용을 위해 생성된 래퍼(wrapper) 객체로, Target을 감싸서 대신 메소드를 호출해주는 객체
- 클라이언트가 Target의 메소드를 호출하면 실제론 Proxy가 호출을 가로채서, 먼저 Advice에 해당하는 부가기능을 수행한 뒤에 Target 메소드를 호출
활용 방법
로깅
- 애플리케이션의 메소드 진입/종료 시점이나 예외 발생 시점에 로그를 남기는 것은 전형적인 횡단 관심사
- AOP를 사용하면 각 클래스마다 로그 코드를 넣지 않고도, 공통 로깅 Aspect를 통해 일관된 로깅 정책을 적용할 수 있음
- 예를 들어, 모든 서비스 메소드가 호출될 때마다 실행 시간을 측정하고 로그를 남기는 Aspect를 정의하면, 서비스 계층의 모든 메소드에 자동으로 해당 기능이 적용
- 개발자는 서비스 메소드 본문에 로그 코드를 넣을 필요 없이도, 투명하게(logically invisibly) 로그 기능이 적용된 결과를 얻을 수 있음
보안 및 권한 체크
- 중요한 비즈니스 메소드마다 권한을 확인하는 코드가 반복된다면 코드 중복과 누락 문제가 발생
- AOP를 통해 보안 검증을 Aspect로 구현하면, 특정 어노테이션이 붙은 메소드나 특정 패키지의 클래스들에 대해 메소드 실행 전 사용자 인증/권한검사를 공통 처리할 수 있음
- 예를 들어 @AdminOnly 같은 애노테이션을 만들고, 해당 애노테이션이 달린 메소드 호출 전에 사용자 권한을 검사하는 Before Advice를 두는 식임
- 이렇게 하면 권한이 없는 사용자의 접근을 사전에 차단하는 로직을 일일이 구현하지 않고도 일괄 적용할 수 있
트랜잭션 관리
- 데이터베이스 작업이 수행되는 서비스 메소드들에 트랜잭션 시작 및 커밋/롤백 코드를 넣는 대신, AOP를 활용해 선언적 트랜잭션 (@Transactional)을 구현할 수 있음
- Spring의 경우 @Transactional 애노테이션을 붙이면 프레임워크가 내부적으로 AOP를 사용하여 해당 메소드를 프록시로 감싸고, 호출 시 자동으로 트랜잭션 시작/종료를 처리
- 이처럼 AOP를 통해 핵심 비즈니스 로직에는 트랜잭션 처리 코드가 전혀 보이지 않지만, 실행 시에는 투명하게 트랜잭션이 적용되어 원자성 보장과 예외 시 롤백 등이 이루어짐( Spring 프레임워크는 이러한 선언적 트랜잭션 관리를 AOP로 구현하고 있으며, 개발자는 AOP 개념을 몰라도 해당 기능을 사용할 수 있게 해줌 )
성능 모니터링
- 메소드의 실행 시간을 측정하거나 호출 빈도를 집계하는 등의 성능 모니터링도 AOP로 구현하기 좋음
- 예를 들어, @Around Advice를 사용하여 특정 패키지의 모든 메소드 호출 전후 시간을 기록하고, 메소드별 수행 시간을 로깅하거나 누적하여 성능 병목을 찾는 것
- 이 경우에도 각 메소드마다 시간을 재는 코드를 넣는 대신 하나의 Aspect로 일괄 적용할 수 있어 편리
예외 처리
- AOP의 @AfterThrowing Advice를 활용하면 특정 계층에서 발생하는 예외를 한 곳에서 잡아 공통 처리하거나 별도 로깅, 알림 발송에 활용할 수 있음
- 예를 들어, 서비스에서 예외가 발생할 때 슬랙/이메일 알림을 보내는 로직을 Aspect로 구현하면, 개별 서비스 코드 수정 없이도 예외 알림 기능을 적용할 수 있음
프레임워크 중심 설명 (Spring AOP vs AspectJ)
Spring AOP (스프링 AOP)
- Spring AOP는 Spring 프레임워크의 일부로 통합되어 있으며, 런타임(dynamic) 프록시 기법을 통해 AOP를 구현
- Spring IoC 컨테이너에 의해 관리되는 빈(Bean)에 한해서 AOP를 적용하며, 기본적으로 프록시 객체를 생성하여 부가기능을 주입함
- Spring은 JDK 표준 동적 프록시와 CGLIB 바이트코드 생성 라이브러리를 활용하는데, 대상 객체가 인터페이스를 구현한 경우 JDK 동적 프록시를 사용하고, 인터페이스가 없는 POJO일 경우 CGLIB로 대상 클래스를 상속한 프록시를 생성
- 프록시를 통해 메소드 호출을 가로채 Advice를 실행하는 구조이므로, JoinPoint가 메소드 실행으로 제한 (프록시가 메소드 호출을 재정의하여 제어하기 때문)
- 또한 Spring AOP는 기본적으로 컴파일 타임에 별도 처리 없이, 애플리케이션 구동 시점에 프록시 빈을 만들어 적용하므로, 프로젝트에 AspectJ 컴파일러나 특별한 설정이 없어도 비교적 쉽게 사용 가능
- 다만, 프록시 방식의 한계로 스프링 컨테이너 밖의 객체나 static 메소드, 생성자 실행, 필드 접근 등은 AOP 적용이 안 되고, private/final 메소드에도 적용되지 않음
- Spring AOP는 보통 @Aspect 애노테이션과 AspectJ 포인트컷 표현식을 차용하여 사용하며, @EnableAspectJAutoProxy 설정이나 <aop:config>를 통해 활성화함
정리하면, Spring AOP는 애플리케이션 개발 생산성에 초점을 맞춘 AOP로, 일반적인 요구사항(메소드 단위의 횡단 관심사)에 충분한 기능을 제공하고 Spring과 자연스럽게 통합되어 있음
Spring 내부 구현 예로, @Transactional, @Async, @Cacheable 등의 기능이 모두 AOP 프록시를 통해 구현되어 있어 개발자가 편리하게 부가기능을 사용할 수 있게 해줌
AspectJ
- AspectJ는 독립적인 AOP 프레임워크/언어로, Spring AOP보다 광범위하고 강력한 AOP 기능을 제공
- AspectJ의 가장 큰 특징은 바이트코드 조작을 통한 직접적인 위빙
- 즉, 자바 소스 코드를 컴파일할 때 AspectJ 컴파일러(ajc)가 애스펙트가 포함된 새로운 바이트코드를 생성함
- 이 때문에 어노테이션만 붙이면 되는 Spring AOP와 달리, AspectJ를 사용하려면 빌드 과정에 AspectJ 컴파일러를 포함시키거나, 또는 로드타임 위빙을 위해 Java 에이전트 설정과 aop.xml 등의 설정이 필요
- AspectJ는 컴파일 시(또는 로드 시) 부가기능이 삽입되므로, 런타임 오버헤드 없이 원래 코드 자체가 바뀌는 효과를 냄 또한 지원하는 JoinPoint 종류가 매우 다양해서, 메소드 실행은 물론 생성자 호출, 객체 초기화, 예외 처리, 필드 get/set 접근 등 세세한 부분까지 포인트컷으로 지정할 수 있음
- 심지어 특정 클래스의 서브클래스를 만드는 등 Introduction(새 메소드나 필드 추가) 같은 고급 기능도 AspectJ는 지원
- AspectJ는 전용 문법(.aj 파일)과 @AspectJ 애노테이션 스타일 두 가지 다 사용할 수 있는데, Spring에서도 AspectJ의 애노테이션 스타일을 차용하기 때문에 표면상 Spring AOP 코드와 유사하게 AspectJ를 활용할 수도 있음
- 하지만 동작 원리는 Spring AOP가 프록시인 반면 AspectJ는 바이트코드 레벨 위빙이라는 점이 다름
- AspectJ로 컴파일된 클래스는 Spring 없이도 자체적으로 부가기능이 포함되어 동작하며, 애플리케이션의 모든 객체에 영향을 줄 수 있음
- 예를 들어, AspectJ를 사용하면 main 메소드 안에서 new로 생성한 객체에도 AOP를 적용할 수 있지만, Spring AOP는 Spring이 관리하는 빈에만 적용
실제 코드 예제
Spring AOP 예제 (어노테이션 기반)
- Spring AOP를 사용하려면 우선 의존성에 spring-boot-starter-aop 등을 추가하고, @EnableAspectJAutoProxy 설정이나 <aop:aspectj-autoproxy/> 설정을 통해 AOP를 활성화해야 함
// 로깅용 Aspect 클래스 (Spring AOP - @AspectJ 스타일)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Aspect
@Component // Spring 빈으로 등록
public class LoggingAspect {
private Logger log = LogManager.getLogger(getClass());
// Pointcut: com.example.web.HomeController 클래스의 모든 메소드
@After("execution(* com.example.web.HomeController.*(..))")
public void logAfterMethod(JoinPoint joinPoint) {
// Advice: 메소드 이름을 로깅 (After Advice)
String methodName = joinPoint.getSignature().getName();
log.info(methodName + " 메소드가 호출되었습니다.");
}
}
- 위 코드에서 @Aspect 애노테이션이 붙은 클래스가 Aspect이며, logAfterMethod 메소드가 Advice 역할을 함
- @After("execution(* com.example.web.HomeController.*(..))") 부분이 포인트컷(Pointcut) 표현식으로, HomeController 클래스의 모든 메소드 실행 후에 Advice를 실행하라는 의미
- 따라서 HomeController의 어떤 메소드가 호출되어도, 호출 종료 후에 logAfterMethod가 자동으로 실행되어 로그를 남기게 됨
- 여기서 JoinPoint 매개변수를 통해 현재 실행된 메소드의 정보(이름 등)에 접근하고 있음
- 위 Aspect를 적용하려면 Spring이 이 클래스를 빈으로 인식하도록 @Component를 달고, AOP를 활성화해야 함
- Spring Boot에서는 @SpringBootApplication에 AOP 설정이 포함되어 있으면 자동으로 프록시가 생성되지만, 명시적으로 설정하려면 Java 설정 클래스에 @EnableAspectJAutoProxy를 붙일 수 있음
XML 설정
<aop:aspectj-autoproxy />
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect" />
- (Spring 환경에서는 spring-aop와 AspectJ 라이브러리를 이용해 위와 같은 @Aspect 클래스 정의만으로도 런타임에 프록시 기반 AOP가 적용됩니다.)
- 위 코드 예제에서 볼 수 있듯이, @After로 지정된 메소드(logAfterMethod)는 Advice이며 대상 JoinPoint 이후에 실행됨
- 그리고 "execution(* com.example.web.HomeController.*(..))"라는 문자열이 Pointcut 표현식으로, 해당 패키지의 HomeController 모든 메소드를 가리킴
- 이처럼 Spring AOP에서는 애노테이션과 문자열 기반 표현식으로 어디에 어떤 부가기능을 적용할지 선언
- 또한 Advice의 종류를 @Before, @AfterReturning, @AfterThrowing, @Around 등으로 자유롭게 바꿀 수 있음
- 예를 들어 위 코드에서 @After를 @Before로 바꾸면 메소드 호출 전에 실행되는 로깅을 할 수 있고, @Around를 사용하면 메소드 호출 전후를 모두 감싸는 처리를 할 수 있습니다.
AspectJ 예제 (컴파일 타임 위빙)
- AspectJ에서는 애노테이션 기반 @Aspect 스타일도 지원하지만, 여기서는 AspectJ의 전용 문법(.aj 파일 사용)을 보여줌
- AspectJ를 사용하려면 AspectJ 컴파일러(ajc) 또는 로드타임 위버(LTW) 설정이 필요
- 이 예제는 순수 AspectJ 문법으로 작성된 .aj 파일이라고 가정
// AspectJ 문법으로 작성된 로깅 Aspect (LoggingAspect.aj)
public aspect LoggingAspect {
// HomeController의 모든 메소드 실행 시점
pointcut controllerMethods(): execution(* com.example.web.HomeController.*(..));
// 메소드 호출 전 로깅
before(): controllerMethods() {
System.out.println("[LOG] "
+ thisJoinPointStaticPart.getSignature().getName()
+ " 메소드 호출됨 (AspectJ)");
}
}
- 위 AspectJ 코드에서는 aspect 키워드를 사용하여 Aspect를 정의하고 있음
- pointcut controllerMethods(): execution(* com.example.web.HomeController.*(..))는 포인트컷 선언으로, Spring AOP의 문자열 표현식과 유사하게 HomeController의 모든 메소드 실행을 지정
- 그 아래 before(): controllerMethods() 부분이 Advice에 해당하며, controllerMethods() 포인트컷에 해당하는 JoinPoint 전에 실행될 코드를 블록 { ... } 안에 작성
- thisJoinPointStaticPart.getSignature().getName()는 현재 조인포인트의 메소드 이름을 얻는 AspectJ API
- 이 AspectJ 코드는 컴파일 타임에 위빙(Weaving)되어 HomeController의 메소드들 앞에 System.out.println(...) 구문이 삽입되는 효과를 가짐
- 즉, AspectJ 전용 컴파일러(ajc)를 사용하여 빌드하면 바이트코드에 해당 Aspect가 적용되어 런타임 오버헤드 없이도 부가기능이 실행
- AspectJ를 사용하면 Spring 같은 컨테이너가 없어도, 혹은 public 메소드가 아니어도 (예: 생성자, 정적 메소드, 필드 접근 등) 다양한 조인포인트에 부가 로직을 넣을 수 있음
면접 대비 질문
기초 질문
Q1. AOP란 무엇이며, 이를 사용하는 목적은 무엇인가요?
모범답안
AOP는 Aspect Oriented Programming의 약자로, 관점 지향 프로그래밍이라고 합니다. 애플리케이션 전반에 걸쳐 반복적으로 나타나는 기능(횡단 관심사)을 분리하여 하나의 모듈로 만들고, 필요한 곳에 적용함으로써 중복 코드를 줄이고 모듈화를 향상시키는 것이 목적입니다. 예를 들어 로깅이나 보안 체크 같은 공통 기능을 각 클래스에 넣는 대신 AOP로 정의하면, 핵심 비즈니스 로직은 깨끗이 유지되고 부가기능은 알아서 적용됩니다. 한마디로 "공통된 부분을 한 군데 모아둔 다음, 알아서 필요한 곳에 끼워넣는 기법”이라고 설명할 수 있습니다.
Q2. 횡단 관심사란 무엇이며, 구체적인 사례를 들어 설명해주세요.
모범답안
횡단 관심사란 애플리케이션의 여러 부분에 걸쳐 공통으로 필요하지만 핵심 로직과는 거리가 있는 기능을 말합니다. 이러한 관심사는 여러 모듈에 흩어져 존재하기 때문에 “횡단(cross-cutting)”한다고 표현합니다. 예를 들어 로깅, 트랜잭션 관리, 인증/권한 체크, 캐싱 등이 대표적인 횡단 관심사입니다. 로깅의 경우 대부분의 비즈니스 메소드에서 필요하지만 그 자체가 비즈니스 로직은 아니므로 횡단 관심사에 해당하고, AOP를 통해 분리 관리하는 것이 좋습니다.
Q3. AOP의 핵심 용어(Aspect, Advice, JoinPoint, Pointcut)를 설명해주세요.
모범답안
- AOP에는 몇 가지 주요 개념이 있습니다. Aspect(애스펙트)는 횡단 관심사에 해당하는 부가기능의 모듈 단위로, 어떤 부가기능(Advice)을 언제 적용할지(Pointcut) 포함하고 있습니다. Advice(어드바이스)는 실제로 수행될 부가기능 코드이며, 적용 시점에 따라 Before, After, Around 등의 종류가 있습니다. JoinPoint(조인포인트)는 Advice가 삽입될 수 있는 지점으로, 메소드 호출이나 예외 발생 등 이벤트를 뜻합니다 (Spring AOP에서는 주로 메소드 실행이 조인포인트입니다). Pointcut(포인트컷)은 이러한 조인포인트 중 실제로 Advice를 적용할 대상을 선별하는 설정이나 표현식입니다. 이 밖에 Target 객체(실제 핵심 로직 객체)나 Weaving(위빙, 부가기능을 핵심 로직에 결합하는 과정) 같은 용어도 있습니다.
Q4. AOP를 적용하지 않으면 발생하는 문제는 무엇인가요?
모범답안
횡단 관심사를 AOP로 처리하지 않으면 중복 코드와 관심사 뒤섞임 문제가 발생합니다. 예를 들어, 여러 메소드에 걸쳐 로깅 코드가 반복되면 코드가 장황해지고 수정할 때 모든 곳을 찾아 바꿔야 하는 문제가 생깁니다. 또한 핵심 로직과 부가기능 코드가 섞여 있어 코드를 이해하거나 변경할 때 실수할 가능성이 높아집니다. AOP를 사용하지 않아도 기능 구현은 가능하지만, 코드가 지저분해지고 유지보수성이 떨어지는 문제가 있습니다. 반대로 AOP를 적용하면 공통 기능을 모듈화하여 이러한 문제를 해결할 수 있습니다.
심화 질문
Q1. Spring AOP와 AspectJ의 차이점을 설명해주세요.
모범답안
Spring AOP는 Spring 프레임워크 내에서 제공하는 AOP 구현으로, 런타임 프록시 기반으로 동작합니다. Spring IoC 컨테이너가 관리하는 빈(Bean)에 한해서만 AOP가 적용되고, JoinPoint도 메소드 실행에 한정됩니다. 내부적으로 대상 객체의 프록시를 생성하여 메소드 호출을 가로챈 뒤 Advice를 실행하는 방식입니다. 반면 AspectJ는 자바 언어의 확장으로 등장한 정통 AOP 프레임워크로, 컴파일 또는 로드타임 위빙을 통해 부가기능 코드를 삽입합니다. AspectJ는 애플리케이션 전역의 모든 객체(일반 객체, static 메소드, 생성자 호출 등)에 대해 풍부한 JoinPoint를 지원하고, 소스 또는 바이트코드 단계에서 코드에 영향을 주기 때문에 Spring에 종속되지 않고 동작합니다.
요약하자면, Spring AOP는 프록시 기반의 경량 AOP(런타임에 동적으로 동작)이고, AspectJ는 언어 수준의 강력한 AOP(컴파일 시 직접 바이트코드 변환)입니다. Spring AOP가 사용 편의성 측면에서 가볍고 부분 기능만 지원하는 반면, AspectJ는 학습과 설정이 약간 복잡하지만 더 강력하고 다양한 AOP 기능을 제공합니다.
Q2. Advice의 종류에는 어떤 것이 있으며, 각각 언제 사용되나요?
모범답안
- Advice는 부가기능 코드가 언제 실행되는지에 따라 구분됩니다. Before Advice는 조인포인트 전에 실행되어 주로 메소드 진입 전에 실행할 로직 (예: 인증/권한 체크)에 쓰입니다. After Returning Advice는 메소드가 정상 종료한 후 실행되어, 메소드 결과 처리나 후처리 (예: 로그 출력)에 사용됩니다. After Throwing Advice는 메소드 실행 중 예외가 발생했을 때 실행되어 에러 로깅이나 리소스 정리 등에 활용됩니다. After (Finally) Advice는 메소드 성공 여부와 상관없이 실행되는 것으로, 자바의 finally 블록처럼 예외 발생 여부와 무관한 사후 정리 작업 (예: 자원 해제)에 쓰입니다. Around Advice는 메소드 호출을 감싸서 전후에 모두 실행되는 것으로, ProceedingJoinPoint.proceed()를 호출하기 전후로 필요한 코드를 수행합니다. Around는 가장 강력한 Advice로, 메소드 실행 전후 동작이나 실행 자체 대체까지 가능하여 트랜잭션 처리, 성능 모니터링처럼 전 과정에 개입해야 하는 경우 사용됩니다.
Q3. 위빙(Weaving)이란 무엇이며, 언제 이루어지나요?
모범답안
위빙은 정의된 Aspect(부가기능 코드)를 애플리케이션의 타겟 객체에 삽입하는 과정을 말합니다. 쉽게 말해, 핵심 로직에 부가 로직을 결합하는 것을 위빙이라고 합니다. 위빙이 이루어지는 시점에 따라 컴파일 타임, 클래스 로드 타임, 런타임 세 가지 유형이 있습니다.
컴파일 타임 위빙은 소스 코드를 컴파일할 때 바이트코드에 Aspect를 삽입하는 것으로, AspectJ 컴파일러(ajc)를 사용할 때 일어납니다.
클래스 로드 타임 위빙(LTW)은 바이트코드를 JVM에 로드하는 시점에 에이전트를 통해 Aspect를 주입하는 방법입니다.
런타임 위빙은 실행 중 프록시 패턴 등을 이용하여 Aspect를 적용하는 방식입니다. Spring AOP는 마지막의 런타임 위빙을 사용하며, AspectJ는 주로 컴파일 타임 위빙을 사용하지만 설정에 따라 로드타임 위빙도 가능합니다. 각 방식마다 장단이 있는데, 컴파일 타임 위빙은 성능상 유리하지만 빌드 과정이 복잡해지고, 런타임 위빙은 유연하지만 약간의 호출 오버헤드가 있습니다.
Q4. Spring AOP에서 프록시(proxy)는 무엇이며 어떻게 동작하나요?
모범답안
Spring AOP에서 프록시는 AOP가 적용된 객체를 대리하는 래퍼 객체입니다. Spring은 빈(Bean)에 AOP를 적용할 때 해당 빈을 대상으로 하는 프록시 클래스를 동적으로 생성합니다. 이 프록시는 원본 빈과 동일한 인터페이스를 구현하거나 (JDK 동적 프록시) 원본 클래스를 상속받아 (CGLIB 프록시) 만들어집니다.
클라이언트가 AOP 적용 빈의 메소드를 호출하면, 실제로는 프록시의 메소드가 호출됩니다. 프록시 메소드 내부에서는 설정된 Pointcut 조건을 확인하여 해당 메소드 호출에 적용할 Advice가 있는지 검사하고, 있다면 사전에 Advice(부가기능 코드)를 실행한 뒤 원본 타겟 객체의 실제 메소드를 호출합니다.
또한 메소드 실행 후에도 After류 Advice가 있다면 추가로 실행합니다. 이렇듯 프록시는 중개자 역할을 하여, 핵심 로직 실행 전후로 부가 로직을 끼워넣는 AOP를 가능하게 합니다. 프록시 덕분에 원래 객체의 코드는 전혀 수정되지 않으며, 필요할 때에만 투명하게 부가기능이 적용됩니다. (참고로 Spring은 기본적으로 인터페이스가 있으면 JDK 동적 프록시를, 없으면 CGLIB로 클래스를 상속한 프록시를 생성합니다.)
압박 질문
Q1. AOP 도입 시 발생할 수 있는 단점이나 위험 요소는 무엇인가요?
모범답안
AOP 도입 시에는 몇 가지 주의점과 단점을 고려해야 합니다. 첫째, AOP를 남용하면 코드의 흐름이 숨겨져 디버깅이 어려워질 수 있습니다. 개발자가 보기에 호출 관계가 분명하지 않아서 버그 추적에 시간이 더 걸릴 수 있죠. 둘째, 성능 오버헤드를 고려해야 합니다. 일반적으로 프록시 호출 오버헤드는 작지만, 만약 수천 개의 빈에 아주 빈번한 Advice를 적용하면 누적되어 성능에 영향이 있을 수 있습니다. 셋째, 팀 내 이해도 부족으로 인한 문제입니다. AOP를 잘 모르는 개발자가 보면 코드에 없는 기능이 실행되어 혼란이 생길 수 있습니다. 따라서 팀원들의 AOP 개념 숙지가 중요합니다. 마지막으로, 도구 지원 이슈도 있습니다. 예를 들어 일부 APM이나 프로파일러는 프록시를 거친 호출 스택을 제대로 표시하지 못할 수 있습니다. 결론적으로 AOP는 강력하지만, 필요한 곳에 최소한으로 적용하고, 적절한 로깅이나 문서를 통해 어느 Aspect가 어떤 일을 하는지 투명성을 확보하는 것이 중요합니다.
Q2. 여러 Aspect가 동일 조인포인트에 적용될 경우 실행 순서를 어떻게 제어하나요?
모범답안
Spring AOP에서는 Aspect 또는 Advice의 우선순위(order)를 설정하여 실행 순서를 제어할 수 있습니다. 구체적으로 Aspect 클래스에 @Order 애노테이션을 사용하거나 Ordered 인터페이스를 구현함으로써 순서를 지정합니다. 값이 낮을수록 높은 우선순위를 가지며 먼저 실행됩니다. 예를 들어 @Order(1)을 부여한 Aspect가 @Order(2)보다 먼저 적용됩니다. 만약 순서를 지정하지 않으면 기본적으로 순서는 정해지지 않으며(Spring은 AspectJ의 우선순위 규칙이나 빈 등록 순서 등에 따라 결정될 수 있지만 명시적이지 않습니다), 이 경우 예측이 어려우므로 명시적인 우선순위 지정을 권장합니다. 또한 하나의 Aspect 내에서도 Advice간 순서는 종류에 따라 이미 정의되어 있습니다 (Around -> Before -> 메소드 실행 -> After류 순으로 실행). 따라서 여러 Aspect 간의 순서만 개발자가 지정해주면 됩니다.
Q3. Spring AOP에서 왜 private, final, static 메소드에는 Advice를 적용할 수 없나요?
모범답안
Spring AOP는 프록시 기반이기 때문에 프록시 객체가 오버라이드할 수 없는 대상에는 Advice를 적용할 수 없습니다. private 메소드는 클래스 내부에서만 호출되고 외부에서 보이지 않으며, 프록시가 해당 메소드를 대체(overriding)할 수 없습니다. 마찬가지로 final 메소드도 오버라이드할 수 없기에 AOP 적용이 안 됩니다.
static 메소드는 인스턴스가 아닌 클래스 레벨 호출이므로 프록시 개념과 맞지 않습니다. 반면 AspectJ처럼 바이트코드 레벨에서 위빙하는 기술은 private이나 static에도 적용 가능하지만, Spring AOP의 설계상 이러한 제한이 있습니다. 따라서 Spring AOP를 사용할 경우, 보통 public 메소드(또는 protected 메소드) 위주로 AOP를 적용하며, private 구현은 AOP대상이 되는 public 메소드에서 호출되는 방식으로 구조를 조정합니다. 이러한 제약은 프록시의 기술적 한계이지만, 한편으로는 AOP 남용을 방지하는 측면도 있습니다.
Q4. @Around Advice를 사용해 반환 값을 조작하는 경우, 원본 기능이 변경될 위험은 없나요?
모범답안
Around Advice는 조인포인트를 완전히 감싸기 때문에, 원본 메소드 호출 여부나 시점을 개발자가 제어할 수 있습니다. ProceedingJoinPoint.proceed()를 호출해야 실제 메소드가 실행되는데, 이를 조건부로 실행하거나 여러 번 실행하거나, 아예 호출하지 않을 수도 있습니다. 또한 proceed 이후 반환된 값을 가로채 가공하거나, 예외를 잡아 처리해버릴 수도 있습니다. 말씀하신 대로 이것은 원본 기능의 의미를 변경할 수 있기 때문에 신중하게 사용해야 하는 기능입니다. 일반적으로 Around Advice는 트랜잭션 처리처럼 전후에 꼭 필요한 부가기능을 추가하되, 원본 로직 자체는 변경하지 않는 경우에 주로 사용합니다. 반환 값을 조작하는 것도 캐싱처럼 부가기능 관점에서 필요한 경우 (예: 결과를 캐시에 저장하고 반환은 그대로 하는 등)로 한정해야 합니다. 만약 Around Advice로 원본의 핵심 의미를 변경하고 싶다면 그건 AOP의 올바른 사용이 아니며, 차라리 해당 로직을 리팩토링하여 명시적으로 처리하는 것이 좋습니다. 요약하면, Around Advice는 강력한 만큼 위험할 수 있으므로, 트랜잭션, 리소스 관리, 성능 측정 등의 경우에 제한적으로 사용하고, 원본 메소드의 계약(contract)을 깨트리지 않도록 주의해야 합니다.
Q5. AOP 적용 후 디버깅이 어려워질 때 어떻게 대처할 수 있나요?
모범답안
로깅, 문서화, 그리고 적절한 테스트 케이스를 통해 어느 Aspect가 언제 동작하는지 파악하는 것이 중요합니다. 또한, IDE와 AOP 디버깅 도구를 활용해 프록시와 위빙된 코드를 추적할 수 있도록 설정하는 것이 좋습니다.
출처
- https://online.wrexham.ac.uk/aspect-oriented-programming-advantages-and-disadvantages/#:~:text=,inherent%20in%20AOP%20encapsulate%20common
- https://velog.io/@geesuee/Spring-AOPAspect-Oriented-Programming%EC%99%80-%ED%94%84%EB%A1%9D%EC%8B%9C#:~:text=%ED%9A%A1%EB%8B%A8%20%EA%B4%80%EC%8B%AC%EC%82%AC%EB%A5%BC%20%ED%91%9C%ED%98%84%ED%95%9C%20%EA%B0%80%EC%9E%A5%20%ED%9D%94%ED%95%9C,%EB%B3%B4%EC%88%98%20%EB%B0%8F%20%ED%99%95%EC%9E%A5%EC%9D%84%20%EC%9A%A9%EC%9D%B4%ED%95%98%EA%B2%8C%20%ED%95%98%EB%A0%A4%EB%A9%B4
- https://stackoverflow.com/questions/15746676/logging-with-aop-in-spring#:~:text=
- https://medium.com/@yukeon97/1-aop%EB%9E%80-7678398ff074
'Spring' 카테고리의 다른 글
[Spring] Spring Cloud와 Microservices 기초 (1) | 2025.03.10 |
---|---|
[Spring] @Scheduled와 Quartz 스케줄러 (1) | 2025.02.27 |
[Spring] H2 Database & Spring Boot DevTools (0) | 2025.02.25 |
[Spring] 트랜잭션 관리(@Transactional) (0) | 2025.02.25 |
[Spring] JPA (0) | 2025.02.24 |
- Total
- Today
- Yesterday
- CS
- 운영체제
- 해시 테이블
- B+Tree
- devops
- db
- TRIE
- k8
- 우테코
- 분할 정복
- 자바
- 그리디 알고리즘
- Java
- 데이터베이스
- Spring
- i/o모델
- CPU 스케줄링
- 알고리즘
- HTTP
- 우선순위 큐
- 프리코스
- 탐색 알고리즘
- 스프링
- 백트래킹
- restful api
- Spring Boot
- 동적 프로그래밍
- 우아한 테크코스
- 자료구조
- MSA
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |