[Spring] MVC 실행 흐름의 완벽 이해

슈퍼대디·2024년 12월 24일

CS면접대비

목록 보기
10/13

Spring MVC 실행 흐름의 모든것

목차

  1. 기본 실행 흐름
  2. 요청 처리 단계 상세 분석
  3. 필터와 인터셉터
  4. 메시지 컨버터와 파라미터 바인딩
  5. 예외 처리 흐름
  6. 면접 예상 질문

1. 기본 실행 흐름

전체 흐름도

상세 실행 단계

  1. 클라이언트 요청
  2. 필터 체인 통과
  3. DispatcherServlet이 요청 접수
  4. HandlerMapping을 통해 적절한 Controller 탐색
  5. HandlerAdapter를 통해 Controller 메서드 실행
  6. Controller에서 로직 처리 후 결과 반환
  7. ViewResolver를 통해 View 탐색
  8. View 렌더링
  9. 응답 반환

2. 요청 처리 단계 상세 분석

DispatcherServlet의 동작

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    
    // 1. Handler 조회
    mappedHandler = getHandler(processedRequest);
    
    // 2. HandlerAdapter 조회
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    // 3. 인터셉터 실행
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    
    // 4. 핸들러 실행
    ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    // 5. 인터셉터 실행
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    
    // 6. 뷰 렌더링
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

HandlerMapping 과정

  1. RequestMappingHandlerMapping

    • @RequestMapping 기반 매핑
    • URL 패턴, HTTP 메서드 등 고려
  2. BeanNameUrlHandlerMapping

    • 빈 이름과 URL 매핑
    • 레거시 지원용

3. 필터와 인터셉터

Filter

  • 서블릿 스펙의 기능
  • 스프링 컨텍스트 외부에서 동작
  • 요청/응답 객체 자체를 변경 가능
@Component
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 요청 로깅
        log.info("Request URI: {}", httpRequest.getRequestURI());
        
        chain.doFilter(request, response);
        
        // 응답 로깅
        log.info("Response Status: {}", ((HttpServletResponse) response).getStatus());
    }
}

Interceptor

  • 스프링 MVC의 기능
  • 스프링 컨텍스트 내부에서 동작
  • 더 정교한 요청 처리 가능
@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
            Object handler) throws Exception {
        // 컨트롤러 실행 전
        String token = request.getHeader("Authorization");
        if (token == null) {
            response.sendError(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
            Object handler, ModelAndView modelAndView) throws Exception {
        // 컨트롤러 실행 후, 뷰 렌더링 전
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
            Object handler, Exception ex) throws Exception {
        // 뷰 렌더링 후
    }
}

4. 메시지 컨버터와 파라미터 바인딩

HttpMessageConverter

HTTP 메시지 바디와 자바 객체 간의 변환을 담당하는 인터페이스입니다.

주요 구현체

  1. MappingJackson2HttpMessageConverter

    • JSON <-> 자바 객체 변환
    • Jackson 라이브러리 사용
    @PostMapping("/api/users")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        // {"name": "John", "age": 30} -> User 객체
        return ResponseEntity.ok(user);  // User 객체 -> JSON
    }
  2. StringHttpMessageConverter

    • 문자열 데이터 처리
    @PostMapping("/api/text")
    public String handleText(@RequestBody String text) {
        // Plain text 처리
        return "received: " + text;
    }
  3. FormHttpMessageConverter

    • Form 데이터 처리
    @PostMapping("/api/form")
    public String handleForm(@RequestBody MultiValueMap<String, String> formData) {
        // application/x-www-form-urlencoded 처리
        return "name: " + formData.getFirst("name");
    }

동작 방식

  1. 요청 처리

    1. Content-Type 헤더 확인
    2. 적절한 MessageConverter 선택
    3. HTTP 메시지 -> 자바 객체 변환
  2. 응답 처리

    1. 반환 타입 확인
    2. Accept 헤더 확인
    3. 적절한 MessageConverter 선택
    4. 자바 객체 -> HTTP 메시지 변환

ArgumentResolver

컨트롤러의 파라미터를 처리하는 인터페이스입니다.

