MVC 프레임워크 만들기
23 Sep 2023스프링 MVC 1편 강의 정리
스프링 MVC 전체 구조
DispatcherServlet
HttpServlet
상속받아 서블릿으로 동작- 스프링 부트에서 DispatcherServlet을 서블릿으로 자동 등록하면서 모든 경로(
urlPatterns="/"
)에 대해 매핑하며 자세한 경로가 우선순위가 더 높음 - 요청 흐름
- 서블릿이 호출되면
HttpServlet
이 제공하는service()
가 호출됨 - 스프링 MVC에서는 DispatcherServlet이 상속받은
FrameworkServlet
에서service()
를 재정의함 FameworkServlet.service()
가 호출되면서DispatcherServlet.doDispatch()
가 호출됨
- 서블릿이 호출되면
// doDispatch 간단 버전
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandleFound(processedRequest, response);
return;
}
// 핸들러 어댑터 조회: 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 핸들러 어댑터 실행 -> 핸들러 어댑터 통해 핸들러 실행 -> ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processedDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processedDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception execption) throws Exception {
// 뷰 렌더링 호출
render(mv, request, reponse);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 뷰 리졸버 이용해서 뷰 찾기 -> view 반환
view = resolveViewName(viewName, mv.getModelinternal(), locale, request);
// 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
SpringMVC 구조
- DispatcherServlet에서 매핑된 핸들러에서 핸들러 조회
- DispatcherServlet에서 핸들러를 처리할 수 있는 핸들러 어댑터 조회
- DispatcherServlet에서 핸들러 어댑처 실행
- 핸들러 어댑터에서 핸들러(컨트롤러) 실행
- 핸들러 어댑터가 DispatcherServlet에 ModelAndView 반환
- DispatcherServlet에서 ViewResolver 실행
- ViewResolver가 뷰의 논리 이름을 물리 이름으로 바꾸고 렌더링을 담당하는 뷰 객체 반환
- DispatcherServlet이 View의
render(model)
메서드 호출하여 HTML 반환
인터페이스
- 스프링 MVC의 강점은 인터페이스로 제공되어 DispatcherServlet 코드의 변경없이 원하는 기능을 추가/확장할 수 있다는 점
- 주요 인터페이스
- 핸들러 매핑:
org.springframework.web.servlet.HandlerMapping
- 핸들러 어댑터:
org.springframework.web.servlet.HandlerAdapter
- 뷰 리졸버:
org.springframework.web.servlet.ViewResolver
- 뷰:
org.springframework.web.servlet.View
- 핸들러 매핑:
Handler mapping & handler adapter
Controller Interface
- 현재에는 사용하지 않은 과거 버전의 스프링 컨트롤러
// org.springframework.web.servlet.mvc.Controller
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
// 간단하게 구현한 컨트롤러
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return null;
}
}
- 위의 컨트롤러가 호출되기 위해 필요한 조건
- 핸들러 매핑: 핸들러 매핑에서 위의 컨트롤러를 찾을 수 있어야 함
- 핸들러 어댑터: 핸들러 매핑에서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 있어야 함
- 위의 두 조건은 스프링에서 구현이 되어있고 스프링 부트에서는 자동으로 등록
- 핸들러 매핑은
BeanNameUrlHandlerMapping
에 의해 실행 - 핸들러 어댑터는
SimpleControllerHandlerAdapter
에 의해 지원됨
Handler Mapping
- 1순위
RequestMappingHandlerMapping
- 애노테이션 기반의 컨트롤러인
@RequestMapping
에서 사용
- 2순위
BeanNameUrlHandlerMapping
- 스프링 빈의 이름으로 핸들러를찾음
Handler Adapter
- 1순위
RequestMappingHandlerAdapter
- 애노테이션 기반의 컨트롤러인
@RequestMapping
에서 사용
- 2순위
HttpRequestHandlerAdapter
HttpRequestHandler
처리
- 3순위
SimpleControllerHandlerAdapter
- Controller 인터페이스
HttpRequestHandler
- 서블릿과 가장 유사한 형태의 핸들러
@FunctionalInterface
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}
// 간단하게 구현한 핸들러
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
BeanNameUrlHandlerMapping
에 의해 핸들러를 찾아 실행됨HttpRequestHandlerAdapter
가HttpRequestHandler
인터페이스를 지원하므로 핸들러 어댑터를 조회- 디스패처 서블릿이
HttpRequestHandlerAdapter
를 실행하면서 구현한 핸들러의 결과 반환
View Resolver
- 기존의
OldController
에서 View를 쓸 수 있도록 반환 타입을ModelAndView
로 변경 - 서버를 띄우면 컨트롤러는 호출되지만 해당 뷰를 찾지 못함
application.properties
에 관련 설정 추가
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return new ModelAndView("new-form");
}
}
# application.properties
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
- 스프링 부트에서
InternalResourceViewResolver
자동으로 등록spring.mvc.view.prefix
,spring.mvc.view.suffix
설정을 사용해서 등록
- 스프링 부트가 자동 등록하는 뷰 리졸버
BeanNameViewResolver
: 빈 이름으로 뷰를 찾아서 반환InternalResourceViewResolver
: JSP를 처리할 수 있는 뷰 반환
- 과정
- 핸들러 어댑터를 통해 뷰의 논리 이름 확인
- 뷰 이름으로 viewResolver 순서대로 호출
- 위의 코드에서는 빈 이름으로 찾을 수 없으므로 InternalResourceViewResolver가 호출됨
- 뷰 리졸버가 InternalResourceView 반환
- view.render()가 호출되고 InternalResourceView가 forward()를 사용해서 JSP 실행
@RequestMapping
- 애노테이션을 활용한 유연하고 실용한 컨트롤러를 쓸 수 있게 함
- 요청 정보를 매핑하고 해당 URL이 호출되면 메서드가 호출됨
- 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는
RequestMappingHandlerMapping
&RequestMappingHandlerAdapter
로서 각각 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터 @Controller
- 스프링이 자동으로 스프링 빈으로 등록
- 내부에
@Component
애노테이션이 있어서 컴포넌트 스캔의 대상이 됨 - 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식
RequestMappingHandlerMappingr
은 스프링 빈 중에@RequestMapping
또는@Controller
애노테이션이 클래스에 있을 경우 매핑 정보로 인식- HTTP method도 매핑 가능 (
@RequestMapping(value="/members", method=RequestMethod.GET)
)@GetMapping
,@PostMapping
,@DeleteMapping
,@PutMapping
,@PatchMapping
등의 애노테이션으로 더 간결하게 사용 가능
그외
@RequestParam
: HTTP 요청 파라미터를 받을 수 있게 해주는 애노테이션Model
: view에 데이터를 전달하는 객체