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순위
HttpRequestHandlerAdapterHttpRequestHandler처리
- 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에 데이터를 전달하는 객체