🎯 F-lab Java 8-9주차 통합 학습 커리큘럼

8-9주차 자료의 모든 토픽을 두 주에 걸쳐 정리한 학습 경로.
1) 8주차 — 프록시의 진화 (AOP가 필요한 이유, 디자인 패턴, 동적 프록시, ProxyFactory)
2) 9주차 — Spring AOP 실전 (자동 프록시, @Aspect, AOP 용어, @Transactional 함정, 트랜잭션 전파)

7주차에서 @Transactional의 프록시 패턴을 맛봤다면, 8-9주차는 그 프록시가 어떻게 만들어지고 적용되는지 의 모든 메커니즘을 파헤친다.

김영한의 스프링 핵심 원리 - 고급편 전체 흐름이 압축되어 있다. F-lab 자바 커리큘럼의 하이라이트 이자 가장 분량이 많은 주차다.


📊 학습 경로 한눈에 보기

[Part A — 8주차: 프록시의 진화]
  [Phase 1] AOP 입문과 동기
     ↓
  [Phase 2] 디자인 패턴의 진화 — 템플릿 메서드 → 전략 패턴
     ↓
  [Phase 3] 콜백과 프록시의 만남 — 템플릿 콜백 → 프록시 개념
     ↓
  [Phase 4] 프록시 패턴 vs 데코레이터 패턴
     ↓
  [Phase 5] 동적 프록시 기술 — Reflection / JDK / CGLIB
     ↓
  [Phase 6] ProxyFactory — 스프링의 통합 추상화 ◄ 8주차 정점

[Part B — 9주차: Spring AOP 실전]
  [Phase 7] 빈 후처리기와 자동 프록시 생성기
     ↓
  [Phase 8] @Aspect와 AOP 용어 완전 정리
     ↓
  [Phase 9] Spring AOP 실전 구현 패턴
     ↓
  [Phase 10] @Transactional의 함정과 트랜잭션 전파 ◄ 9주차 정점

총 10 Phase × 35 Unit (5·6주차의 합과 비슷한 분량)

🔗 1~9주차 흐름 정리

주차주제핵심 변화
1주차OOP·JVM·GC·컬렉션·I/O 개론자바 큰 그림
2주차JVM 내부·바이트코드·G1 GC"어떻게 돌아가나"
3주차컬렉션·제네릭·함수형자바 표현력
4주차멀티스레딩·동시성·Executor동시성 정복
5주차Atomic + Spring IoC/DI 입문자바 → Spring 다리
6주차테스트 + 웹 인프라 + DB 접근 진화Spring 실전 환경
7주차JPA/ORM + 트랜잭션 추상화DB 추상화의 정점
8주차 (지금)프록시의 진화AOP 메커니즘 이해
9주차 (지금)Spring AOP 실전 + 트랜잭션 전파AOP 실전 활용

🗓️ 권장 학습 일정 (압축 14일)

DayPhase학습 목표
Week 1
1일차Phase 1AOP 동기, 로그 추적기, ThreadLocal
2일차Phase 2템플릿 메서드 + 전략 패턴
3일차Phase 3템플릿 콜백 패턴, 프록시 개념
4일차Phase 4프록시 vs 데코레이터
5일차Phase 5Reflection + JDK 동적 프록시 + CGLIB
6-7일차Phase 6ProxyFactory + Advice + Pointcut + Advisor (★ 8주차 정점)
Week 2
8일차Phase 7빈 후처리기 + 자동 프록시 생성기
9일차Phase 8@Aspect + AOP 용어
10일차Phase 9Spring AOP 실전 패턴
11-12일차Phase 10@Transactional 함정 + 트랜잭션 전파 (★ 9주차 정점)
13-14일차종합 자기 점검 + 실습전체 정리

여유 일정 (21일): 각 정점 Phase에 +2일씩, 그리고 9-섹션 마스터 프롬프트로 핵심 Unit을 깊이 파는 시간 확보.


🌟 Part A — 8주차: 프록시의 진화

📚 Phase 1 — AOP 입문과 동기

목표: "왜 AOP가 필요한가"를 코드의 고통으로 직접 이해한다. 7주차에서 본 @Transactional의 정체를 더 깊이 알기 위한 출발점.

Unit 1.1 — AOP란 무엇인가 (관점 지향 프로그래밍)

선수 지식: 7주차 Phase 7 (@Transactional)

핵심 개념

AOP (Aspect Oriented Programming) = 관점 지향 프로그래밍

핵심 통찰:

  • 어떤 로직을 핵심 관점 + 부가 관점 으로 나눠서 본다
  • 각 관점을 별도로 모듈화

핵심 vs 부가:

  • 핵심 관점: 비즈니스 로직 (주문 처리, 회원 가입 등)
  • 부가 관점: 로깅, 트랜잭션, DB 연결, 보안, 캐싱 등

흩어진 관심사 (Crosscutting Concerns):

  • 여러 클래스에 반복적으로 등장하는 코드
  • OOP만으로는 깔끔히 처리하기 어려움
  • → AOP의 등장 이유

자기 점검

  • 7주차의 @Transactional이 이 정의의 어디에 해당하는가?
  • "흩어진 관심사"의 실제 사례 3가지를 들어보라

Unit 1.2 — 로그 추적기로 보는 흩어진 관심사

선수 지식: Unit 1.1

핵심 시나리오

로그 추적기란?:

  • 메서드 호출 시 자동으로 로그 남기기
  • 호출 깊이, 실행 시간, 예외까지 추적

예시 출력:

정상 요청
[796bccd9] OrderController.request()
[796bccd9] |-->OrderService.orderItem()
[796bccd9] | |-->OrderRepository.save()
[796bccd9] | |<--OrderRepository.save() time=1004ms
[796bccd9] |<--OrderService.orderItem() time=1014ms
[796bccd9] OrderController.request() time=1016ms

예외 발생
[b7119f27] OrderController.request()
[b7119f27] |-->OrderService.orderItem()
[b7119f27] | |<X-OrderService.orderItem() time=10ms ex=...

문제:

  • 모든 메서드에 try-catch + 로그 시작/종료 코드 추가 필요
  • "배보다 배꼽이 더 큰" 상황
  • 100개 메서드면 100군데 수정

자기 점검

  • 로그 추적기의 어떤 부분이 "변하지 않는 것"인가?
  • 어떤 부분이 "변하는 것"인가?

Unit 1.3 — ThreadLocal — 싱글톤 환경의 동시성 해결

선수 지식: 4주차 Phase 4 (synchronized), 5주차 Phase 8 (싱글톤 빈)

핵심 개념

