디자인 패턴 & Spring AOP
한 줄 소개 — "모든 메서드에 로그·트레이스 를 남기고 싶은데, 원본 코드는 건드리고 싶지 않다" 는 문제를 GoF 디자인 패턴으로 풀어 나가는 여정이다. 템플릿 메서드 · 전략 · 프록시 · 데코레이터 패턴에서 출발해 JDK 동적 프록시 → Spring ProxyFactory → BeanPostProcessor → @Aspect AOP 로 추상화가 올라가는 과정을 보면, Spring AOP 가 결국 프록시 패턴의 자동화 라는 게 보인다. (김영한님 스프링 고급편의 GoF 부분 + 직접 구현)
1 · 문제 — 횡단 관심사의 중복
모든 컨트롤러·서비스·리포지토리 메서드에 "시작 로그 → 실행 → 종료 로그(+예외 로그)" 를 넣고 싶다. 그런데 이 로깅·트레이스 는 비즈니스 로직과 무관한 횡단 관심사(cross-cutting concern) 다. 메서드마다 같은 코드를 복붙하면 — 핵심 로직이 부가 코드에 묻히고, 수정 한 번에 수백 곳을 고쳐야 한다.
목표 — 원본(핵심 로직)을 전혀 건드리지 않고 부가 기능(로그)을 입힌다. 이 한 문장이 아래 모든 패턴의 동기다.
먼저 "변하는 부분(핵심 로직)"과 "변하지 않는 부분(로그 골격)"을 분리하는 패턴들이 있다 — 템플릿 메서드(상속으로 변하는 부분만 override), 전략 패턴(변하는 부분을 인터페이스로 주입), 템플릿 콜백(전략을 람다로 그때그때 전달). 하지만 이들은 원본 코드를 수정해야 적용된다는 한계가 있다. 그래서 프록시 로 넘어간다.
2 · 프록시 & 데코레이터 패턴
프록시(Proxy) 는 원본(Real Subject)과 같은 인터페이스 를 구현한 대리자다. 클라이언트는 프록시를 원본인 줄 알고 호출하고, 프록시는 부가 기능(로그)을 수행한 뒤 원본에 위임한다. 부가 기능에 초점을 맞추면 데코레이터(Decorator) 패턴이라 부른다 — 구조는 같다.
하지만 이렇게 손으로 만들면 — 대상 클래스마다 프록시 클래스를 하나씩 만들어야 한다(인터페이스 기반 프록시, 구체 클래스 기반 프록시 모두). 클래스가 수십 개면 프록시도 수십 개. 그래서 동적 프록시 가 등장한다.
3 · JDK 동적 프록시 (Dynamic Proxy)
동적 프록시 는 프록시 클래스를 개발자가 만들지 않고 런타임에 자동 생성한다. 부가 기능 로직은 InvocationHandler 하나에만 작성하면, 어떤 인터페이스든 그 핸들러를 공유하는 프록시가 만들어진다. (인터페이스가 없으면 CGLIB 로 구체 클래스를 상속한 프록시를 만든다.)
// 부가 기능(로그)을 InvocationHandler 한 곳에만 작성 → 모든 프록시가 공유
public class LogTraceBasicHandler implements InvocationHandler {
private final Object target; // 원본
private final LogTrace logTrace;
public LogTraceBasicHandler(Object target, LogTrace logTrace) {
this.target = target; this.logTrace = logTrace;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TraceStatus status = null;
try {
// 메서드 메타정보로 "OrderController.request()" 메시지 생성
String message = method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()";
status = logTrace.begin(message); // 부가 기능 (전)
Object result = method.invoke(target, args); // 원본 로직 위임
logTrace.end(status); // 부가 기능 (후)
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
// Proxy.newProxyInstance(loader, interfaces, new LogTraceBasicHandler(target, logTrace))4 · Spring ProxyFactory + Advisor
스프링은 "인터페이스면 JDK 동적 프록시, 아니면 CGLIB" 를 ProxyFactory 가 알아서 선택하게 추상화했다. 그리고 부가 기능과 적용 대상을 Advisor 로 묶는다.
- Advice — 부가 기능 그 자체(로그 begin/end).
InvocationHandler와 비슷하지만 더 추상화됨. - Pointcut — 부가 기능을 어디에 적용할지(어떤 클래스·메서드).
- Advisor = Advice + Pointcut (하나의 부가기능 + 적용 대상 묶음).
5 · 자동 프록시 — BeanPostProcessor
아직 남은 문제 — 설정 클래스에서 빈마다 ProxyFactory 로 프록시를 만들어 등록 하는 코드가 반복된다. BeanPostProcessor 는 스프링 컨테이너가 빈을 등록하기 직전에 가로채, 원본 빈을 프록시로 바꿔치기 할 수 있게 해 준다. 스프링이 제공하는 자동 프록시 생성기(AutoProxyCreator) 가 바로 이 빈 후처리기로, 등록된 Advisor 들을 보고 Pointcut 에 걸리는 빈을 자동으로 프록시화한다.
6 · @Aspect — Spring AOP
마지막 추상화. @Aspect 로 "부가 기능(@Around 안의 로직)과 적용 대상(execution(...) 포인트컷)" 을 한 클래스에 선언하면, 스프링이 이를 Advisor 로 변환 하고 자동 프록시 생성기가 알아서 프록시를 적용한다. 개발자는 더 이상 프록시·핸들러·팩토리를 신경 쓰지 않는다.
@Aspect // 스프링이 이 클래스를 보고 Advisor 로 변환해 등록
public class LogTraceAspect {
private final LogTrace logTrace;
public LogTraceAspect(LogTrace logTrace) { this.logTrace = logTrace; }
@Around("execution(* design.pattern.proxy.app..*(..))") // Pointcut — 어디에
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { // Advice — 무엇을
TraceStatus status = null;
try {
String message = joinPoint.getSignature().toShortString();
status = logTrace.begin(message); // 부가 기능 (전)
Object result = joinPoint.proceed(); // 원본 로직 실행
logTrace.end(status); // 부가 기능 (후)
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}7 · 보너스 — 책임 연쇄 패턴 (Chain of Responsibility)
요청을 여러 처리기(필터)가 사슬처럼 이어서 처리하고, 각자 "내가 처리할지 / 다음으로 넘길지" 결정하는 패턴. 서블릿 필터 · 스프링 인터셉터 · API 게이트웨이 필터 체인 이 정확히 이 구조다(앞서 정리한 게이트웨이의 Pre/Post 필터도 같은 원리).
public interface ChainFilter {
void handle(Request req, ChainFilter next); // 처리 후 next 로 위임
}
public class Filter1 implements ChainFilter {
public void handle(Request req, ChainFilter next) {
// ... 인증/로깅 등 처리 ...
next.handle(req, /* 다음 필터 */); // 다음 책임자에게 전달
}
}
// Filter1 → Filter2 → Filter3 … 사슬로 연결📓 2022–2023년 제가 김영한님의 스프링 고급편(GoF 디자인 패턴 부분)을 공부하며 직접 구현하고 정리한 노트입니다 · 구현 GitHub · DesignPattern ↗ · 강의 코드 SpringAdvanced ↗