주요 구현체

  1. RequestParamMethodArgumentResolver

    • @RequestParam 처리
    @GetMapping("/api/search")
    public List<Item> searchItems(@RequestParam String keyword) {
        // ?keyword=book -> String "book"
        return itemService.search(keyword);
    }
  2. PathVariableMethodArgumentResolver

    • @PathVariable 처리
    @GetMapping("/api/users/{id}")
    public User getUser(@PathVariable Long id) {
        // /api/users/123 -> Long 123
        return userService.findById(id);
    }
  3. RequestResponseBodyMethodProcessor

    • @RequestBody, @ResponseBody 처리
    • MessageConverter와 연동
    @PostMapping("/api/users")
    @ResponseBody
    public User createUser(@RequestBody User user) {
        // HTTP 메시지 바디 -> User 객체
        return userService.create(user);
    }

커스텀 ArgumentResolver 구현

특별한 파라미터 처리가 필요한 경우 구현할 수 있습니다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}

@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
    private final UserService userService;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class) &&
               parameter.getParameterType().equals(User.class);
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter, 
                                ModelAndViewContainer mavContainer,
                                NativeWebRequest webRequest, 
                                WebDataBinderFactory binderFactory) {
        String token = webRequest.getHeader("Authorization");
        // 토큰에서 사용자 정보 추출
        return userService.getUserFromToken(token);
    }
}

// 사용 예시
@GetMapping("/api/my-info")
public User getMyInfo(@CurrentUser User user) {
    // 현재 로그인한 사용자 정보 자동 주입
    return user;
}

설정 방법

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CurrentUserArgumentResolver(userService));
    }
}

5. 예외 처리 흐름

@ExceptionHandler

  • 컨트롤러 레벨의 예외 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("User not found"));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAllExceptions(Exception e) {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("Internal server error"));
    }
}

HandlerExceptionResolver

  • 전역 레벨의 예외 처리
  • 기본 구현체들:
    • DefaultHandlerExceptionResolver
    • SimpleMappingExceptionResolver
    • ResponseStatusExceptionResolver
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) {
        if (ex instanceof BusinessException) {
            // 비즈니스 예외 처리
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            return new ModelAndView("error/business");
        }
        return null;
    }
}

6. 면접 예상 질문

Q: Spring MVC의 전체적인 실행 흐름을 설명해주세요.
A: Spring MVC의 실행 흐름은 크게 다음과 같습니다. 먼저 클라이언트의 요청이 들어오면 Filter를 거쳐 DispatcherServlet이 요청을 받습니다. DispatcherServlet은 HandlerMapping을 통해 적절한 Controller를 찾고, HandlerAdapter를 사용하여 Controller의 메서드를 실행합니다. 이 과정에서 Interceptor가 동작하며, Controller의 처리 결과는 ViewResolver를 통해 적절한 View로 변환되어 최종적으로 클라이언트에게 응답됩니다. 각 단계에서는 필요에 따라 ArgumentResolver나 MessageConverter 등이 동작하여 데이터 변환을 처리합니다.

Q: Filter와 Interceptor의 차이점은 무엇인가요?
A: Filter와 Interceptor는 실행 시점과 사용 목적에서 차이가 있습니다. Filter는 서블릿 컨테이너 레벨에서 동작하여 Spring Context 외부에서 실행되며, 요청과 응답 자체를 변경할 수 있습니다. 주로 인코딩 변환, XSS 방어 등 웹 애플리케이션 전반적인 처리에 사용됩니다. 반면 Interceptor는 Spring Context 내부에서 동작하며, Spring MVC의 처리 과정에 특화되어 있습니다. 컨트롤러 호출 전후에 추가적인 처리를 할 수 있으며, Spring의 모든 빈에 접근할 수 있어 더 세밀한 처리가 가능합니다.

Q: Spring MVC에서 예외 처리 방식들에 대해 설명해주세요.
A: Spring MVC에서는 여러 단계의 예외 처리 방식을 제공합니다:
1. @ExceptionHandler를 사용한 컨트롤러 레벨의 예외 처리
2. @ControllerAdvice나 @RestControllerAdvice를 사용한 전역 예외 처리
3. HandlerExceptionResolver를 구현한 커스텀 예외 처리
각 방식은 상황에 따라 적절히 선택하여 사용할 수 있으며, 일반적으로 @ControllerAdvice를 사용한 전역 예외 처리가 가장 많이 사용됩니다. 이는 일관된 예외 처리와 응답 형식을 보장하면서도, 필요한 경우 특정 컨트롤러나 예외에 대해 세부적인 처리가 가능하기 때문입니다.

참고 자료

profile
성장하고싶은 Backend 개발자

0개의 댓글