실행흐름 심화📚 전체 맵DI 조감도
06f · 통합 완결편

COMPLETE
FLOW

Application 기동 → DI → AOP Proxy → Security 설정 → HTTP 요청 → Filter Chain → Controller → Service → DB → 응답까지
모든 단계의 코드 실행 순서와 JVM 메모리 상태 변화 전체

Part 1 — Boot
Application 기동 — DI → AOP → Security → 완료까지 전체 순서
기동 전체 흐름 — 단계별 메모리 상태
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 단계  │ 실행 내용                   │ 메모리 변화
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   JVM 시작, main() 호출           Stack: main 프레임
   SpringApplication.run()         Heap: ApplicationContext 생성 시작
   클래스 스캔                       Method Area: .class 파일들 로딩
     @Component/@Service/@Repository
     @Controller/@Aspect/@Configuration

   ──── DI 단계 ────
     Repository Bean 생성             Heap: UserRepository, BoardRepository 생성
     Service Bean 생성                Heap: UserService(userRepo 주입), BoardService 생성
     Controller Bean 생성             Heap: UserController, BoardController 생성
     Util Bean 생성                   Heap: JwtUtil(secretKey 주입) 생성
     Security Bean 생성               Heap: CustomUserDetailsService, JwtAuthFilter 생성

   ──── AOP 단계 ────
     @Aspect 감지                     Method Area: LogAspect 클래스
     Pointcut 분석                    → service 패키지 대상 확인
     Proxy 생성                       Heap: UserService$$CGLIB (Proxy) 생성
                                      Heap: BoardService$$CGLIB (Proxy) 생성
                                      Heap: AdminService$$CGLIB (Proxy) 생성
     ApplicationContext 교체          → 원본 Bean 대신 Proxy Bean 등록
     (@Transactional 대상도 포함)

   ──── Security 단계 ────
     SecurityConfig.filterChain()     Heap: SecurityFilterChain 생성
     Filter 등록                      → [CorsFilter → JwtAuthFilter → AuthorizFilter] 순서 등록
     CORS 설정                        Heap: CorsConfigurationSource 생성
     권한 규칙 등록                    → permitAll/hasRole/authenticated 규칙 저장

   ──── DB 단계 ────
     DataSource 생성                  Heap: HikariDataSource (커넥션 풀)
     EntityManagerFactory 생성        Heap: JPA 핵심 객체
     ddl-auto=update                  DB: 테이블 검증/생성

   Tomcat 시작                      Thread: Accept 스레드 생성
     포트 8083 LISTEN                  ThreadPool 초기화 (최대 200 스레드)

   "Started BoardApplication"        기동 완료 — 요청 대기
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📌 기동 완료 시점 Heap 상태 — 상주 객체 전체

Bean (Singleton): UserRepository, BoardRepository, AdminRepository, UserService Proxy, BoardService Proxy, AdminService Proxy, UserController, BoardController, AdminController, JwtUtil, CustomUserDetailsService, JwtAuthenticationFilter, LogAspect, BCryptPasswordEncoder

Infrastructure: SecurityFilterChain, CorsConfigurationSource, HikariDataSource, EntityManagerFactory, ApplicationContext (이 모든 Bean들을 담는 컨테이너)

이 객체들은 서버가 살아있는 한 Heap에 상주합니다. 요청마다 새로 만들지 않습니다.

Part 2 — Request
게시글 작성 요청 — 처음부터 끝까지 모든 단계 + Stack 상태

3차 기준. React에서 "글쓰기" 버튼을 누르는 순간부터 화면에 성공 메시지가 뜨기까지 모든 코드가 실행되는 순서와 그 시점의 JVM Stack/Heap 상태를 전부 추적합니다.

