2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

Spring MVC 동작 원리

2026. 4. 22. 15:08ㆍ스프링

데일리 CS 학습

더보기
  • Q. 객체지향의 4가지 특징을 설명해주세요.
  • A. 캡슐화: 데이터와 메서드를 하나로 묶고 접근 제어자로 외부에 노출할 것만 공개
    상속: 부모 클래스의 속성, 기능을 자식이 물려받아 재사용성 향상
    다형성: 같은 타입 참조변수로 여러 구현체를 다루는 것(오버라이딩, 인터페이스)
    추상화: 불필요한 세부사항을 숨기고 핵심 개념만 드러내는 것

  • Q. 인터페이스와 추상 클래스의 차이점과 각각 어떤 상황에 사용하는지 말해주세요.
  • A. 추상클래스는 단일 상속만 가능하고 상태와 일반 메서드 포함 가능하다. 공통 구현을 공유할 때 사용합니다.
    인터페이스는 다중 구현이 가능하고 서로 다른 계층에 같은 역할을 부여할 때 사용합니다.
    핵심 원칙 : 공통 구현 공유 -> 추상 클래스, 역할 명세 -> 인터페이스

  • Q. 오버로딩과 오버라이딩을 비교 설명해주세요
  • A. 오버로딩은 같은 이름의 메서드를 매개변수만 다르게 여러 개 정의하는 것으로, 컴파일 시점에 결정됩니다. 오버라이딩은 부모 메서드를 자식에서 재정의하는 것으로 런타임에 결정되고 다형성의 핵심입니다.

  • Q. JVM의 메모리 구조를 설명해주세요.
  • A. 크게 세 가지입니다. Method Area에는 클래스 메타데이터와 static 변수가, Heap에는 new로 생성된 객체가 저장되고 GC가 관리합니다. Stack은 스레드마다 존재하며 메서드 호출 시 지역변수와 매개변수를 저장하고, 메서드 종료 시 자동으로 해제됩니다.

  • Q. GC란 무엇인지 설명해주세요.
  • A. Heap에서 더 이상 참조되지 않는 객체를 자동으로 메모리에서 해제하는 기능입니다.

  • Q. final 키워드를 변수,메서드,클래스에 사용하면 각각 어떻게 되나요?
  • A. final 변수는 초기화 후 재할당 불가, final 메서드는 오버라이딩 불가, final 클래스는 상속 불가입니다. String,Integer 같은 클래스가 final인 이유가 상속을 막아 불변성을 보장하기 위함입니다. 멀티스레드 환경에서 안전한 불변 객체를 설계할 때 주로 활용합니다.

  • Q. 접근 제어자 4가지를 설명하고 캡슐화의 관계를 말해주세요.
  • A. 넓은 순서로 public -> protected -> default -> private입니다. 캡슐화의 관점에서는 필드를 private로 숨기고 getter/setter만 공개합니다. 이렇게 하면 내부 구현을 바꿔도 외부에 영향이 없고 접근 시 유효성 검증도 추가할 수 있습니다.

 

 

Today I Learned · 2026 · Spring Backend

Spring MVC 동작 원리
DispatcherServlet · HandlerMapping · ArgumentResolver · MessageConverter

요청 하나가 들어와서 응답으로 나가기까지 — 스프링이 내부에서 하는 모든 일을 단계별로 파헤친다

Java Spring Boot Spring MVC DispatcherServlet HTTP

01 Spring MVC 전체 요청 흐름

HTTP 요청이 들어와 응답으로 나가기까지 Spring MVC 내부에서 일어나는 전체 흐름입니다. 이 흐름을 이해하는 것이 Spring 웹 개발의 핵심입니다.