문제 상황:

  • 로그 추적기 = 싱글톤 빈
  • 여러 요청이 동시에 호출 → 같은 인스턴스의 상태를 동시 변경
  • → 트랜잭션 ID, 호출 깊이 등이 서로 섞임

ThreadLocal의 해결:

  • 각 스레드마다 독립적인 저장소 제공
  • 다른 스레드와 데이터 공유 안 함
  • synchronized 없이 안전
ThreadLocal<String> threadLocal = new ThreadLocal<>();

threadLocal.set("Thread-1 데이터");  // 이 스레드만 보임
String value = threadLocal.get();    // 다른 스레드는 다른 값

⚠️ 치명적 함정 — remove() 필수:

  • 스레드 풀 환경에서 스레드가 재사용
  • ThreadLocal 값이 남아있으면 → 다음 요청에 이전 사용자의 데이터 노출
  • 반드시 finally에서 remove()
try {
    threadLocal.set(data);
    // 작업
} finally {
    threadLocal.remove();  // ✅ 필수!
}

자기 점검

  • ThreadLocal이 멀티스레드를 "순차적"으로 만드는가? (힌트: NO)
  • remove()를 깜박하면 어떤 보안 사고가? (힌트: 사용자 정보 유출)

📚 Phase 2 — 디자인 패턴의 진화

목표: "변하는 것과 변하지 않는 것을 분리"라는 좋은 설계의 원칙을 두 가지 패턴(템플릿 메서드 → 전략)을 통해 코드로 익힌다.

Unit 2.1 — 변하는 것과 변하지 않는 것의 분리

선수 지식: Phase 1, 5주차 Phase 5

핵심 원칙

"좋은 설계는 변하는 것과 변하지 않는 것을 분리하는 것이다"

예시 — 시간 측정 로직:

private void logic1() {
    long startTime = System.currentTimeMillis();  // ← 변하지 않음
    log.info("비즈니스 로직1 실행");                 // ← 변함
    long endTime = System.currentTimeMillis();    // ← 변하지 않음
    log.info("resultTime={}", endTime - startTime); // ← 변하지 않음
}

private void logic2() {
    long startTime = System.currentTimeMillis();  // ← 변하지 않음
    log.info("비즈니스 로직2 실행");                 // ← 변함
    long endTime = System.currentTimeMillis();    // ← 변하지 않음
    log.info("resultTime={}", endTime - startTime); // ← 변하지 않음
}

시간 측정은 같은데 비즈니스 로직만 다름

자기 점검

  • 5주차 토비의 스프링에서 본 "관심사 분리"와 같은 원칙인가?
  • "변하는 부분"을 분리하는 가장 단순한 방법은? (힌트: 메서드 추출, 그러나 한계 있음)

Unit 2.2 — 템플릿 메서드 패턴

선수 지식: Unit 2.1, 5주차 Unit 5.1

핵심 개념

"슈퍼클래스에 변하지 않는 템플릿을 두고, 변하는 부분을 자식 클래스에 두는 패턴"

public abstract class AbstractTemplate {
    public void execute() {
        long startTime = System.currentTimeMillis();
        call();  // ← 변하는 부분 (자식이 구현)
        long endTime = System.currentTimeMillis();
        log.info("resultTime={}", endTime - startTime);
    }
    
    protected abstract void call();
}

public class SubClassLogic1 extends AbstractTemplate {
    @Override
    protected void call() {
        log.info("비즈니스 로직1 실행");
    }
}

익명 내부 클래스 활용:

AbstractTemplate template = new AbstractTemplate() {
    @Override
    protected void call() {
        log.info("비즈니스 로직1 실행");
    }
};
template.execute();

자기 점검

  • 5주차에서 토비의 스프링이 같은 패턴을 어떻게 사용했는가? (힌트: UserDao + getConnection)
  • 자식 클래스가 부모의 어떤 기능을 사용하는가?

Unit 2.3 — 템플릿 메서드 패턴의 한계 (상속의 결합)

선수 지식: Unit 2.2

핵심 한계

상속의 강한 결합:

  • 자식 클래스 입장에서 부모의 기능을 사용하지도 않는데 상속해야 함
  • 자식 코드 extends AbstractTemplate부모에 강하게 의존
  • 부모 변경 시 자식들에게 영향 전파

복잡함:

  • 작업마다 별도 클래스 또는 익명 내부 클래스 필요
  • 코드량 증가

해결의 방향:

  • 상속(extends) 대신 합성(composition)
  • = 전략 패턴

자기 점검

  • "Composition over Inheritance" 원칙의 의미는?
  • 5주차 Phase 5.3의 "두 패턴의 한계"와 같은 맥락인가?

Unit 2.4 — 전략 패턴 (위임으로 해결)

선수 지식: Unit 2.3, 5주차 Unit 6.3

핵심 개념

"변하지 않는 부분을 Context에, 변하는 부분을 Strategy 인터페이스로"

상속 대신 위임:

public interface Strategy {
    void call();
}

public class StrategyLogic1 implements Strategy {
    @Override
    public void call() {
        log.info("비즈니스 로직1 실행");
    }
}

public class ContextV1 {
    private Strategy strategy;
    
    public ContextV1(Strategy strategy) {
        this.strategy = strategy;  // 위임
    }
    
    public void execute() {
        long startTime = System.currentTimeMillis();
        strategy.call();  // ← 위임
        long endTime = System.currentTimeMillis();
        log.info("resultTime={}", endTime - startTime);
    }
}

람다로 더 간결하게:

ContextV1 context = new ContextV1(() -> log.info("비즈니스 로직1"));
context.execute();

핵심 통찰:

"Context는 Strategy 인터페이스에만 의존" → Spring의 DI와 같은 사상

자기 점검

  • 5주차의 ConnectionMaker → UserDao 패턴과 동일한가?
  • 람다가 가능한 이유는? (힌트: 함수형 인터페이스)

📚 Phase 3 — 콜백과 프록시의 만남

목표: 전략 패턴의 한계를 콜백 패턴으로 극복하고, 그래도 남는 한계를 프록시로 해결하기 위한 출발점에 선다.

Unit 3.1 — 실행 시점 파라미터 전달 (전략 패턴 V2)

선수 지식: Unit 2.4

핵심 개념

Context V1의 한계:

  • Context와 Strategy를 미리 조립 후 사용
  • 전략 변경하려면 새 Context 인스턴스 필요
  • 싱글톤 환경에서는 더 까다로움

Context V2 — 파라미터로 받기:

public class ContextV2 {
    public void execute(Strategy strategy) {  // ← 매번 다른 전략
        long startTime = System.currentTimeMillis();
        strategy.call();
        long endTime = System.currentTimeMillis();
        log.info("resultTime={}", endTime - startTime);
    }
}

