Spring Boot 애플리케이션이 실행될 때 코드가 어떤 순서로 동작하고 JVM 메모리 어디에 무엇이 올라가는지 — 1차부터 3차+심화까지 전체 조감도
코드 실행 흐름을 이해하는 가장 좋은 방법은 큰 그림 → 레이어별 흐름 → 코드 한 줄씩 순서로 파고드는 것입니다. 이 파일(06a)이 전체 조감도고, 06b~06f에서 각 차수별로 깊게 들어갑니다.
Java 코드가 실행될 때 JVM은 메모리를 세 영역으로 나눠서 관리합니다. Spring에서 어떤 객체가 어디에 올라가는지 알면 코드 흐름이 훨씬 선명하게 보입니다.
@SpringBootApplication이 붙은 main()이 실행되면 → Spring Container(ApplicationContext)가 생성되고 →
@Component, @Service, @Repository, @Controller가 붙은 클래스들을 스캔해서 →
Heap에 객체(Bean)를 딱 하나씩 생성합니다 (기본: Singleton).
이후 UserService가 필요한 곳 어디서든 이 하나의 객체를 재사용합니다.
HTTP 요청이 하나 들어오면 → Tomcat이 스레드를 하나 할당 → 그 스레드의 Stack이 생성됩니다.
login() 호출 → Stack에 login 프레임 push → userRepository.findByUsername() 호출 → Stack에 또 push →
메서드 끝나면 pop.
Bean 객체(Heap)는 공유되지만, 각 요청의 지역변수(Stack)는 완전히 독립적입니다.
그래서 동시에 1000명이 요청해도 뒤섞이지 않습니다.
BoardApplication.java의 main()을 실행하면 아래 순서로 진행됩니다.
코드가 어떤 순서로 메모리에 올라가는지 전부 나열합니다.
| 단계 | 무슨 일이 일어나나 | 메모리 | 코드/클래스 |
|---|---|---|---|
| ① JVM 시작 | JVM 프로세스 생성, 클래스로더 초기화 | Method Area 초기화 | java 명령어 |
| ② main() 실행 | Stack에 main 프레임 생성 | Stack — main 프레임 | BoardApplication.main() |
| ③ SpringApplication.run() | ApplicationContext(Spring Container) 생성 시작 | Heap — ApplicationContext 객체 | SpringApplication.run(BoardApplication.class) |
| ④ 클래스 스캔 | @Component 계열 클래스 전부 탐색 | Method Area — 클래스 정보 로딩 | ComponentScan |
| ⑤ Bean 생성 | 발견된 클래스들을 Heap에 객체로 생성 (new) | Heap — UserService, BoardService, UserRepository... 객체 생성 | BeanFactory |
| ⑥ 의존성 주입(DI) | 생성자/필드 주입으로 Bean들 연결 | Heap — UserService.userRepository 참조 연결 | @RequiredArgsConstructor → 생성자 호출 |
| ⑦ AOP Proxy 생성 | @Aspect, @Transactional 붙은 Bean을 Proxy로 감쌈 | Heap — 원본 Bean 대신 Proxy 객체로 교체 | CGLIB Proxy |
| ⑧ Security 설정 (3차) | SecurityFilterChain 생성, Filter 순서 등록 | Heap — FilterChain 객체, 각 Filter 객체 | SecurityConfig.filterChain() |
| ⑨ DB 연결 (JPA) | DataSource 생성, Hibernate 초기화, 테이블 검증 | Heap — DataSource, EntityManagerFactory | application.properties 설정 |
| ⑩ Tomcat 시작 | 내장 Tomcat 서버 8081/8082/8083 포트 LISTEN | 별도 스레드 — Accept 스레드 | EmbeddedTomcat |
| ⑪ 준비 완료 | "Started BoardApplication" 로그 출력, 요청 대기 | 전체 Bean 그래프 완성 | — |
UserService, BoardController, JwtUtil 등 모든 Bean은
서버 시작 시 딱 한 번 Heap에 생성되고, 서버가 살아있는 동안 계속 재사용됩니다.
요청이 올 때마다 새로 만들지 않습니다.
반면 User user = new User(), BoardDto dto = new BoardDto() 같은 요청 처리 중 만드는 객체는
요청마다 새로 Heap에 생성되고, GC가 처리합니다.
| 실행 단계 | 1차 (Thymeleaf+Session) | 2차 (React+JWT) | 3차 (Spring Security) |
|---|---|---|---|
| ① 요청 방식 | HTML form POST/user/login |
axios POST/api/user/login |
axios POST/api/user/login |
| ② Filter 실행 | 없음 | JwtFilter 실행 (로그인은 토큰 없으니 건너뜀) |
CorsFilter → JwtAuthFilter(건너뜀) → AuthorizFilter(permitAll) |
| ③ Controller | @ControllerString 반환 (View) |
@RestControllerJSON 반환 |
@RestControllerJSON 반환 |
| ④ Service 실행 | userService.login()User 반환 |
userService.login()JWT String 반환 |
userService.login()Map(token+role) 반환 |
| ⑤ 인증 저장 | session.setAttribute("loginUser", user)→ Heap의 Session 객체에 저장 |
request.setAttribute("loginUser", user)→ Stack의 request 객체에 저장 |
SecurityContextHolder.getContext().setAuthentication()→ Thread-Local에 저장 |
| ⑥ 클라이언트 저장 | JSESSIONID 쿠키 (브라우저 자동 관리) |
token → localStorage | token + role → localStorage |
| ⑦ 응답 | redirect:/board/list (브라우저 이동) |
JSON { token } | JSON { token, role } |
| 인증 방식 특성 | 1차 Session | 2차 JWT (직접 구현) | 3차 JWT + Security |
|---|---|---|---|
| 서버 메모리 사용 | 사용 — Session 저장 | 미사용 — STATELESS | 미사용 — STATELESS |
| 로그인 유저 꺼내기 | session.getAttribute() |
request.getAttribute() |
@AuthenticationPrincipal (자동) |
| URL 권한 설정 위치 | Controller마다 직접 | Controller마다 직접 | SecurityConfig 한 곳 |
| 확장성 | 서버 확장 시 세션 공유 문제 | 서버 어디서든 검증 가능 | 서버 어디서든 검증 가능 |
| 파일 | 내용 | 읽는 순서 |
|---|---|---|
| 06b | 1차 Thymeleaf+Session — 기동부터 요청처리까지 코드 실행 순서 + 메모리 | 1번째 |
| 06c | 2차 React+JWT — JwtFilter 실행 순서, JWT 생성/검증 메모리 흐름 | 2번째 |
| 06d | 3차 Spring Security — FilterChain 실행 순서, SecurityContext Thread-Local | 3번째 |
| 06e | 심화 — AOP Proxy 생성, @Transactional 커밋/롤백, Stream 내부 실행 | 4번째 |
| 06f | 통합 — DI→AOP→Security→Controller→Service→DB 전체 메모리 상태 변화 | 마지막 정리 |