① HTTP 요청
클라이언트(브라우저/앱)가 GET /members/1 요청 전송 → 서블릿 컨테이너(Tomcat)가 수신
② Dispatcher
Servlet
프론트 컨트롤러. 모든 요청의 진입점. 적절한 핸들러를 찾아 위임하고 응답을 조율하는 총괄 역할
③ Handler
Mapping
URL + HTTP 메서드로 어떤 컨트롤러 메서드를 실행할지 결정. @RequestMapping 정보를 기반으로 매핑
④ Handler
Adapter
핸들러 실행 전담. ArgumentResolver로 파라미터를 바인딩하고 실제 컨트롤러 메서드를 호출
⑤ Argument
Resolver
@RequestBody, @PathVariable, @RequestParam 등 어노테이션을 보고 HTTP 요청 데이터를 메서드 파라미터로 변환
⑥ Controller
비즈니스 로직 실행. Service를 호출하고 결과 반환. @RestController는 반환값을 HTTP 응답 바디에 직접 직렬화
⑦ Message
Converter
Java 객체 ↔ JSON/XML 변환. 요청 시 JSON→객체, 응답 시 객체→JSON. 기본값은 Jackson MappingJackson2HttpMessageConverter
⑧ HTTP 응답
직렬화된 JSON이 HTTP 응답 바디에 담겨 클라이언트로 전송. Content-Type: application/json

02 DispatcherServlet — 프론트 컨트롤러

DispatcherServlet은 Spring MVC의 프론트 컨트롤러(Front Controller)입니다. 모든 HTTP 요청을 단일 진입점에서 받아 적절한 핸들러에게 위임하고 응답을 조율합니다.