// 사용
ContextV2 context = new ContextV2();
context.execute(() -> log.info("비즈니스 로직1"));
context.execute(() -> log.info("비즈니스 로직2"));

선 조립 후 실행 vs 실행 시점 전달:

  • 선 조립: 의존관계 설정 (Spring의 DI 방식)
  • 실행 시점: 단순히 코드 조각 실행 시 더 적합

자기 점검

  • V1과 V2 중 람다를 쓰기 더 자연스러운 쪽은?
  • 자바 8 이전에는 V2가 비현실적이었던 이유는? (힌트: 익명 내부 클래스의 verbosity)

Unit 3.2 — 템플릿 콜백 패턴 (Spring의 XxxTemplate)

선수 지식: Unit 3.1

핵심 개념

용어 매핑:

  • ContextTemplate
  • StrategyCallback

템플릿 콜백 패턴은 GOF가 아닌 스프링 전용 용어:

  • 전략 패턴 V2에서 템플릿과 콜백을 강조한 형태

Spring의 XxxTemplate 시리즈 ⭐ :

  • JdbcTemplate (6주차)
  • RestTemplate
  • TransactionTemplate
  • RedisTemplate

이름에 "Template"이 붙으면 이 패턴

콜백(Callback)의 정의:

"다른 코드의 인수로서 넘겨주는 실행 가능한 코드"

"코드가 호출(call)되는데, 코드를 넘겨준 곳의 뒤(back)에서 실행됨"

template.execute(() -> log.info("비즈니스 로직1"));
//                ↑ 이 람다가 콜백
//                template 안에서 "나중에" 실행됨

자기 점검

  • 6주차의 JdbcTemplate.query() 의 RowMapper 인자가 콜백인가?
  • 자바스크립트의 콜백과 같은 개념인가?

Unit 3.3 — 프록시 개념과 조건

선수 지식: Phase 3

핵심 한계 직시

지금까지의 모든 방법(템플릿 메서드, 전략 패턴, 템플릿 콜백)의 공통 한계:

"결국 원본 코드를 수정해야 한다"

100개 메서드면 100군데 수정. 이걸 안 하려면?

프록시(Proxy)의 등장:

  • "대리인"이라는 뜻
  • 클라이언트와 실제 객체 사이에 중간 객체 배치
  • 클라이언트는 프록시를 호출
  • 프록시가 부가 기능을 수행하고 실제 객체에 위임

프록시가 되기 위한 조건:

  • 클라이언트가 서버 호출인지 프록시 호출인지 몰라야
  • 서버와 프록시는 같은 인터페이스 구현
  • 클라이언트 코드 변경 없이 프록시 주입 가능
[Client] → [Server]    ──변경──>    [Client] → [Proxy] → [Server]
       (DI 활용)                              (코드 변경 X)

프록시의 주요 기능:

  • 접근 제어: 권한 차단, 캐싱, 지연 로딩
  • 부가 기능 추가: 요청/응답 변형, 로그, 시간 측정

자기 점검

  • DI가 프록시 적용에 어떻게 도움을 주는가?
  • 클라이언트 코드는 프록시 주입을 알 수 있는가?

📚 Phase 4 — 프록시 패턴 vs 데코레이터 패턴

목표: 같은 모양의 두 패턴을 의도 로 구분하는 법을 익힌다.

Unit 4.1 — 프록시 패턴 (접근 제어 — 캐시 예제)

선수 지식: Unit 3.3

핵심 개념

프록시 패턴 = 접근 제어 목적

캐시 예제:

public class CacheProxy implements Subject {
    private Subject target;
    private String cacheValue;
    
    public CacheProxy(Subject target) {
        this.target = target;
    }
    
    @Override
    public String operation() {
        if (cacheValue == null) {
            cacheValue = target.operation();  // 처음만 실제 호출
        }
        return cacheValue;  // 두 번째부터 캐시 반환
    }
}

효과:

  • 첫 호출: 1초 (실제 객체 호출)
  • 두 번째 호출: 0초 (캐시)
  • 클라이언트 코드 수정 0줄

자기 점검

  • "캐시는 접근 제어인가?"의 답은?
  • JPA의 1차 캐시도 같은 패턴인가?

Unit 4.2 — 데코레이터 패턴 (부가 기능)

선수 지식: Unit 4.1

핵심 개념

데코레이터 패턴 = 부가 기능 추가 목적

예시 — 메시지 꾸미기 + 시간 측정:

public class MessageDecorator implements Component {
    private Component component;
    
    @Override
    public String operation() {
        String result = component.operation();
        return "*****" + result + "*****";  // 꾸며줌
    }
}

public class TimeDecorator implements Component {
    private Component component;
    
    @Override
    public String operation() {
        long start = System.currentTimeMillis();
        String result = component.operation();
        log.info("time={}ms", System.currentTimeMillis() - start);
        return result;
    }
}

중첩 사용:

Component real = new RealComponent();
Component message = new MessageDecorator(real);
Component time = new TimeDecorator(message);
client.execute(time);
// 호출 흐름: time → message → real → "data"
//          → "*****data*****" → 시간 로그

자기 점검

  • 데코레이터를 무한히 중첩할 수 있는가?
  • 자바 I/O의 BufferedReader, InputStreamReader도 이 패턴인가? (힌트: YES)

Unit 4.3 — 의도로 구분하기

선수 지식: Unit 4.1, 4.2

핵심 통찰

모양은 같다, 의도가 다르다:

프록시 패턴데코레이터 패턴
의도접근 제어기능 추가
사례캐시, 권한, 지연 로딩로깅, 메시지 꾸미기, 시간 측정
클라이언트 인지보통 모름알 수도 있음
중첩 사용드물음흔함

구분 기준:

"프록시가 접근 제어 가 목적이면 프록시 패턴, 새 기능 추가 가 목적이면 데코레이터 패턴"

자기 점검

  • @Transactional 프록시는 어느 쪽에 가까운가?
  • 같은 코드가 두 패턴으로 동시에 분류될 수 있는가?

📚 Phase 5 — 동적 프록시 기술 (Reflection / JDK / CGLIB)

목표: "프록시 클래스를 100개 만들어야 하는 문제"를 자바의 동적 기술로 해결한다.

Unit 5.1 — 수동 프록시의 문제와 리플렉션

선수 지식: Phase 4

핵심 문제

수동 프록시의 한계:

  • 적용 대상이 100개면 프록시 클래스도 100개
  • 모든 프록시의 코드가 거의 같음 (호출 대상만 차이)
  • 유지보수 지옥

해결의 출발점 — 리플렉션:

리플렉션 사용 전:

