Study · open-source/backend/java/spring-boot
[Study] Spring Boot — main() 한 줄이 HTTP 응답 한 번이 되기까지
무엇을 켤지 정하고, 빈을 만들어 담고, 프록시로 감싸고, 톰캣을 올리고, 요청 하나를 받아 JSON으로 돌려주기까지를 8단계로 나눴다. 단계마다 페이지가 하나이고, 장면은 버튼을 눌러 한 스텝씩 넘기며 본다.
값은 example/spring-boot/example을 --debug로 한 번 돌려 얻은 실측이다(Spring Boot 4.0.6 · Tomcat 11.0.21 · H2 + HikariCP · JDK 21.0.10 · 포트 18080). 버전이 다르면 조건 개수와 빈 개수가 달라진다.
8단계 지도
올릴 때 — 서버를 띄울 때 한 번
요청마다 — 요청이 들어올 때마다
요청 한 번이 지나가는 소스를 디버거로 멈춰 세워 쌓았다
6 · 7 · 8단계는 요청 한 번을 스레드 · 주소 표 · 응답으로 나눠 봤다. 나눠 보면 그 요청이 실제로 어느 프로젝트의 소스를 어떤 차례로 지나가는지는 한눈에 안 보인다. 내 앱은 내가 쓴 코드만으로 돌지 않는다. build.gradle 에 적은 톰캣 · 스프링 · DB 같은 남의 코드(jar)가 같은 프로그램 안에 들어 있고, 요청은 그 코드들을 번갈아 지나간다.
내가 쓴 코드는 메서드 두 개다. 요청을 받는 OrderController.place와, @Transactional이 붙어 DB 에 주문 한 줄을 넣고 개수를 세는 OrderService.place다. 여기에 POST /orders?item=book(책 한 권을 주문으로 넣어 달라는 HTTP 요청)를 한 번 보냈다.
보내기 전에 예제 앱을 디버그 모드로 띄우고 디버거를 연결해 두었다. 그리고 요청이 지나가리라 짐작한 7곳에 중단점을 걸었다 — 연결을 받는다 · 읽을 것이 온 연결을 일꾼 스레드에 넘긴다 · 내 메서드에 들어선다 · insert 를 DB 에 보낸다 · 트랜잭션을 커밋한다 · JSON 바이트를 톰캣에 넘긴다 · 바이트가 소켓으로 나간다. 짐작이 틀렸다면 그 자리는 안 걸렸을 텐데, 7곳 모두 걸렸다. 걸릴 때마다 그곳을 지나던 스레드를 멈춰 세우고, 그 순간의 스택을 맨 아래 칸부터 전부 읽었다. 앱 코드는 한 줄도 고치지 않았다.
첫 그림에서 막대 한 칸이 스택의 한 칸이다. 맨 위 칸이 그 순간 실행 중인 메서드이고, 그 아래 칸들은 바로 위 칸을 부르고 돌아오기를 기다리는 메서드다. 위로 쌓일수록 더 깊이 부른 메서드이고, 막대 색이 그 메서드의 소스가 든 프로젝트다. CGLIB 색만 예외로, 소스 없이 스프링이 실행 중에 만든 클래스(4단계)다.
멈춘 차례마다 한 열씩 오른쪽으로 나아간다. 같은 스레드의 앞 차례와 아래부터 같은 메서드인 칸은 이어진 것으로 보고 옆 열까지 막대를 이었고, 달라진 칸부터 새로 쌓았다. 그 사이에 한 번 돌아왔다가 같은 메서드를 다시 불렀을 수도 있지만, 멈춘 순간만으로는 둘을 가를 수 없다. 같은 까닭으로 그림은 멈춘 일곱 순간만 안다. 그 사이에 불렸다가 이미 돌아온 메서드는 그림에 없다. 「다음」을 누르면 한 차례씩 나아가고, 내 메서드에 들어설 때까지의 긴 첫 차례만 네 번에 나눠 올라간다.
이 화면에서는 2D 그림을 그리지 못했다. 아래 시퀀스 다이어그램 밑의 로그(「멈춘 7번의 스택」)가 같은 내용이다.
잰 때 2026-10-13 10:56 · OpenJDK 64-Bit Server VM 21.0.9 ·
POST /orders?item=book → 200(성공). 1–8단계의 값은 JDK 21.0.10 으로 쟀지만, 이 그림을 잰 날 이 컴퓨터에서는 21.0.10 을 찾지 못해 위의 JVM 으로 쟀다. 나온 프로젝트의 버전은 톰캣 11.0.21 · 스프링 프레임워크 7.0.7 · HikariCP 7.0.2 · H2 2.4.240 · Jackson 3.1.2 · JDK 21.0.9이다.이 화면에서는 시퀀스 다이어그램을 그리지 못했다. 바로 아래 로그(「멈춘 7번의 스택」)를 차례로 견줘 보면 누가 누구를 불렀는지 나온다. 그 아래 설명은 그 그림을 읽는 법이다.
로그 — 멈춘 7번의 스택. 맨 위가 그 순간 실행 중이던 메서드, 맨 아래가 스레드가 시작한 곳이고, 이름:숫자의 숫자는 그 소스 파일의 줄 번호다. 줄을 누르면 위 시퀀스 다이어그램이 그 순간으로 간다.
1. 연결을 받는다 http-nio-18080-Acceptor · 4칸 · 멈춘 곳 org.apache.tomcat.util.net.NioEndpoint.setSocketOptions
- 톰캣
org.apache.tomcat.util.net.NioEndpoint.setSocketOptions:71 - 톰캣
org.apache.tomcat.util.net.Acceptor.run:147 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
2. 읽을 것이 온 연결을 일꾼 스레드에 넘긴다 http-nio-18080-Poller · 5칸 · 멈춘 곳 org.apache.tomcat.util.net.AbstractEndpoint.processSocket
- 톰캣
org.apache.tomcat.util.net.AbstractEndpoint.processSocket:1575 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$Poller.processKey:845 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$Poller.run:812 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
3. 내 메서드에 들어선다 http-nio-18080-exec-2 · 68칸 · 멈춘 곳 com.example.example.service.OrderService.place
- 내 코드
com.example.example.service.OrderService.place:18 - JDK
java.lang.invoke.LambdaForm$DMH/0x000002d7c0002800.invokeVirtual - JDK
java.lang.invoke.LambdaForm$MH/0x000002d7c05fc800.invoke - JDK
java.lang.invoke.Invokers$Holder.invokeExact_MT - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl:154 - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invoke:103 - JDK
java.lang.reflect.Method.invoke:580 - 스프링 프레임워크
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection:359 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint:190 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed:158 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation:133 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction:371 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionInterceptor.invoke:130 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed:179 - 스프링 프레임워크
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept:719 - 스프링이 만든 클래스
com.example.example.service.OrderService$$SpringCGLIB$$0.place - 내 코드
com.example.example.controller.OrderController.place:25 - JDK
java.lang.invoke.LambdaForm$DMH/0x000002d7c0158c00.invokeVirtual - JDK
java.lang.invoke.LambdaForm$MH/0x000002d7c0369800.invoke - JDK
java.lang.invoke.Invokers$Holder.invokeExact_MT - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl:154 - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invoke:103 - JDK
java.lang.reflect.Method.invoke:580 - 스프링 프레임워크
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke:252 - 스프링 프레임워크
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest:184 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle:117 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod:934 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal:853 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle:86 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doDispatch:963 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doService:866 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.processRequest:1000 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.doPost:903 - 톰캣
jakarta.servlet.http.HttpServlet.service:649 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.service:874 - 톰캣
jakarta.servlet.http.HttpServlet.service:710 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:128 - 톰캣
org.apache.tomcat.websocket.server.WsFilter.doFilter:53 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.RequestContextFilter.doFilterInternal:100 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.FormContentFilter.doFilterInternal:93 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal:110 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal:199 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 톰캣
org.apache.catalina.core.StandardWrapperValve.invoke:165 - 톰캣
org.apache.catalina.core.StandardContextValve.invoke:77 - 톰캣
org.apache.catalina.authenticator.AuthenticatorBase.invoke:492 - 톰캣
org.apache.catalina.core.StandardHostValve.invoke:113 - 톰캣
org.apache.catalina.valves.ErrorReportValve.invoke:83 - 톰캣
org.apache.catalina.core.StandardEngineValve.invoke:72 - 톰캣
org.apache.catalina.connector.CoyoteAdapter.service:341 - 톰캣
org.apache.coyote.http11.Http11Processor.service:397 - 톰캣
org.apache.coyote.AbstractProcessorLight.process:63 - 톰캣
org.apache.coyote.AbstractProtocol$ConnectionHandler.process:903 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun:1801 - 톰캣
org.apache.tomcat.util.net.SocketProcessorBase.run:52 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker:946 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run:480 - 톰캣
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run:57 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
4. insert 를 DB 에 보낸다 http-nio-18080-exec-2 · 77칸 · 멈춘 곳 org.h2.jdbc.JdbcPreparedStatement.executeUpdate
- H2
org.h2.jdbc.JdbcPreparedStatement.executeUpdate:170 - HikariCP
com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate:61 - HikariCP
com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate - 스프링 프레임워크
org.springframework.jdbc.core.JdbcTemplate.lambda$update$0:971 - 스프링 프레임워크
org.springframework.jdbc.core.JdbcTemplate$$Lambda/0x000002d7c05f99b8.doInPreparedStatement - 스프링 프레임워크
org.springframework.jdbc.core.JdbcTemplate.execute:670 - 스프링 프레임워크
org.springframework.jdbc.core.JdbcTemplate.update:966 - 스프링 프레임워크
org.springframework.jdbc.core.JdbcTemplate.update:1010 - 스프링 프레임워크
org.springframework.jdbc.core.JdbcTemplate.update:1020 - 내 코드
com.example.example.service.OrderService.place:18 - JDK
java.lang.invoke.LambdaForm$DMH/0x000002d7c0002800.invokeVirtual - JDK
java.lang.invoke.LambdaForm$MH/0x000002d7c05fc800.invoke - JDK
java.lang.invoke.Invokers$Holder.invokeExact_MT - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl:154 - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invoke:103 - JDK
java.lang.reflect.Method.invoke:580 - 스프링 프레임워크
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection:359 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint:190 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed:158 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation:133 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction:371 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionInterceptor.invoke:130 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed:179 - 스프링 프레임워크
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept:719 - 스프링이 만든 클래스
com.example.example.service.OrderService$$SpringCGLIB$$0.place - 내 코드
com.example.example.controller.OrderController.place:25 - JDK
java.lang.invoke.LambdaForm$DMH/0x000002d7c0158c00.invokeVirtual - JDK
java.lang.invoke.LambdaForm$MH/0x000002d7c0369800.invoke - JDK
java.lang.invoke.Invokers$Holder.invokeExact_MT - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl:154 - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invoke:103 - JDK
java.lang.reflect.Method.invoke:580 - 스프링 프레임워크
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke:252 - 스프링 프레임워크
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest:184 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle:117 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod:934 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal:853 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle:86 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doDispatch:963 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doService:866 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.processRequest:1000 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.doPost:903 - 톰캣
jakarta.servlet.http.HttpServlet.service:649 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.service:874 - 톰캣
jakarta.servlet.http.HttpServlet.service:710 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:128 - 톰캣
org.apache.tomcat.websocket.server.WsFilter.doFilter:53 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.RequestContextFilter.doFilterInternal:100 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.FormContentFilter.doFilterInternal:93 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal:110 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal:199 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 톰캣
org.apache.catalina.core.StandardWrapperValve.invoke:165 - 톰캣
org.apache.catalina.core.StandardContextValve.invoke:77 - 톰캣
org.apache.catalina.authenticator.AuthenticatorBase.invoke:492 - 톰캣
org.apache.catalina.core.StandardHostValve.invoke:113 - 톰캣
org.apache.catalina.valves.ErrorReportValve.invoke:83 - 톰캣
org.apache.catalina.core.StandardEngineValve.invoke:72 - 톰캣
org.apache.catalina.connector.CoyoteAdapter.service:341 - 톰캣
org.apache.coyote.http11.Http11Processor.service:397 - 톰캣
org.apache.coyote.AbstractProcessorLight.process:63 - 톰캣
org.apache.coyote.AbstractProtocol$ConnectionHandler.process:903 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun:1801 - 톰캣
org.apache.tomcat.util.net.SocketProcessorBase.run:52 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker:946 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run:480 - 톰캣
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run:57 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
5. 트랜잭션을 커밋한다 http-nio-18080-exec-2 · 64칸 · 멈춘 곳 org.h2.jdbc.JdbcConnection.commit
- H2
org.h2.jdbc.JdbcConnection.commit:481 - HikariCP
com.zaxxer.hikari.pool.ProxyConnection.commit:378 - HikariCP
com.zaxxer.hikari.pool.HikariProxyConnection.commit - 스프링 프레임워크
org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit:343 - 스프링 프레임워크
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit:794 - 스프링 프레임워크
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit:757 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning:687 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction:408 - 스프링 프레임워크
org.springframework.transaction.interceptor.TransactionInterceptor.invoke:130 - 스프링 프레임워크
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed:179 - 스프링 프레임워크
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept:719 - 스프링이 만든 클래스
com.example.example.service.OrderService$$SpringCGLIB$$0.place - 내 코드
com.example.example.controller.OrderController.place:25 - JDK
java.lang.invoke.LambdaForm$DMH/0x000002d7c0158c00.invokeVirtual - JDK
java.lang.invoke.LambdaForm$MH/0x000002d7c0369800.invoke - JDK
java.lang.invoke.Invokers$Holder.invokeExact_MT - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl:154 - JDK
jdk.internal.reflect.DirectMethodHandleAccessor.invoke:103 - JDK
java.lang.reflect.Method.invoke:580 - 스프링 프레임워크
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke:252 - 스프링 프레임워크
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest:184 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle:117 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod:934 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal:853 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle:86 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doDispatch:963 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doService:866 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.processRequest:1000 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.doPost:903 - 톰캣
jakarta.servlet.http.HttpServlet.service:649 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.service:874 - 톰캣
jakarta.servlet.http.HttpServlet.service:710 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:128 - 톰캣
org.apache.tomcat.websocket.server.WsFilter.doFilter:53 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.RequestContextFilter.doFilterInternal:100 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.FormContentFilter.doFilterInternal:93 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal:110 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal:199 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 톰캣
org.apache.catalina.core.StandardWrapperValve.invoke:165 - 톰캣
org.apache.catalina.core.StandardContextValve.invoke:77 - 톰캣
org.apache.catalina.authenticator.AuthenticatorBase.invoke:492 - 톰캣
org.apache.catalina.core.StandardHostValve.invoke:113 - 톰캣
org.apache.catalina.valves.ErrorReportValve.invoke:83 - 톰캣
org.apache.catalina.core.StandardEngineValve.invoke:72 - 톰캣
org.apache.catalina.connector.CoyoteAdapter.service:341 - 톰캣
org.apache.coyote.http11.Http11Processor.service:397 - 톰캣
org.apache.coyote.AbstractProcessorLight.process:63 - 톰캣
org.apache.coyote.AbstractProtocol$ConnectionHandler.process:903 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun:1801 - 톰캣
org.apache.tomcat.util.net.SocketProcessorBase.run:52 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker:946 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run:480 - 톰캣
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run:57 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
6. JSON 바이트를 톰캣에 넘긴다 http-nio-18080-exec-2 · 54칸 · 멈춘 곳 org.apache.catalina.connector.CoyoteOutputStream.write
- 톰캣
org.apache.catalina.connector.CoyoteOutputStream.write:97 - 스프링 프레임워크
org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleServletOutputStream.write:396 - 스프링 프레임워크
org.springframework.util.StreamUtils$NonClosingOutputStream.write:311 - Jackson
tools.jackson.core.json.UTF8JsonGenerator._flushBuffer:2341 - Jackson
tools.jackson.core.json.UTF8JsonGenerator.flush:1235 - Jackson
tools.jackson.databind.ObjectWriter.writeValue:984 - 스프링 프레임워크
org.springframework.http.converter.AbstractJacksonHttpMessageConverter.writeInternal:436 - 스프링 프레임워크
org.springframework.http.converter.AbstractSmartHttpMessageConverter.write:116 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters:342 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue:213 - 스프링 프레임워크
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue:77 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle:135 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod:934 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal:853 - 스프링 프레임워크
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle:86 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doDispatch:963 - 스프링 프레임워크
org.springframework.web.servlet.DispatcherServlet.doService:866 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.processRequest:1000 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.doPost:903 - 톰캣
jakarta.servlet.http.HttpServlet.service:649 - 스프링 프레임워크
org.springframework.web.servlet.FrameworkServlet.service:874 - 톰캣
jakarta.servlet.http.HttpServlet.service:710 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:128 - 톰캣
org.apache.tomcat.websocket.server.WsFilter.doFilter:53 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.RequestContextFilter.doFilterInternal:100 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.FormContentFilter.doFilterInternal:93 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal:110 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 스프링 프레임워크
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal:199 - 스프링 프레임워크
org.springframework.web.filter.OncePerRequestFilter.doFilter:116 - 톰캣
org.apache.catalina.core.ApplicationFilterChain.doFilter:107 - 톰캣
org.apache.catalina.core.StandardWrapperValve.invoke:165 - 톰캣
org.apache.catalina.core.StandardContextValve.invoke:77 - 톰캣
org.apache.catalina.authenticator.AuthenticatorBase.invoke:492 - 톰캣
org.apache.catalina.core.StandardHostValve.invoke:113 - 톰캣
org.apache.catalina.valves.ErrorReportValve.invoke:83 - 톰캣
org.apache.catalina.core.StandardEngineValve.invoke:72 - 톰캣
org.apache.catalina.connector.CoyoteAdapter.service:341 - 톰캣
org.apache.coyote.http11.Http11Processor.service:397 - 톰캣
org.apache.coyote.AbstractProcessorLight.process:63 - 톰캣
org.apache.coyote.AbstractProtocol$ConnectionHandler.process:903 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun:1801 - 톰캣
org.apache.tomcat.util.net.SocketProcessorBase.run:52 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker:946 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run:480 - 톰캣
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run:57 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
7. 바이트가 소켓으로 나간다 http-nio-18080-exec-2 · 25칸 · 멈춘 곳 sun.nio.ch.SocketChannelImpl.write
- JDK
sun.nio.ch.SocketChannelImpl.write:533 - 톰캣
org.apache.tomcat.util.net.NioChannel.write:129 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite:1441 - 톰캣
org.apache.tomcat.util.net.SocketWrapperBase.doWrite:747 - 톰캣
org.apache.tomcat.util.net.SocketWrapperBase.flushBlocking:713 - 톰캣
org.apache.tomcat.util.net.SocketWrapperBase.flush:698 - 톰캣
org.apache.coyote.http11.Http11OutputBuffer$SocketOutputBuffer.end:558 - 톰캣
org.apache.coyote.http11.filters.IdentityOutputFilter.end:121 - 톰캣
org.apache.coyote.http11.Http11OutputBuffer.end:230 - 톰캣
org.apache.coyote.http11.Http11Processor.finishResponse:1237 - 톰캣
org.apache.coyote.AbstractProcessor.action:386 - 톰캣
org.apache.coyote.Response.action:197 - 톰캣
org.apache.catalina.connector.OutputBuffer.close:241 - 톰캣
org.apache.catalina.connector.Response.finishResponse:386 - 톰캣
org.apache.catalina.connector.CoyoteAdapter.service:370 - 톰캣
org.apache.coyote.http11.Http11Processor.service:397 - 톰캣
org.apache.coyote.AbstractProcessorLight.process:63 - 톰캣
org.apache.coyote.AbstractProtocol$ConnectionHandler.process:903 - 톰캣
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun:1801 - 톰캣
org.apache.tomcat.util.net.SocketProcessorBase.run:52 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker:946 - 톰캣
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run:480 - 톰캣
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run:57 - JDK
java.lang.Thread.runWith:1596 - JDK
java.lang.Thread.run:1583
- 그림 바로 밑의 로그가 이 그림의 손잡이다. 머리줄(「1. 연결을 받는다」 같은 줄)을 누르면 그 차례에 멈춘 순간으로 가고, 그 아래 줄(스택의 칸 하나)을 누르면 같은 순간에서 그 칸이 든 막대에 테두리를 두르고 멈춤 줄 옆에 그 칸의 이름을 단다. 이전 · 다음으로 단계를 넘기면 로그도 지금 차례의 머리줄로 굴러간다.
- 맨 위 이름 칸이 참여자(프로젝트)다. 칸의 차례와 색은 위 2D 그림의 범례와 같고, 차례에는 뜻이 없다. 칸에서 내려오는 회색 세로 점선은 자리를 잇는 줄일 뿐이다.
- 아래로 갈수록 나중이다. 다만 세로 간격은 일마다 같게 두어서, 막대 길이는 걸린 시간이 아니다.
- 실선 화살표는 다른 프로젝트의 메서드를 부른 것이다. 화살표 위 이름은 화살표가 가리키는 프로젝트에 들어가 처음 실행된 메서드다. 같은 프로젝트 안에서 부른 것은 그리지 않아서, 화살표 수가 스택의 칸 수보다 훨씬 적다.
- 점선 화살표는 그 프로젝트의 메서드가 다 돌아온 것이다.
- 세로 막대는 그 프로젝트의 메서드가 스택에 있는 동안이다 — 실행 중이거나, 자기가 부른 메서드가 돌아오기를 기다리는 중이다. 한 참여자 밑에 막대가 조금씩 비켜 겹쳐 있으면 다른 프로젝트를 거쳐 다시 들어온 것이다. 점선 화살표 없이 상자 바닥까지 가는 막대는, 마지막으로 멈춘 뒤를 안 봐서 돌아왔는지 모르는 것이다.
- 바깥 상자 하나가 스레드 하나다. 상자마다 첫 화살표는 왼쪽 끝의 점, 곧 스레드가 시작한 곳(Thread.run)에서 나온다. Acceptor 가 받은 연결을 Poller 가 지켜보다 일꾼 스레드(exec-2)에게 넘기는데, 이것은 메서드 호출이 아니라서 상자 사이에 화살표가 없다.
- 노란 점선 「멈춤 N · M칸」은 디버거가 멈춘 순간이고, M 은 그때 스택 전체의 칸 수다(같은 프로젝트 안의 칸까지 센다). 그 줄 위의 노란 점은 그때 스택 꼭대기의 메서드가 든 막대에 찍었다.
- 단계를 넘기면 그림이 그 단계의 화살표까지 아래로 따라가고, 지난 단계의 화살표와 돌아온 막대는 흐리게 둔다. 마지막 단계는 전체를 줄여 보인다.
- 보통의 시퀀스 다이어그램은 실제로 지나간 호출을 다 적어 그리지만, 이 그림은 멈춘 순간의 스택을 차례로 견줘 맞춘 것이다. 앞 차례에만 있던 칸은 그 사이에 돌아왔고(스택 꼭대기부터), 새로 생긴 칸은 그 뒤에 불렸다(바닥 쪽부터). 그래서 멈춘 사이에 부르고 돌아온 호출은 안 보이고, 막대가 이어져 보여도 그 사이에 돌아왔다가 다시 불렸을 수 있다.
- 화살표나 막대에 마우스를 올리면 클래스의 전체 이름이 뜬다.
마지막 단계는 멈춘 7번을 한데 모은 모습과, 프로젝트마다 칸이 몇 개 나왔는지(이어진 칸은 한 번만 센다)를 보여 준다. 거기서 스프링 부트 소스는 한 칸도 안 나온다. 스프링 부트와 스프링 프레임워크는 다른 프로젝트다. 빈 · 웹 · 트랜잭션을 실제로 처리하는 코드는 프레임워크에 있고, 부트는 그것을 설정 없이 켜 주는 쪽이다(1–5단계). 그렇다고 「부트는 요청 때 아무 일도 안 한다」로 읽지는 않는다. 이 요청은 성공한 요청 하나다. 7단계의 /nope처럼 404가 나면 부트의 BasicErrorController가 답한다. 확인한 것은 이 요청의 이 7순간에는 부트 소스의 줄이 스택에 없었다는 데까지다.