프론트 컨트롤러 패턴이란?
과거에는 각 URL마다 별도의 서블릿을 만들어 관리했습니다. 프론트 컨트롤러 패턴은 단 하나의 서블릿이 모든 요청을 받고, 공통 처리(인증, 로깅, 인코딩 등)를 중앙화한 뒤 개별 컨트롤러에 위임합니다. Spring Boot는 자동으로 /* 경로로 DispatcherServlet을 등록합니다.
// DispatcherServlet 핵심 로직 (개념적 표현) protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { // 1. HandlerMapping으로 핸들러(컨트롤러 메서드) 조회 HandlerExecutionChain handler = getHandler(request); // 2. 핸들러를 실행할 수 있는 HandlerAdapter 조회 HandlerAdapter adapter = getHandlerAdapter(handler.getHandler()); // 3. 인터셉터 preHandle 실행 handler.applyPreHandle(request, response); // 4. HandlerAdapter로 핸들러 실행 → ModelAndView 반환 ModelAndView mv = adapter.handle(request, response, handler.getHandler()); // 5. 인터셉터 postHandle 실행 handler.applyPostHandle(request, response, mv); // 6. 뷰 렌더링 or @ResponseBody 응답 처리 processDispatchResult(request, response, handler, mv, null); }Java

Spring Boot에서 DispatcherServlet 자동 등록

# application.yml — DispatcherServlet 경로 변경 (기본값: /) spring: mvc: servlet: path: /api # /api/* 경로만 DispatcherServlet이 처리YAML

03 HandlerMapping — 핸들러 조회

요청 URL과 HTTP 메서드를 보고 어떤 컨트롤러의 어떤 메서드를 호출할지 결정합니다. 스프링은 여러 HandlerMapping을 우선순위 순으로 순회하며 핸들러를 찾습니다.

RequestMappingHandlerMapping
우선순위 0 — 가장 많이 사용

@RequestMapping, @GetMapping 등 어노테이션 기반 매핑. 실무에서 사실상 유일하게 사용.

BeanNameUrlHandlerMapping
우선순위 2 — 레거시

스프링 빈 이름이 URL 경로와 일치할 때 매핑. 과거 XML 설정 방식에서 사용. 현재는 거의 안 씀.

// RequestMappingHandlerMapping이 처리하는 어노테이션들 @RestController @RequestMapping("/members") public class MemberController { @GetMapping("/{id}") // GET /members/{id} public MemberDto get(...) { ... } @PostMapping // POST /members public MemberDto create(...) { ... } @PutMapping("/{id}") // PUT /members/{id} public MemberDto update(...) { ... } @DeleteMapping("/{id}") // DELETE /members/{id} public void delete(...) { ... } } // HandlerMapping이 반환하는 것은 핸들러 + 인터셉터 목록 // HandlerExecutionChain = Handler(컨트롤러 메서드) + List<HandlerInterceptor>Java

04 HandlerAdapter — 핸들러 실행

HandlerAdapter는 DispatcherServlet이 다양한 종류의 핸들러를 일관된 방식으로 실행할 수 있도록 중간에서 변환해주는 어댑터입니다. 어댑터 패턴의 전형적인 적용입니다.

HandlerAdapter 처리 대상 사용 여부
RequestMappingHandlerAdapter @RequestMapping 어노테이션 기반 핸들러 ✅ 현재 표준
HttpRequestHandlerAdapter HttpRequestHandler 인터페이스 구현체 △ 정적 리소스 처리 등
SimpleControllerHandlerAdapter Controller 인터페이스 구현체 (구형) ❌ 레거시
왜 HandlerAdapter가 필요한가?
DispatcherServlet은 핸들러의 구체적인 타입을 모릅니다. 어노테이션 기반 컨트롤러, 인터페이스 기반 컨트롤러 등 다양한 핸들러가 존재하기 때문에 각 타입에 맞는 어댑터가 실행을 위임받아 처리합니다. DispatcherServlet 입장에서는 어댑터를 통해 항상 동일한 방식으로 핸들러를 실행할 수 있습니다.

05 ArgumentResolver — 파라미터 바인딩

HandlerMethodArgumentResolver는 컨트롤러 메서드의 파라미터를 HTTP 요청에서 추출해 바인딩해주는 역할입니다. 어노테이션 종류에 따라 서로 다른 ArgumentResolver가 동작합니다.

// 각 어노테이션마다 전담 ArgumentResolver가 존재 @GetMapping("/members") public List<MemberDto> getMembers( @RequestParam String name, // 쿼리 파라미터 (?name=현수) @RequestParam("page") int page, // 쿼리 파라미터 이름 명시 @PathVariable Long id, // URL 경로 변수 (/members/1) @RequestBody MemberCreateRequest req, // HTTP 바디 (JSON → 객체) @RequestHeader("Authorization") String token, // HTTP 헤더 @CookieValue("SESSION") String sessionId, // 쿠키 @ModelAttribute SearchCondition cond, // 폼 데이터 → 객체 HttpServletRequest request, // 서블릿 요청 객체 직접 주입 @AuthenticationPrincipal UserDetails user // Spring Security 인증 객체 ) { ... }Java

커스텀 ArgumentResolver 구현

직접 ArgumentResolver를 만들어 등록하면 특정 어노테이션이 붙은 파라미터를 원하는 방식으로 처리할 수 있습니다. 예를 들어 @LoginMember 어노테이션으로 세션에서 로그인 유저를 자동 주입하는 패턴입니다.

// 1. 커스텀 어노테이션 정의 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface LoginMember { } // 2. ArgumentResolver 구현 @Component public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { // @LoginMember 어노테이션이 붙은 파라미터만 처리 return parameter.hasParameterAnnotation(LoginMember.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { // 세션에서 로그인 멤버 꺼내서 반환 HttpSession session = ((HttpServletRequest) webRequest.getNativeRequest()).getSession(); return session.getAttribute("loginMember"); } } // 3. WebMvcConfigurer에 등록 @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new LoginMemberArgumentResolver()); } } // 4. 컨트롤러에서 사용 — 세션 꺼내는 코드가 사라짐! @GetMapping("/my-page") public MyPageDto myPage(@LoginMember Member loginMember) { return memberService.getMyPage(loginMember.getId()); }Java

06 MessageConverter — HTTP 메시지 변환

HttpMessageConverterHTTP 요청 바디 ↔ Java 객체 간 변환을 담당합니다. @RequestBody로 요청 JSON을 객체로, @ResponseBody로 객체를 응답 JSON으로 변환합니다.

Content-Type에 따른 MessageConverter 선택

MessageConverter 처리 미디어 타입 비고
MappingJackson2HttpMessageConverter application/json 기본값, Jackson 사용
StringHttpMessageConverter text/plain, */* String 타입 변환
ByteArrayHttpMessageConverter application/octet-stream byte[] 타입 변환, 파일 처리
Jaxb2RootElementHttpMessageConverter application/xml XML 변환, JAXB 사용