target.callA();  // 메서드 이름이 코드에 박혀있음
target.callB();  // 다른 메서드는 다른 호출 코드

리플렉션 사용 후:

Method methodA = classHello.getMethod("callA");
Method methodB = classHello.getMethod("callB");
dynamicCall(methodA, target);
dynamicCall(methodB, target);

private void dynamicCall(Method method, Object target) {
    method.invoke(target);  // 어떤 메서드든 호출 가능
}

리플렉션의 장단점:

  • ✅ 메타 정보 기반 동적 호출 → 매우 유연
  • ❌ 런타임 동작 → 컴파일 시점 오류 검출 불가

일반 코드에서 쓰지 말 것 — 프레임워크 개발용

자기 점검

  • 리플렉션이 컴파일 시점 안전성을 깨는 이유는?
  • JPA가 엔티티의 기본 생성자를 요구하는 이유와 관련이 있는가? (힌트: YES)

Unit 5.2 — JDK 동적 프록시 (인터페이스 기반)

선수 지식: Unit 5.1

핵심 개념

"프록시 클래스를 런타임에 자동 생성"

전제 조건: 인터페이스 필수

InvocationHandler 인터페이스:

public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

구현 예시 — 시간 측정 프록시:

public class TimeInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public TimeInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = method.invoke(target, args);  // 실제 호출
        log.info("time={}ms", System.currentTimeMillis() - start);
        return result;
    }
}

프록시 생성:

AInterface target = new AImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);

AInterface proxy = (AInterface) Proxy.newProxyInstance(
    AInterface.class.getClassLoader(),
    new Class[]{AInterface.class},
    handler
);

proxy.call();  // 동적 프록시가 가로챔
// 출력: proxyClass=class com.sun.proxy.$Proxy1

핵심 통찰:

  • AImpl 100개여도 InvocationHandler 1개로 충분
  • 프록시 클래스는 JDK가 동적 생성 ($Proxy1, $Proxy2...)

자기 점검

  • JDK 동적 프록시가 인터페이스를 요구하는 이유는? (힌트: Proxy.newProxyInstance가 받는 매개변수)
  • 5주차의 어떤 디자인 패턴이 여기 적용됐는가?

Unit 5.3 — CGLIB (구체 클래스 기반)

선수 지식: Unit 5.2

핵심 개념

JDK 동적 프록시의 한계:

  • 인터페이스가 반드시 필요
  • 구체 클래스만 있으면 사용 불가

CGLIB (Code Generator Library):

  • 바이트코드 조작 으로 동적 클래스 생성
  • 인터페이스 없어도 OK — 구체 클래스 상속
  • Spring 프레임워크 내부에 포함되어 있음

비교:

JDK 동적 프록시CGLIB
전제인터페이스 필요구체 클래스만으로 OK
방식인터페이스 구현클래스 상속
핸들러InvocationHandlerMethodInterceptor
라이브러리JDK 표준외부 (Spring 포함)

실무에서 직접 사용?:

  • 거의 안 함
  • Spring의 ProxyFactory 가 둘을 통합 (다음 Phase)

자기 점검

  • CGLIB가 구체 클래스로도 작동하는 이유는? (힌트: 상속)
  • final 클래스에 CGLIB을 적용할 수 있는가? (힌트: NO, 상속 불가)

📚 Phase 6 — ProxyFactory: 스프링의 통합 추상화 (★ 8주차 정점)

목표: JDK 동적 프록시와 CGLIB의 분기를 Spring이 어떻게 통합했는지, 그리고 그 위에 어떻게 Pointcut/Advice/Advisor 추상화를 쌓았는지를 본다.

Unit 6.1 — ProxyFactory — JDK/CGLIB 통합

선수 지식: Phase 5

핵심 개념

문제:

  • 인터페이스 있으면 JDK 동적 프록시
  • 구체 클래스만 있으면 CGLIB
  • 두 기술을 매번 선택해야 함

ProxyFactory의 해결:

ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());

ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
// → 자동으로 JDK 동적 프록시 또는 CGLIB 선택

자동 선택 규칙:

  • 인터페이스 있음 → JDK 동적 프록시
  • 구체 클래스만 → CGLIB
  • 옵션으로 강제 가능 (proxyTargetClass=true → 강제 CGLIB)

Spring Boot의 기본 설정:

Spring Boot는 AOP 적용 시 기본적으로 proxyTargetClass=true항상 CGLIB

자기 점검

  • Spring Boot가 CGLIB을 기본으로 선택한 이유는? (힌트: 인터페이스 없는 클래스도 일관성 있게)
  • ProxyFactory가 5주차의 어떤 디자인 패턴인가? (힌트: 팩토리)

Unit 6.2 — Advice 추상화

선수 지식: Unit 6.1

핵심 개념

문제:

  • JDK는 InvocationHandler, CGLIB는 MethodInterceptor
  • 두 인터페이스를 둘 다 구현하면 중복

Advice의 등장:

  • 둘을 개념적으로 추상화 한 인터페이스
  • 개발자는 Advice만 구현하면 됨
  • ProxyFactory가 내부에서 둘을 변환
// org.aopalliance.intercept.MethodInterceptor 구현
public class TimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();  // 실제 호출
        log.info("time={}ms", System.currentTimeMillis() - start);
        return result;
    }
}

⚠️ 패키지 주의:

  • org.aopalliance.intercept.MethodInterceptor (Advice용 ✅)
  • org.springframework.cglib.proxy.MethodInterceptor (CGLIB 직접 사용용)

상속 구조:

Advice ← Interceptor ← MethodInterceptor (org.aopalliance)

자기 점검

  • "개발자는 프록시 생성은 ProxyFactory에, 로직은 Advice에" 의미는?
  • 6주차 JdbcTemplate의 RowMapper와 Advice의 공통점은? (힌트: 함수형 인터페이스 추상화)

Unit 6.3 — Pointcut + Advice = Advisor

선수 지식: Unit 6.2

핵심 개념

3대 개념 정리 ⭐ :

용어의미
Pointcut"어디에" 적용할지 (필터링)
Advice"어떤 로직"을 적용할지 (부가 기능)
Advisor"Pointcut + Advice" (어디에 + 어떤 로직)

Pointcut 사용 예시:

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("save");  // save 메서드만 매칭

DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());

proxyFactory.addAdvisor(advisor);
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

proxy.save();   // ✅ Advice 적용됨
proxy.find();   // ❌ Advice 안 됨

스프링 기본 Pointcut 종류:

  • NameMatchMethodPointcut: 메서드 이름 매칭
  • JdkRegexpMethodPointcut: 정규표현식
  • TruePointcut: 항상 참
  • AnnotationMatchingPointcut: 어노테이션 매칭
  • AspectJExpressionPointcut: AspectJ 표현식 ⭐ (실무 표준)