REACT
01
handleWrite() 실행 → axios interceptor → HTTP 요청 전송
브라우저 JS 엔진에서 handleWrite() 실행 →
axios interceptor: localStorage.getItem("token")
config.headers.Authorization = "Bearer eyJ..." 자동 세팅 →
POST /api/board/write HTTP 요청 전송 (title, content JSON body)
브라우저 메모리 — localStorage token 읽기
TOMCAT
02
TCP 연결 수신 → 스레드 할당 → Stack 생성
Accept 스레드가 TCP 연결 감지 → ThreadPool에서 Worker 스레드 하나 꺼냄 → 그 스레드 전용 Call Stack 생성 → HTTP 요청 파싱 → HttpServletRequest 객체 생성 (Heap)
STACK — 새 Worker 스레드 스택 생성 HEAP — new HttpServletRequest()
FILTER
03
CorsFilter → 통과
CorsFilter 실행 → Origin: http://localhost:3002 체크 → SecurityConfig의 allowedOrigins에 있음 → 통과 → Access-Control-Allow-Origin 헤더 응답에 추가 예약
STACK — CorsFilter.doFilter() 프레임 push/pop
FILTER
04
JwtAuthenticationFilter → 토큰 검증 → SecurityContext 저장
doFilterInternal() 실행 →
헤더에서 token 추출 → Stack에 token 지역변수 →
jwtUtil.validateToken(token) → 서명 검증 →
jwtUtil.getUsername(token) → "hong" → Stack에 username 지역변수 →
customUserDetailsService.loadUserByUsername("hong") → DB SELECT →
Heap에 new CustomUserDetails(user) 생성 →
Heap에 new UsernamePasswordAuthenticationToken(userDetails, null, authorities) 생성 →
SecurityContextHolder.getContext().setAuthentication(auth)Thread-Local에 auth 저장
STACK — token, username, userDetails, auth 지역변수 HEAP — CustomUserDetails, Authentication 객체 THREAD-LOCAL — SecurityContext.auth 저장
FILTER
05
AuthorizationFilter → /api/board/write → authenticated() 체크 → 통과
Thread-Local에서 auth 읽음 → auth != null → authenticated() 조건 충족 → 통과 → DispatcherServlet으로 전달
THREAD-LOCAL — auth 읽기
DISPATCH
06
DispatcherServlet → HandlerMapping → BoardController.write() 매핑
DispatcherServlet이 URL/Method 보고 BoardController.write()로 매핑 →
ArgumentResolver 실행:
- @RequestBody BoardDto: JSON body 역직렬화 → Heap에 new BoardDto() 생성
- @AuthenticationPrincipal: Thread-Local에서 auth.getPrincipal() → CustomUserDetails 꺼냄
HEAP — new BoardDto() (JSON 역직렬화) THREAD-LOCAL — getPrincipal() → CustomUserDetails
CONTROLLER
07
BoardController.write() 실행
Stack에 write() 프레임 push →
userDetails.getUser() → Heap의 User 객체 참조 꺼냄 →
boardService.write(boardDto, user) 호출 →
boardService는 실제로 BoardService Proxy → Proxy.write() 먼저 실행
STACK — write() 프레임, boardDto/userDetails 지역변수
AOP PROXY
08
AOP Proxy.write() → LogAspect.logAround() 실행 → 실제 write() 진입
BoardService$$CGLIB.write() 호출 →
LogAspect.logAround() 프레임 push →
"[LOG] write 시작" 출력 →
joinPoint.proceed() → 실제 BoardService.write() 프레임 push
STACK 상태: write(ctrl) → Proxy.write() → logAround() → BoardService.write()
SERVICE
09
BoardService.write() — @Builder로 Board 객체 생성
Stack에 write() 프레임 push →
Board.builder().title(...).content(...).user(user).build()
Heap: new BoardBuilder() 생성 → 필드 세팅 → new Board() 생성 → BoardBuilder GC 대상 →
boardRepository.save(board) 호출
HEAP — new BoardBuilder() → new Board() STACK — board 지역변수 (Board 참조)
JPA/DB
10
boardRepository.save(board) → @PrePersist → INSERT
JPA: board 엔티티 상태 확인 (새 객체 → INSERT) →
@PrePersist 실행: createdAt = LocalDateTime.now()
HikariPool에서 DB 커넥션 꺼냄 →
INSERT INTO board (title, content, user_id, created_at) VALUES (?, ?, ?, ?) 실행 →
DB가 id 발급 → board.id에 채워짐 →
커넥션 반납
HEAP — DB Connection (순간 점유, 즉시 반납)
AOP
11
BoardService.write() 완료 → AOP finally 실행 → 로그 출력
BoardService.write() 프레임 pop →
logAround() finally 블록: "[LOG] write 종료 (23ms)" 출력 →
logAround() 프레임 pop → Proxy.write() 프레임 pop →
Controller write() 로 반환
STACK — BoardService.write() pop → logAround() pop → Proxy.write() pop
CONTROLLER
12
ResponseEntity.ok() → Jackson JSON 직렬화 → HTTP 응답
Map.of("message", "글쓰기 성공") → Heap에 Map 생성 →
Jackson이 JSON 문자열 생성 {"message":"글쓰기 성공"}
HTTP 200 응답 전송 → Map, JSON 문자열 GC 대상 →
Controller write() 프레임 pop
HEAP — Map, JSON 문자열 (전송 후 GC) STACK — Controller write() 프레임 pop
SECURITY
13
SecurityContextHolderFilter → Thread-Local 정리
응답 완료 후 Filter 역방향 실행 →
SecurityContextHolder.clearContext() → Thread-Local에서 auth 삭제 →
스레드가 ThreadPool로 반환 → 다음 요청 처리 준비
THREAD-LOCAL — clearContext() → auth 삭제 STACK — 모든 프레임 pop, 스레드 반환
REACT
14
response.data 수신 → navigate("/board/list")
axios Promise resolve → response.data.message
navigate("/board/list") → 목록 페이지로 이동
브라우저 — React 상태 업데이트, 라우팅