Content Negotiation — 어떻게 컨버터를 선택하나

// 요청 시 (@RequestBody): Content-Type 헤더로 읽기 컨버터 결정 // Content-Type: application/json → MappingJackson2 선택 → JSON → 객체 // 응답 시 (@ResponseBody): Accept 헤더 + 미디어타입으로 쓰기 컨버터 결정 // Accept: application/json → MappingJackson2 선택 → 객체 → JSON // produces 설정으로 응답 타입 고정 @GetMapping(value = "/members/{id}", produces = "application/json") public MemberDto getMember(@PathVariable Long id) { ... } // consumes 설정으로 요청 타입 제한 @PostMapping(value = "/members", consumes = "application/json") public MemberDto create(@RequestBody MemberCreateRequest req) { ... } // Jackson 커스텀 설정 @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper() .registerModule(new JavaTimeModule()) // LocalDateTime 직렬화 .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // ISO-8601 포맷 .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } }Java

07 ReturnValueHandler — 반환값 처리

HandlerMethodReturnValueHandler는 컨트롤러 메서드의 반환값을 HTTP 응답으로 변환합니다. ArgumentResolver의 반대 방향입니다.

// 반환 타입에 따라 처리 방식이 달라짐 // 1. ResponseEntity — 상태코드 + 헤더 + 바디 직접 제어 @GetMapping("/{id}") public ResponseEntity<MemberDto> getMember(@PathVariable Long id) { return ResponseEntity.ok() // 200 OK .header("X-Custom", "value") // 커스텀 헤더 .body(memberService.findById(id)); // 응답 바디 } // 2. 객체 직접 반환 — @ResponseBody에 의해 자동으로 JSON 직렬화 @GetMapping("/{id}") public MemberDto getMember(@PathVariable Long id) { return memberService.findById(id); // 200 OK + JSON 자동 변환 } // 3. void — 응답 바디 없음 (204 No Content) @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable Long id) { memberService.delete(id); } // 4. String — @Controller(뷰 이름 반환) vs @RestController(문자열 그대로 응답) @Controller public String home() { return "home"; // ViewResolver가 templates/home.html을 찾아 렌더링 }Java

08 Filter vs Interceptor vs AOP

요청/응답 전후에 공통 처리를 끼워넣는 방법이 세 가지 있습니다. 어느 시점에, 어느 범위에서 동작하는지가 핵심 차이입니다.

동작 위치
Filter
서블릿 컨테이너
Interceptor
Dispatcher
Servlet 이후
AOP
Service/Repository
메서드 단위
Controller
비즈니스 로직
구분 Filter Interceptor AOP
동작 위치 서블릿 컨테이너 DispatcherServlet 이후 스프링 빈 메서드 단위
스프링 컨텍스트 접근 어려움 접근 가능 접근 가능
적용 단위 URL 패턴 URL 패턴 + 컨트롤러 어노테이션 / 패키지 / 메서드
예외 처리 직접 처리 필요 @ControllerAdvice로 처리 가능 @ControllerAdvice로 처리 가능
주요 용도 인코딩, CORS, XSS 방어, 보안 인증/인가, 로깅, API 통계 트랜잭션, 로깅, 성능 측정

Interceptor 구현 예시

// 로그인 여부 확인 인터셉터 @Component public class LoginCheckInterceptor implements HandlerInterceptor { // 컨트롤러 실행 전 — false 반환 시 이후 처리 중단 @Override public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) { HttpSession session = req.getSession(false); if (session == null || session.getAttribute("loginMember") == null) { res.sendRedirect("/login"); return false; // 컨트롤러 실행 안 함 } return true; } // 컨트롤러 실행 후, 뷰 렌더링 전 @Override public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView mv) { } // 뷰 렌더링 후 — 예외 발생 여부와 무관하게 항상 실행 @Override public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) { // 리소스 정리, 요청 로그 마무리 등 } } // WebMvcConfigurer에 등록 @Configuration @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { private final LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor) .addPathPatterns("/**") // 모든 경로에 적용 .excludePathPatterns("/login", "/join", "/css/**"); // 제외 경로 } }Java