자기 점검

  • @Transactional의 Pointcut은 어떤 표현식이 될 수 있을까?
  • ProxyFactory에 Advisor 없이 Advice만 등록하면? (힌트: 모든 메서드에 적용)

Unit 6.4 — 하나의 프록시, 여러 어드바이저

선수 지식: Unit 6.3

핵심 개념

여러 부가 기능을 한 객체에:

ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvisor(advisor2);  // 먼저 등록
proxyFactory.addAdvisor(advisor1);  // 나중 등록

ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
proxy.save();
// 호출 흐름: proxy → advisor2 → advisor1 → target

중요 — Spring AOP 최적화 ⭐ :

"스프링 AOP는 target 마다 하나의 프록시만 생성한다"

여러 AOP가 동시 적용되어도 → 1개 프록시 + N개 어드바이저

의미:

  • 메모리 효율
  • 성능 최적화
  • 호출 순서는 등록 순서

자기 점검

  • 만약 어드바이저마다 별도 프록시를 만든다면 100개 AOP 적용 시 어떻게 될까?
  • 어드바이저의 순서를 명시적으로 제어하려면? (힌트: @Order)

🌟 Part B — 9주차: Spring AOP 실전

📚 Phase 7 — 빈 후처리기와 자동 프록시 생성기

목표: 컴포넌트 스캔된 빈에도 프록시를 적용하는 메커니즘을 이해한다.

Unit 7.1 — 수동 등록의 한계 (컴포넌트 스캔 문제)

선수 지식: Phase 6

두 가지 큰 문제

문제 1 — 너무 많은 설정:

  • 빈 100개에 프록시 적용 → 100개 프록시 생성 코드
  • "설정 지옥"

문제 2 — 컴포넌트 스캔:

  • @Service, @Repository 등으로 자동 등록된 빈
  • 이미 실제 객체 가 컨테이너에 등록됨
  • 프록시로 교체할 방법이 없음

빈 등록을 가로채서 프록시로 바꿔치기 해야 한다

자기 점검

  • 5주차의 @Component 컴포넌트 스캔 메커니즘과 충돌하는가?
  • "빈 후처리기"라는 이름에서 "후"는 무엇 후일까?

Unit 7.2 — BeanPostProcessor — 빈 조작 후킹

선수 지식: Unit 7.1

핵심 개념

BeanPostProcessor 인터페이스:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName);
    Object postProcessAfterInitialization(Object bean, String beanName);
}

예시 — A를 B로 바꿔치기:

public class AToBPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof A) {
            return new B();  // A 대신 B 반환
        }
        return bean;
    }
}

// 결과
B b = applicationContext.getBean("beanA", B.class);  // beanA 이름으로 B 반환!

의의:

  • 빈을 컨테이너 등록 직전에 가로채기
  • 객체를 조작/교체 가능
  • 빈을 프록시로 바꿔치기 가능

자기 점검

  • 일반적인 빈 등록 흐름에서 어떤 단계에 끼어드는가?
  • 이게 가능한 이유는? (힌트: ApplicationContext의 라이프사이클)

Unit 7.3 — AnnotationAwareAspectJAutoProxyCreator

선수 지식: Unit 7.2

핵심 개념

스프링이 제공하는 자동 프록시 생성기:

  • 의존성: spring-boot-starter-aop
  • Spring Boot가 자동 등록
  • = 빈 후처리기

동작:
1. 스프링 빈으로 등록된 모든 Advisor 들을 자동으로 찾음
2. 각 빈에 대해 Advisor의 Pointcut으로 매칭 검사
3. 매칭되면 해당 빈을 프록시로 교체

@Bean
public Advisor advisor3(LogTrace logTrace) {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))");
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    return new DefaultPointcutAdvisor(pointcut, advice);
}

이름의 의미:

  • AnnotationAwareAspectJAutoProxyCreator
  • AutoProxyCreator: 자동 프록시 생성
  • AspectJ: AspectJ 표현식 지원
  • AnnotationAware: 어노테이션 인식 (@Aspect)

자기 점검

  • "스프링 빈으로 등록된 Advisor"를 어떻게 등록하는가?
  • noLog() 메서드를 제외시킨 표현식의 의미는?

📚 Phase 8 — @Aspect와 AOP 용어 완전 정리

목표: 실무에서 가장 많이 쓰는 @Aspect 어노테이션 방식을 마스터하고, AOP 용어를 정확히 잡는다.

Unit 8.1 — @Aspect — 어드바이저 자동 생성

선수 지식: Phase 7

핵심 개념

@Aspect 의 역할:

  • AspectJ 프로젝트의 어노테이션
  • Spring이 차용해서 사용
  • @Aspect 클래스 → Advisor 자동 변환

예시:

@Slf4j
@Aspect
public class LogTraceAspect {
    private final LogTrace logTrace;
    
    public LogTraceAspect(LogTrace logTrace) {
        this.logTrace = logTrace;
    }
    
    @Around("execution(* hello.proxy.app..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        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;
        }
    }
}

구성 요소:

  • @Aspect: "이 클래스는 어드바이저 변환 대상"
  • @Around: 어드바이스 + 포인트컷
  • ProceedingJoinPoint: MethodInvocation의 AOP 버전
  • joinPoint.proceed(): target 호출

⚠️ @Aspect는 자동 빈 등록이 아님:

  • @Aspect는 단순한 표식
  • 별도로 빈 등록 필요
  • 방법: @Bean, @Component, @Import

자기 점검

  • @Aspect 클래스를 빈으로 등록하지 않으면?
  • ProceedingJoinPoint와 MethodInvocation의 차이는? (힌트: AspectJ vs Spring AOP)

Unit 8.2 — AOP 등장 배경 정리

선수 지식: Unit 8.1, Phase 1

핵심 통찰

OOP의 한계:

  • 핵심 기능과 부가 기능 (로그, 트랜잭션) 분리가 어려움
  • 부가 기능을 100개 클래스에 적용 → 100군데 수정
  • 부가 기능 위치 변경 → 또 100군데 수정

AOP의 답:

"부가 기능 + 부가 기능을 어디에 적용할지 의 선택을 합쳐서 하나의 모듈로 만든다"

그게 Aspect = 관점

중요한 명제:

AOP는 OOP를 대체하는 것이 아니다.
OOP의 부족한 부분(횡단 관심사)을 보조한다.

자기 점검

  • "횡단 관심사(Cross-cutting concerns)"의 의미는?
  • AOP를 남용하면 어떤 문제가? (힌트: 흐름 추적 어려움, 디버깅 난이도)

