요약 한 줄
스프링 MVC는 DispatcherServlet이 중앙에서 요청을 받아 HandlerMapping → HandlerAdapter → (Controller) → ViewResolver → View 순으로 흐름을 조율하고, 중간에 필터/인터셉터/예외처리/리졸버들이 참여하는 파이프라인입니다.
HTTP 요청
↓ (Servlet Filter 체인)
DispatcherServlet
↓ HandlerMapping(매핑 찾기)
HandlerExecutionChain(핸들러 + 인터셉터들)
↓ HandlerAdapter(호출 어댑터)
@Controller/@RestController 메서드 실행
↓ (Model/ModelAndView or @ResponseBody)
ViewResolver(뷰 선택) ─ 또는 HttpMessageConverter(바디 변환)
↓ View 렌더링(템플릿/정적리소스) or JSON 직렬화
HTTP 응답
4) 인터셉터 vs 필터 vs AOP
구분 적용 레벨 주요 시점 사용 예
| Filter | 서블릿 컨테이너 | DispatcherServlet 전/후 | 인코딩, 보안, CORS, 공통 로깅 |
| Interceptor | 스프링 MVC | 컨트롤러 전/후/완료 | 로그인 체크, 요청 컨텍스트, 성능 측정 |
| AOP | 스프링 빈 메서드 | 메서드 호출 단위 | 트랜잭션, 캐시, 로깅 단면화 |
인증/인가는 필터/시큐리티, 컨트롤러 전후 로직은 인터셉터, 서비스/리포지토리横단 관심사는 AOP가 깔끔합니다.
우선순위: 로컬 @ExceptionHandler → 글로벌 @ControllerAdvice → HandlerExceptionResolver
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public Map<String, Object> hello(@RequestParam String name) {
return Map.of("message", "Hello " + name);
}
}
@Component
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
req.setAttribute("startAt", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
long took = System.currentTimeMillis() - (long) req.getAttribute("startAt");
System.out.println("[TIME] " + req.getRequestURI() + " -> " + took + "ms");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/api/**");
}
}
7-3. 커스텀 ArgumentResolver (예: @CurrentUser)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}
@Component
public class CurrentUserResolver implements HandlerMethodArgumentResolver {
@Override public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override public Object resolveArgument(MethodParameter p, ModelAndViewContainer mav,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
return webRequest.getNativeRequest(HttpServletRequest.class).getAttribute("user");
}
}
@Configuration
class MvcConfig implements WebMvcConfigurer {
@Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserResolver());
}
}
7-4. 글로벌 예외 처리
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handleBadRequest(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of(
"error", "BAD_REQUEST",
"message", e.getMessage()
));
}
}
7-5. 메시지 컨버터 추가(예: CSV)
@Configuration
public class HttpMsgConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new CsvHttpMessageConverter()); // 우선 등록
}
}
| 🌱 스프링(Spring)의 탄생과 철학 – Rod Johnson의 한 줄기 ‘봄’ (0) | 2022.11.21 |
|---|---|
| 📌Lombok 어노테이션 총정리: Getter부터 @Data까지 코드가 줄어든다! (0) | 2022.11.02 |
| @Configuration 안에 @Bean을 사용해야 하는 이유, proxyBeanMethods (0) | 2022.10.30 |
| 빈 등록을 위한 어노테이션 (0) | 2022.10.30 |
| @Controller와 @RestController (0) | 2022.10.30 |