📚 최대 Stack 깊이 — 단계 09 시점 (가장 깊을 때)

BoardService.write() ← 현재 실행 중
LogAspect.logAround() — proceed() 대기 중
BoardService$$CGLIB.write() — Proxy 프레임
BoardController.write() — Service 호출 대기
DispatcherServlet.doDispatch()
FilterChain.doFilter() 체인들
Tomcat Worker 스레드 run()
↑ Stack의 바닥 (먼저 호출된 것)
Part 3 — Final
1차 vs 2차 vs 3차 — 메모리 관점 최종 비교표
메모리 관점 1차 Thymeleaf+Session 2차 React+JWT 3차 Spring Security
인증 저장 위치 Heap — HttpSession 객체
(서버 메모리 30분 점유)
Heap — request 객체 속성
(요청 처리 동안만)
Thread-Local — SecurityContext
(요청 처리 동안만, 요청마다 독립)
클라이언트 저장 브라우저 쿠키
(JSESSIONID)
localStorage
(token)
localStorage
(token + role)
요청마다 Stack Controller → Service → Repository JwtFilter → Controller → Service → Repository CorsFilter → JwtAuthFilter → AuthorizFilter → Controller → AOP Proxy → Service → Repository
AOP Proxy @Transactional 대상만 @Transactional 대상만 @Transactional + @Aspect 로깅 대상
서버 확장 시 Session 공유 문제 발생
(서버 A Session을 서버 B 모름)
문제 없음
(서버가 상태 저장 안 함)
문제 없음
(STATELESS)
HTML 생성 위치 서버 Heap
(Thymeleaf 렌더링)
브라우저
(React 렌더링)
브라우저
(React 렌더링)
로그인 유저 꺼내기 session.getAttribute() — 직접 request.getAttribute() — 직접 @AuthenticationPrincipal — 자동 주입
(ArgumentResolver가 Thread-Local에서 꺼냄)
📌 전체를 꿰뚫는 핵심 한 줄

기동 시: 모든 Bean은 Heap에 딱 한 번 만들어지고 재사용된다. AOP Proxy가 Bean을 감싸고, Security는 FilterChain을 Heap에 등록한다.

요청 시: 스레드마다 독립적인 Stack이 생기고, 메서드 호출마다 프레임이 push/pop된다. Thread-Local(SecurityContext)은 같은 요청 내 어디서든 인증 정보를 꺼낼 수 있게 해준다. 요청 완료 후 Stack은 비워지고 Thread-Local도 정리된다. Heap의 임시 객체는 GC가 처리한다.

실행흐름 심화📚 전체 맵DI 조감도