Unit 8.3 — AspectJ vs Spring AOP

선수 지식: Unit 8.2

핵심 비교

AspectJ 프레임워크:

  • AOP의 대표적 구현
  • 자체 컴파일러, 자체 문법
  • 기능 강력

Spring AOP:

  • AspectJ 문법만 차용
  • 프록시 방식으로 동작
  • 별도 컴파일러 불필요

AOP 적용 시점 3가지:

시점방식누가 사용?
컴파일 시점실제 코드에 부가 기능 코드 삽입AspectJ 직접
클래스 로딩 시점클래스 로딩 시 코드 변경AspectJ 직접
런타임 시점프록시로 부가 기능 적용Spring AOP

실무 결론:

"Spring AOP만으로 대부분 해결 가능. AspectJ 직접 사용은 안 해도 됨"

자기 점검

  • Spring AOP가 항상 프록시를 거쳐야 한다는 게 어떤 한계로 이어지는가? (힌트: internal call)
  • AspectJ 컴파일 시점 방식은 internal call 문제가 있는가? (힌트: NO)

Unit 8.4 — AOP 핵심 용어

선수 지식: Phase 7~8

핵심 용어 7가지 ⭐ :

용어의미예시
조인 포인트(Join point)부가 기능을 적용할 수 있는 모든 지점메서드 호출, 생성자, 필드 접근
포인트컷(Pointcut)조인 포인트 중 실제 적용할 곳을 선택execution(* hello..*Service.*(..))
어드바이스(Advice)적용할 부가 기능 코드@Around 메서드
애스펙트(Aspect)포인트컷 + 어드바이스의 모듈@Aspect 클래스
타겟(Target)부가 기능이 적용되는 실제 객체OrderService 인스턴스
위빙(Weaving)포인트컷을 통해 어드바이스를 결합프록시 생성 시
AOP 프록시AOP 기능을 구현한 프록시JDK 동적 프록시 또는 CGLIB

Spring AOP의 한계 — 메서드 조인 포인트만:

  • 다른 AOP는 필드 접근, 생성자도 가능
  • Spring AOP는 메서드 호출만 지원

자기 점검

  • "조인 포인트 vs 포인트컷"의 차이는? (힌트: 후자가 부분집합)
  • 위빙이 일어나는 시점은 Spring AOP에서 언제인가? (힌트: 빈 후처리기 시점)

📚 Phase 9 — Spring AOP 실전 구현 패턴

목표: 실무에서 매일 쓰는 패턴들을 손에 익힌다.

Unit 9.1 — @Around와 ProceedingJoinPoint

선수 지식: Phase 8

핵심 개념

@Around — 가장 강력한 어드바이스:

  • 메서드 호출 전후 모두 가로채기
  • 호출 자체를 건너뛸 수도 있음
  • 반환값/예외 변환 가능
@Aspect
public class AspectV1 {
    @Around("execution(* hello.aop.order..*(..))")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();  // 실제 호출
    }
}

ProceedingJoinPoint의 주요 메서드:

  • proceed(): target 메서드 호출
  • getSignature(): 호출된 메서드의 시그니처
  • getArgs(): 전달 인자 배열
  • getTarget(): 실제 대상 객체

자기 점검

  • proceed()를 호출하지 않으면? (힌트: target 메서드가 실행 안 됨)
  • proceed()를 두 번 호출하면? (힌트: 메서드가 두 번 실행)

Unit 9.2 — @Pointcut으로 분리

선수 지식: Unit 9.1

핵심 개념

문제: 같은 포인트컷을 여러 어드바이스에서 반복 작성 → 중복

해결 — @Pointcut으로 시그니처 분리:

@Aspect
public class AspectV2 {
    @Pointcut("execution(* hello.aop.order..*(..))")
    private void allOrder() {}  // 시그니처만 (body 없음)
    
    @Around("allOrder()")  // 재사용
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
}

별도 클래스로 분리 (실무 표준):

public class Pointcuts {
    @Pointcut("execution(* hello.aop.order..*(..))")
    public void allOrder() {}
    
    @Pointcut("execution(* *..*Service.*(..))")
    public void allService() {}
    
    @Pointcut("allOrder() && allService()")  // 조합
    public void orderAndService() {}
}

@Aspect
public class AspectV4Pointcut {
    @Around("hello.aop.order.aop.Pointcuts.allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable { ... }
    
    @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable { ... }
}

포인트컷 표현식의 조합:

  • &&, ||, !

자기 점검

  • @Pointcut 메서드의 body가 비어있는 이유는?
  • 포인트컷 분리가 5주차 어떤 원칙의 적용인가? (힌트: DRY, 관심사 분리)

Unit 9.3 — 어드바이스 종류 5가지

선수 지식: Unit 9.2

핵심 개념

어드바이스시점용도
@Around메서드 전후가장 강력, 모든 것 가능
@Before메서드 실행 전단순 사전 작업
@AfterReturning정상 완료 후결과 처리
@AfterThrowing예외 발생 시예외 로깅
@After정상/예외 무관 (finally)자원 해제

선택 가이드:

  • 트랜잭션처럼 전후 + 예외 모두 처리 필요 → @Around
  • 단순 로깅 (전 또는 후) → @Before / @AfterReturning
  • 자원 정리 → @After

@Around의 강력함:

  • proceed() 호출 여부를 결정 가능 → 호출 자체 차단
  • 결과/예외를 변환 가능
  • 다른 어드바이스로 못 하는 것이 가능

실무 결론:

"고민되면 @Around 써라. 다른 건 단순 케이스에서만"

자기 점검

  • @AfterThrowing은 왜 강력하지 않은가? (힌트: 예외를 못 막음)
  • @Around 메서드는 왜 ProceedingJoinPoint를 받는가? (힌트: proceed 필요)

📚 Phase 10 — @Transactional의 함정과 트랜잭션 전파 (★ 9주차 정점)

목표: 7주차에서 다룬 @Transactional의 5가지 함정 중 internal call 문제 를 깊이 파고, 트랜잭션 전파의 4가지 시나리오를 마스터한다.

Unit 10.1 — @Transactional 동작 원리 복습

선수 지식: 7주차 Phase 7, Phase 6 (ProxyFactory)

핵심 정리

@Transactional은 트랜잭션 AOP 다.

동작 흐름:
1. 빈 등록 시 빈 후처리기가 가로챔
2. @Transactional 메서드가 있으면 → 트랜잭션 프록시로 교체
3. DI 시 실제 객체 대신 프록시 주입
4. 메서드 호출 → 프록시가 가로채서 트랜잭션 시작/커밋/롤백

@Service
public class CallService {
    @Transactional
    public void internal() {
        // 트랜잭션 적용됨
    }
}

// CallService 빈은 실제로는 프록시 객체

자기 점검

