AOP로 횡단 관심사를 분리하는 설계 방식
AOP의 Aspect, Join Point, Pointcut, Advice, Weaving 개념과 Spring AOP·AspectJ의 적용 범위를 정리한다.
2026-08-14 · 최초 발행 2025-05-23
비즈니스 코드에 반복되는 공통 기능을 분리하는 방식
로깅, 보안, 트랜잭션 관리는 개별 업무 기능과는 성격이 다르지만 여러 모듈에 반복해서 들어간다. AOP(Aspect Oriented Programming)는 이런 횡단 관심사(Cross-cutting Concerns)를 별도 모듈로 분리하는 프로그래밍 패러다임이다.
AOP는 객체지향 프로그래밍(OOP)을 대체하기보다 보완한다. 비즈니스 로직과 부가 기능을 분리해 중복을 줄이고, 핵심 로직이 맡아야 할 책임에 집중할 수 있게 한다. 그 결과 코드의 응집도와 유지보수성이 높아진다.
OOP만으로는 공통 처리가 흩어지는 이유
OOP는 상속, 다형성, 캡슐화를 통해 재사용성과 확장성을 제공한다. 다만 여러 객체와 계층을 가로질러 적용되는 기능을 하나의 단위로 묶는 일은 쉽지 않다. 공통 처리를 각 클래스에 넣으면 같은 코드가 반복되고, 변경 시 수정 지점도 늘어난다.
이 구조에서는 공통 기능을 바꿀 때 여러 클래스를 함께 수정해야 한다. AOP는 이 반복을 횡단 관심사라는 별도 축으로 다룬다.
Aspect가 적용되는 지점을 선택하는 구성
Aspect
Aspect는 여러 객체에 공통 적용할 기능을 모듈화한 단위다. 로깅, 보안, 트랜잭션 관리가 대표적인 예이며, Aspect는 Advice와 Pointcut의 조합으로 구성된다.
Join Point와 Pointcut
Join Point는 Aspect를 적용할 수 있는 프로그램 실행 지점이다. 메소드 호출과 실행, 예외 발생, 필드 값 변경 등이 이에 해당한다. 지원하는 Join Point의 유형은 AOP 프레임워크마다 다르다.
Pointcut은 여러 Join Point 가운데 Advice를 실제로 적용할 위치를 고르는 표현식이다. 정규 표현식이나 프레임워크별 문법으로 정의하며, 예를 들어 service 패키지 안의 모든 메소드 호출처럼 지정할 수 있다.
Advice
Advice는 선택된 Join Point에서 실행되는 코드다. 실행 시점에 따라 다음과 같이 구분한다.
- Before: 메소드 실행 전
- After: 메소드 실행 후 (성공/실패 여부 관계없이)
- After-returning: 메소드가 정상적으로 반환된 후
- After-throwing: 메소드에서 예외 발생 시
- Around: 메소드 실행 전후 (가장 강력한 Advice)
Weaving
Weaving은 Aspect를 대상 객체와 연결해 새로운 프록시 객체를 만드는 과정이다. Aspect 코드는 소스 컴파일 시 삽입할 수 있고, JVM이 클래스를 로드할 때 삽입할 수도 있다. 프로그램 실행 중 프록시 객체를 통해 적용하는 런타임 위빙도 있다.
횡단 관심사를 별도 모듈로 두는 구조
AOP를 적용하면 비즈니스 로직에서 횡단 관심사를 떼어내 독립적으로 관리할 수 있다.
비즈니스 로직과 공통 처리가 분리되므로 코드의 가독성과 유지보수가 쉬워진다.
Spring AOP와 AspectJ의 적용 범위
Spring AOP
Spring AOP는 Spring 프레임워크가 제공하는 프록시 기반 구현체다. 메소드 실행 Join Point만 지원하며, 런타임 위빙을 사용한다. 별도의 컴파일러나 클래스 로더 없이 XML 설정 또는 어노테이션으로 Aspect를 정의할 수 있다.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethodExecution(JoinPoint joinPoint) {
System.out.println("Executing method: " + joinPoint.getSignature().getName());
}
}
AspectJ
AspectJ는 다양한 Join Point 유형을 지원하는 완전한 AOP 프레임워크다. 컴파일 시점, 클래스 로드 시점, 런타임 위빙을 모두 지원하며, 세밀한 제어를 위한 Pointcut 표현식을 제공한다. Spring AOP보다 많은 기능을 제공하지만 설정이 복잡할 수 있다.
aspect LoggingAspect {
pointcut serviceMethods() : execution(* com.example.service.*.*(..));
before() : serviceMethods() {
System.out.println("Executing method: " + thisJoinPoint.getSignature().getName());
}
}
반복 호출되는 부가 기능에 적용하는 예
로깅과 모니터링
메소드 호출 시간, 파라미터, 반환 값을 기록해 애플리케이션을 모니터링할 수 있다. 실행 시간 측정과 예외 발생 시 자동 로깅도 AOP 적용 대상이다.
@Around("execution(* com.example.service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
logger.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
return result;
}
트랜잭션 경계 관리
데이터베이스 트랜잭션의 시작, 커밋, 롤백을 자동화하면 비즈니스 로직에서 트랜잭션 관련 코드를 제거할 수 있다.
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
from.debit(amount);
to.credit(amount);
// 트랜잭션 관련 코드 없이 순수 비즈니스 로직만 구현
}
권한 확인
메소드 실행 전에 사용자 권한을 확인하고, 특정 역할을 가진 사용자에게만 접근을 허용하는 처리도 횡단 관심사로 분리할 수 있다.
@Before("execution(* com.example.admin.*.*(..))")
public void checkAdminAccess(JoinPoint joinPoint) {
if (!securityService.isAdmin()) {
throw new AccessDeniedException("Admin access required");
}
}
캐시 적용
메소드 결과를 캐싱하고 같은 파라미터의 반복 호출에 캐시된 결과를 반환하는 방식도 AOP 활용 사례다.
@Cacheable(value = "products", key = "#productId")
public Product getProductById(Long productId) {
// 데이터베이스에서 상품 조회 로직
return productRepository.findById(productId).orElse(null);
}
적용 범위를 좁혀야 하는 이유
프록시 기반 AOP는 약간의 오버헤드를 만들 수 있고, 많은 Aspect가 적용되면 성능 저하가 우려된다. 성능 критическ한 부분에 적용할 때는 성능 테스트가 필요하다.
AOP가 개입하면 실제 코드 흐름을 따라가기 어려워질 수 있다. 특히 Around Advice는 원본 메소드의 실행 흐름을 변경하므로 주의해야 하며, 익숙하지 않은 개발자에게는 학습 곡선도 생긴다.
모든 문제를 AOP로 풀려 해서는 안 된다. 핵심 비즈니스 로직과 직접 연결된 기능은 일반 OOP 방식으로 구현하고, 여러 모듈에 반복되는 횡단 관심사에 한정해 적용하는 편이 적절하다. OOP와 AOP를 상호 보완적으로 사용하면 코드 중복을 줄이면서도 애플리케이션 아키텍처의 품질을 높일 수 있다.