09 면접 예상 질문

Q1. Spring MVC의 요청 처리 흐름을 설명하세요.

HTTP 요청이 들어오면 서블릿 컨테이너가 DispatcherServlet에 전달합니다. DispatcherServlet은 HandlerMapping으로 URL에 맞는 컨트롤러 메서드를 찾고, HandlerAdapter를 통해 해당 핸들러를 실행합니다. 실행 전 ArgumentResolver가 파라미터를 바인딩하고, MessageConverter가 요청 JSON을 객체로 역직렬화합니다. 컨트롤러 실행 후 반환값을 MessageConverter가 다시 JSON으로 직렬화해 응답합니다.

Q2. DispatcherServlet이 프론트 컨트롤러인 이유는?

프론트 컨트롤러 패턴은 모든 요청이 단일 진입점을 통해 처리되는 패턴입니다. DispatcherServlet이 모든 HTTP 요청(/*)을 받아 인코딩, 예외처리, HandlerMapping 등 공통 처리를 중앙화하고 개별 컨트롤러에 위임합니다. 덕분에 각 컨트롤러가 공통 관심사를 직접 처리하지 않아도 됩니다.

Q3. ArgumentResolver와 MessageConverter의 차이점은?

ArgumentResolver는 컨트롤러 메서드의 파라미터를 어노테이션 기반으로 처리합니다. @PathVariable, @RequestParam, @RequestBody 등 다양한 소스에서 데이터를 추출합니다. MessageConverter@RequestBody/@ResponseBody가 있을 때 HTTP 바디의 JSON과 Java 객체 간 변환만 담당합니다. 즉 ArgumentResolver가 MessageConverter를 호출해 사용하는 관계입니다.

Q4. Filter와 Interceptor의 차이와 각각의 사용 사례는?

Filter는 서블릿 컨테이너 레벨에서 동작해 스프링 컨텍스트 밖에 위치합니다. 스프링 빈에 직접 접근하기 어렵고 @ControllerAdvice의 예외 처리가 적용되지 않습니다. CORS, 인코딩 설정, XSS 방어 등 서블릿 레벨 공통 처리에 적합합니다. Interceptor는 DispatcherServlet 이후 스프링 컨텍스트 내부에서 동작해 스프링 빈에 접근 가능하고 @ControllerAdvice가 적용됩니다. 인증/인가, API 로깅, 요청 통계 등 스프링 빈이 필요한 처리에 적합합니다.

Q5. @Controller와 @RestController의 차이는?

@Controller는 메서드 반환값을 뷰 이름으로 처리하고 ViewResolver가 템플릿 파일을 찾아 렌더링합니다. @RestController@Controller + @ResponseBody로, 반환값을 MessageConverter가 JSON으로 직렬화해 HTTP 응답 바디에 직접 씁니다. REST API 개발에서는 @RestController가 표준입니다.
핵심 요약
  • DispatcherServlet — 모든 요청의 단일 진입점(프론트 컨트롤러). HandlerMapping → HandlerAdapter → 응답까지 전체 흐름 조율
  • HandlerMapping — URL + HTTP 메서드로 핸들러 결정. 실무에서는 RequestMappingHandlerMapping만 사용
  • HandlerAdapter — 다양한 핸들러 타입을 일관된 방식으로 실행. 어댑터 패턴 적용
  • ArgumentResolver — 어노테이션 기반 파라미터 바인딩. 커스텀 구현으로 @LoginMember 같은 패턴 만들 수 있음
  • MessageConverter — @RequestBody/@ResponseBody 시 JSON ↔ 객체 변환. Content-Type / Accept 헤더로 컨버터 선택
  • Filter vs Interceptor — Filter는 서블릿 컨테이너 레벨(CORS, 인코딩), Interceptor는 스프링 컨텍스트 내부(인증, 로깅)
오늘의 핵심 — 요청 하나가 들어올 때 DispatcherServlet이 HandlerMapping → HandlerAdapter → ArgumentResolver → MessageConverter 순으로 위임한다. 각 컴포넌트의 역할 분리가 Spring MVC의 핵심 설계다.
728x90