  • @Transactional이 AOP의 어떤 어드바이스에 해당하는가? (힌트: @Around)
  • 8주차 Phase 6의 어떤 메커니즘이 적용된 건가? (힌트: ProxyFactory + Advisor)

Unit 10.2 — Internal call 문제 (★★★ 면접 단골)

선수 지식: Unit 10.1

핵심 시나리오

@Service
public class CallService {
    public void external() {
        log.info("call external");
        internal();  // ⚠️ 같은 클래스의 메서드 호출
    }
    
    @Transactional
    public void internal() {
        log.info("call internal");
    }
}

문제:

  • callService.external() 호출
  • external()은 @Transactional 없음 → 프록시는 그대로 통과
  • external()이 internal() 호출 → 사실 this.internal()
  • this는 프록시가 아닌 target!
  • → internal()의 @Transactional이 무시됨

문제의 본질:

"프록시는 외부 호출을 가로챌 수 있지만, 내부 호출은 가로채지 못한다"

원인 — 자바의 동작:

  • 메서드 앞에 참조 없으면 this
  • this는 자기 자신 인스턴스
  • 프록시를 거치지 않음

자기 점검

  • this.internal()과 다른 빈의 internal() 호출은 동작이 다른가? (힌트: YES)
  • AspectJ 컴파일 시점 방식이라면 이 문제가 발생하는가? (힌트: NO)

Unit 10.3 — 해결책 — 클래스 분리

선수 지식: Unit 10.2

핵심 해결

원리:

  • 다른 클래스의 메서드 호출 = 프록시를 거침
  • → @Transactional 메서드를 별도 빈 으로 분리
@Service
public class CallService {
    private final InternalService internalService;  // 다른 빈 주입
    
    public void external() {
        log.info("call external");
        internalService.internal();  // ✅ 프록시 거침
    }
}

@Service
public class InternalService {
    @Transactional
    public void internal() {
        log.info("call internal");
    }
}

호출 흐름:

test → CallService(실제 객체) → InternalService(프록시) → InternalService(실제) → DB
                                          ↑
                                    여기서 트랜잭션 시작

다른 해결책들 (참고):

  • AspectJ 컴파일 시점 방식 사용
  • AopContext.currentProxy() 사용 (권장 X)

자기 점검

  • 왜 같은 클래스에 두면 안 되는가를 한 문장으로?
  • 객체지향 설계 관점에서 분리가 더 나은 이유는? (힌트: SRP)

Unit 10.4 — 트랜잭션 전파란 + REQUIRED 기본 동작

선수 지식: Unit 10.3, 7주차 Phase 7

핵심 개념

트랜잭션 전파(Propagation):

"이미 진행 중인 트랜잭션이 있을 때, 새 트랜잭션 요청을 어떻게 처리할까?"

REQUIRED (기본 옵션):

  • 진행 중인 트랜잭션 있음 → 참여
  • 없음 → 새 트랜잭션 시작

예시 — 둘 다 커밋:

@Test
void inner_commit() {
    log.info("외부 트랜잭션 시작");
    TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info("outer.isNewTransaction()={}", outer.isNewTransaction());  // true
    
    log.info("내부 트랜잭션 시작");
    TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info("inner.isNewTransaction()={}", inner.isNewTransaction());  // false (참여)
    
    txManager.commit(inner);   // 실제로는 안 일어남
    txManager.commit(outer);   // 여기서 진짜 commit
}

핵심 통찰:

  • 외부 + 내부 트랜잭션이 하나의 물리 트랜잭션 으로 묶임
  • 내부의 commit/rollback은 "논리적" 동작
  • 실제 물리 commit/rollback은 외부에서만 발생

자기 점검

  • 논리 트랜잭션과 물리 트랜잭션의 차이를 설명하라
  • isNewTransaction()이 true/false인 의미는?

Unit 10.5 — 내부 롤백의 함정 (UnexpectedRollbackException)

선수 지식: Unit 10.4

핵심 시나리오

@Test
void inner_rollback() {
    TransactionStatus outer = txManager.getTransaction(...);
    TransactionStatus inner = txManager.getTransaction(...);
    
    txManager.rollback(inner);   // 내부 롤백
    
    assertThatThrownBy(() -> txManager.commit(outer))   // 외부 커밋 시도
        .isInstanceOf(UnexpectedRollbackException.class);  // ⚠️ 예외!
}

동작 분석:

내부 롤백 시:
1. 신규 트랜잭션이 아니므로 실제 롤백 안 함
2. 트랜잭션 동기화 매니저에 rollbackOnly=true 표시

외부 커밋 시:
1. 신규 트랜잭션이므로 실제 커밋 시도
2. rollbackOnly 표시 발견
3. 커밋 대신 롤백 실행
4. 개발자에게 UnexpectedRollbackException 던짐

중요한 통찰 ⭐ :

"내부든 외부든 논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백"

왜 예외를 던지는가:

  • 개발자는 commit을 호출했음
  • 실제로는 롤백됨
  • → 시스템 일관성을 위해 명시적으로 알림

자기 점검

  • "조용한 롤백"이 일어나면 어떤 사고가 가능한가?
  • @Transactional에서 이 예외가 어떻게 노출되는가?

Unit 10.6 — REQUIRES_NEW (별도 트랜잭션) + 전파 옵션

선수 지식: Unit 10.5

핵심 개념

REQUIRES_NEW:

  • 항상 새 트랜잭션 시작
  • 외부 트랜잭션과 완전히 분리
  • 각각 별도 commit/rollback

예시:

TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());

DefaultTransactionAttribute def = new DefaultTransactionAttribute();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus inner = txManager.getTransaction(def);  // ⭐ 새 물리 트랜잭션

txManager.rollback(inner);  // 내부만 롤백
txManager.commit(outer);    // 외부는 정상 커밋  ✅

효과:

  • 내부 롤백이 외부에 영향 X
  • 외부 롤백이 내부에 영향 X

⚠️ 주의사항:

  • 내부 트랜잭션이 끝날 때까지 외부 커넥션은 대기 상태
  • 동시에 2개 커넥션 사용 → 커넥션 풀 빠르게 고갈 가능

전파 옵션 7가지:

옵션설명
REQUIRED기본. 있으면 참여, 없으면 시작
REQUIRES_NEW항상 새 트랜잭션
SUPPORT있으면 참여, 없으면 트랜잭션 없이
NOT_SUPPORT있으면 보류, 트랜잭션 없이 진행
MANDATORY반드시 있어야 함, 없으면 예외
NEVER트랜잭션 있으면 예외
NESTED중첩 트랜잭션 (Savepoint)

