우리가 4) 파트에서 MVC를 실제로 구현한 것과
실제 스프링MVC의 구조를 비교해보자.
사실 지금까지 우리가 한 과정들은 스프링MVC의 구조를 이해하기 위해 한 행동들이다.
동일하다.
그러면 이름을 이제 스프링MVC에 맞게 수정해보자.
- FrontController -> DispatcherServlet
- handlerMappingMap -> HandlerMapping
- MyHandlerAddapter -> HandlerAdapter
- ModelView -> ModelAndView
- viewResolver -> ViewResolver
- MyView -> View
스프링 MVC도 FrontController 패턴으로 구현되어 있다.
해당 FrontController가 바로 DispatcherServlet이다. 그리고 이 DispatcherServlet이 핵심이다.
DispatcherServlet은 부모 클래스에서 HttpServlet을 상속받고 서블릿으로 동작한다.
스프링 부트의 경우 DispatcherServlet을 서블릿으로 자동으로 등록하면서 모든 경로에 대해서 매핑한다.
서블릿이 호출되면 service()가 호출되면서, 여러 메소드 중 하나인 DispatcherServlet.doDispatch()가 호출된다.
해당 코드가 핵심 기능들을 하니, 코드를 분석해보자.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
}
private void processDispatchResult(HttpServletRequest request,HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 뷰 렌더링 호출
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request,HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
SpringMVC구조)
순서를 잘 보자.
스프링의 MVC의 큰 장점은, DispatcherServlet 코드의 변경 없이,
원하는 기능을 변경하거나 확장하기 위해 인터페이스로 제공하고 있다.
핸들러 매핑과 핸들러 어댑터)
* 과거 버전의 스프링 컨트롤러
public interface Controller{
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
@Component("/springmvc/old-controller")
public class OldCOntroller implements Controller{
@Override
public ModelAndView handleRequest(HttpServlet Request request, HttpServletResponse response) throws Exception{
return null;
}
}
@Component를 통해 /springmvc/old-controller이라는 이름의 스프링 빈으로 등록되었다.
빈의 이름으로 URL을 매핑한다
해당 컨트롤러가 호출되려면 2가지가 필요하다.
- HandlerMapping(핸들러 매핑)
핸들러 매핑에서 해당 컨트롤러를 찾을 수 있어야 한다. - HandlerAdapter(핸들러 어댑터)
핸들러 매핑에서 찾은 핸들러를 실행할 수 있는 어댑터가 필요하다.
스프링의 경우 필요한 핸들러매핑과 핸들러 어댑터를 대부분 구현해놓아서, 우리가 직접 만들 일은 거의 없다.
우선순위)
순서 과정 - OldCOntroller)
1. 핸들러 매핑으로 핸들러 조회
1) HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
2) 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.
2. 핸들러 어댑터 조회
1) HandlerAdapter 의 supports() 를 순서대로 호출한다.
2) SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
3. 핸들러 어댑터 실행
1) 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
2) SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다
-> OldController의 경우
BeanNameUrlHandlerMapping, SimpleControllerHandlerAdapter 가 작동된다.
가장 우선순위가 높은 핸들러 매핑과 핸들러 어대버는
RequestMappingHandlerMapping, RequestMappingHandlerAdapter이다.
@RequestMapping 방식, 즉 Annotation 방식의 컨트롤러를 현재 실무에서 다 쓴다 보면 된다
View Resolver의 경우
application.properties에 들어가서
spring.mvc.view.prefix
spring.mvc.view.suffix 부분에 저번처럼 경로를 지정해주면 된다.
(ex. prefix = /WEB-INF/views/ , suffix = .jsp)
이제 @RequestMapping 기반으로 구현해보자.
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@Controller 가 붙어있어서 스프링이 자동으로 스프링 빈으로 등록한다
@Controller 내부에 @Component가 구현되어 있어서 스캔 대상이 된다.
@RequestMapping을 통해 , 해당 URL이 호출되면 이 메소드가 호출된다.
ModelAndView : 모델과 뷰 정보를 담아서 반환하면 된다.
스프링MVC - 컨트롤러 통합
@RequestMapping의 경우 클래스 단위가 메서드 단위이므로, 하나로 통합할 수 있다.
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/new-form")
public ModelAndView newForm() {
return new ModelAndView("new-form");
}
@RequestMapping("/save")
public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelAndView mav = new ModelAndView("save-result");
mav.addObject("member", member);
return mav;
}
@RequestMapping
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mav = new ModelAndView("members");
mav.addObject("members", members);
return mav;
}
}
하지만 ModelVIew 객체를 만드는 것이 불편하다고 우리가 V3에서 배웠다.
V4에서 그래서 해결책으로 FrontController에서 객체를 만들었다.
import java.util.List;
/**
* v3
* Model 도입
* ViewName 직접 반환
* @RequestParam 사용
* @RequestMapping -> @GetMapping, @PostMapping
*/
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@GetMapping("/new-form")
public String newForm() {
return "new-form";
}
@PostMapping("/save")
public String save(
@RequestParam("username") String username,
@RequestParam("age") int age,
Model model) {
Member member = new Member(username, age);
memberRepository.save(member);
model.addAttribute("member", member);
return "save-result";
}
@GetMapping
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
'스프링 강의 필기 > 스프링 MVC 1편' 카테고리의 다른 글
7) 스프링 MVC - 웹 페이지 만들기 (0) | 2023.01.27 |
---|---|
6) 스프링 mvc 기본 기능 (0) | 2022.08.03 |
4) MVC 프레임워크 만들기 (0) | 2022.07.30 |
3) 서블릿, JSP, MVC 패턴 (0) | 2022.07.27 |
1) 웹 애플리케이션 이해 (0) | 2022.07.22 |