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);
}
RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
@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());
}
}
@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 {
// 뷰 렌더링 후
}
}
HTTP 메시지 바디와 자바 객체 간의 변환을 담당하는 인터페이스입니다.
MappingJackson2HttpMessageConverter
@PostMapping("/api/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// {"name": "John", "age": 30} -> User 객체
return ResponseEntity.ok(user); // User 객체 -> JSON
}
StringHttpMessageConverter
@PostMapping("/api/text")
public String handleText(@RequestBody String text) {
// Plain text 처리
return "received: " + text;
}
FormHttpMessageConverter
@PostMapping("/api/form")
public String handleForm(@RequestBody MultiValueMap<String, String> formData) {
// application/x-www-form-urlencoded 처리
return "name: " + formData.getFirst("name");
}
요청 처리
1. Content-Type 헤더 확인
2. 적절한 MessageConverter 선택
3. HTTP 메시지 -> 자바 객체 변환
응답 처리
1. 반환 타입 확인
2. Accept 헤더 확인
3. 적절한 MessageConverter 선택
4. 자바 객체 -> HTTP 메시지 변환
컨트롤러의 파라미터를 처리하는 인터페이스입니다.
RequestParamMethodArgumentResolver
@GetMapping("/api/search")
public List<Item> searchItems(@RequestParam String keyword) {
// ?keyword=book -> String "book"
return itemService.search(keyword);
}
PathVariableMethodArgumentResolver
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
// /api/users/123 -> Long 123
return userService.findById(id);
}
RequestResponseBodyMethodProcessor
@PostMapping("/api/users")
@ResponseBody
public User createUser(@RequestBody User user) {
// HTTP 메시지 바디 -> User 객체
return userService.create(user);
}
특별한 파라미터 처리가 필요한 경우 구현할 수 있습니다.
@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));
}
}
@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"));
}
}
@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;
}
}
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를 사용한 전역 예외 처리가 가장 많이 사용됩니다. 이는 일관된 예외 처리와 응답 형식을 보장하면서도, 필요한 경우 특정 컨트롤러나 예외에 대해 세부적인 처리가 가능하기 때문입니다.