실무에서 가장 많이 쓰는 것: REQUIRED, REQUIRES_NEW

자기 점검

  • REQUIRES_NEW를 남발하면 어떤 사고가? (힌트: 커넥션 풀 고갈, 데드락)
  • 로깅 작업에는 어떤 전파 옵션이 적합할까? (힌트: REQUIRES_NEW or SUPPORT)

🎓 종합 자기 점검 (8-9주차 졸업 시험)

Part A: 8주차

AOP 동기와 패턴 진화

  1. AOP에서 핵심 관점과 부가 관점의 차이는?
  2. "흩어진 관심사"의 정의와 사례 3가지는?
  3. ThreadLocal의 remove()를 누락하면 어떤 사고가 가능한가?
  4. 템플릿 메서드 패턴의 한계 2가지는?
  5. 전략 패턴이 상속 대신 무엇을 사용하는가?
  6. 템플릿 콜백 패턴은 어떤 GOF 패턴의 변형인가?

프록시

  1. 프록시의 두 가지 주요 기능은?
  2. 프록시 패턴과 데코레이터 패턴의 차이를 의도로 설명하라
  3. JDK 동적 프록시의 전제 조건은?
  4. CGLIB가 인터페이스 없이도 작동하는 이유는?
  5. Spring Boot의 기본 프록시 방식은?

ProxyFactory와 추상화

  1. Pointcut, Advice, Advisor의 정의와 관계는?
  2. Advice 인터페이스를 도입한 이유는?
  3. AspectJExpressionPointcut이 실무 표준인 이유는?
  4. "하나의 target에 여러 AOP 적용 시 프록시는 몇 개?"

Part B: 9주차

자동 프록시

  1. 빈 후처리기가 무엇인가?
  2. AnnotationAwareAspectJAutoProxyCreator의 이름이 의미하는 바는?
  3. @Aspect가 자동으로 빈 등록되지 않는 이유는?

AOP 용어

  1. 조인 포인트와 포인트컷의 차이는?
  2. 위빙(Weaving)이란 무엇이며 Spring AOP에서 언제 일어나는가?
  3. AOP 적용 3가지 시점과 Spring AOP의 선택은?

Spring AOP 실전

  1. @Around가 가장 강력한 이유는?
  2. ProceedingJoinPoint와 MethodInvocation의 차이는?
  3. @Pointcut으로 분리하는 이유 2가지는?
  4. 5가지 어드바이스 어노테이션과 각각의 용도는?

@Transactional 함정

  1. Internal call 문제의 본질을 한 문장으로?
  2. Internal call 문제의 해결 방법은?
  3. AspectJ 컴파일 방식은 internal call 문제가 있는가?

트랜잭션 전파

  1. 논리 트랜잭션과 물리 트랜잭션의 차이는?
  2. REQUIRED 옵션의 동작 규칙은?
  3. 내부 롤백 시 UnexpectedRollbackException이 던져지는 이유는?
  4. REQUIRES_NEW의 주의사항은?

📌 학습 운영 팁

9-섹션 마스터 프롬프트로 깊이 파야 할 Unit (특별판)

이번 주차는 분량이 많은 만큼 반드시 깊이 파야 할 Unit 을 명확히 표시:

★★★ 면접·실무 단골 (반드시):

  • Unit 5.2 — JDK 동적 프록시 + InvocationHandler
  • Unit 6.1 — ProxyFactory의 자동 선택
  • Unit 6.3 — Pointcut + Advice = Advisor (3대 개념)
  • Unit 8.1 — @Aspect + @Around
  • Unit 8.4 — AOP 용어 7가지
  • Unit 10.2 — Internal call 문제
  • Unit 10.5 — UnexpectedRollbackException

★★ 매우 권장:

  • Unit 1.3 — ThreadLocal과 remove()
  • Unit 4.3 — 프록시 vs 데코레이터 의도 구분
  • Unit 5.3 — CGLIB
  • Unit 9.3 — 어드바이스 5종
  • Unit 10.6 — REQUIRES_NEW

Phase별 진도 체크리스트

[ Part A — 8주차 ]
[ ] Phase 1 — AOP 입문과 동기 (Unit 1.1~1.3)
[ ] Phase 2 — 디자인 패턴의 진화 (Unit 2.1~2.4)
[ ] Phase 3 — 콜백과 프록시의 만남 (Unit 3.1~3.3)
[ ] Phase 4 — 프록시 vs 데코레이터 (Unit 4.1~4.3)
[ ] Phase 5 — 동적 프록시 기술 (Unit 5.1~5.3)
[ ] Phase 6 — ProxyFactory 통합 추상화 (Unit 6.1~6.4)  ★ 8주차 정점

[ Part B — 9주차 ]
[ ] Phase 7 — 빈 후처리기와 자동 프록시 (Unit 7.1~7.3)
[ ] Phase 8 — @Aspect와 AOP 용어 (Unit 8.1~8.4)
[ ] Phase 9 — Spring AOP 실전 패턴 (Unit 9.1~9.3)
[ ] Phase 10 — @Transactional + 트랜잭션 전파 (Unit 10.1~10.6)  ★ 9주차 정점

[ ] 종합 자기 점검 32문항 통과

8-9주차의 두 정점

8주차 정점 — Phase 6 (ProxyFactory):

  • 모든 디자인 패턴 학습이 한 곳으로 모이는 지점
  • 5주차 OOP 원칙 + 8주차 패턴 진화 + Spring 통합

9주차 정점 — Phase 10 (트랜잭션 전파):

  • AOP 학습의 결실
  • 7주차 @Transactional이 왜 그렇게 동작했는지 완전 이해
  • 면접·실무 모두에서 가장 자주 부딪히는 영역

1~9주차 통합 흐름의 절정

8-9주차는 1~7주차의 학습이 모두 결합 되는 지점:

출처 주차8-9주차에서의 역할
1주차 (OOP, 인터페이스)프록시·전략 패턴의 기반
3주차 (람다, 함수형)Advice/Callback의 람다 활용
4주차 (멀티스레드, ThreadLocal)로그 추적기의 동시성 해결
5주차 (디자인 패턴, OCP)템플릿 메서드 → 전략 패턴 진화
5주차 (싱글톤 빈)자동 프록시의 빈 교체 메커니즘
6주차 (JdbcTemplate)템플릿 콜백 패턴의 실제 사례
7주차 (@Transactional)8-9주차 학습의 동기 + 적용 사례

8-9주차는 자바·Spring 학습의 클라이맥스

profile
Software Developer

0개의 댓글