스프링 MVC 1편 - 구조 이해

서은경·2023년 1월 2일
0

Spring

목록 보기
37/43

예전에 정리했던 내용 참고 !
https://velog.io/@eeun95/작심구일러의-스프링-시작하기9

DispatcherServlet 구조

스프링 MVC의 프론트 컨트롤러가 바로 디스패쳐 서블릿이다. 이 디스패쳐 서블릿이 바로 스프링 MVC의 핵심!

DispatcherServlet 서블릿 등록

dispatcherServlet도 부모클래스에서 HttpServlet을 상속받아서 사용하고 서블릿으로 동작
스프링 부트는 DispatcherServlet을 서블릿으로 자동으로 등록하면서 모든 경로(urlPatterns="/")에 대해 매핑

요청 흐름

서블릿이 호출되면 HttpServlet이 제공하는 service() 호출
스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet을 오버라이드
FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch 호출

동작순서

  1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
  2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
  3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
  4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
  5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
  6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.
  7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
  8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링한다.

핸들러매핑과 핸들러 어댑터

  1. 핸들러 매핑으로 핸들러 조회
  2. 핸들러 어댑터 조회
  3. 핸들러 어댑터 실행
package hello.servlet.web.springmvc.old;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

// @Controller와 전혀 다름 임포트 주의!
@Component("/springmvc/old-controller")
public class OldController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return null;
    }
}

이 컨트롤러가 호출되려면 2가지가 필요하다

  • HandlerMapping(핸들러 매핑)
    • 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다
    • 예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑 필요
  • HandlerAdapter(핸들러 어댑터)
    • 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다
    • 예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다

스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해둠 !

스프링 부트가 자동등록하는 핸들러 매핑과 핸들러 어댑터

  • HandlerMapping
    0순위 = RequestMappingHandlerMapping
    어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
    1순위 = BeanNameUrlHandlerMapping
    스프링 빈의 이름으로 핸들러를 찾는다
  • HandlerAdapter
    0순위 = RequestMappingHandlerAdapter
    어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
    1순위 = HttpRequestHandlerAdapter
    HttpRequestHandler 처리
    2순위 = SimpleControllerHandlerAdapter
    Contorller 인터페이스(어노테이션X, 과거에 사용) 처리
package hello.servlet.web.springmvc.old;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import java.io.IOException;

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHttpRequestHandler.handleRequest");
    }
}

스프링 빈 이름으로 핸들러를 찾아 httpRequestHandlerAdapter가 처리

🙋‍♀️ 우선순위가 가장 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandler~~이다. (99.9% 이 방식의 컨트롤러 사용)

뷰 리졸버

  1. 핸들러 어댑터 호출
  2. ViewResolver 호출
  3. InternalResourceViewResolver
  4. 뷰 - InternalResourceView
  5. view.render()

스프링 부트는 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties에 설정 정보를 등록해주어야 한다.

#logging.level.org.apache.coyote.http11=debug
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

스프링 부트가 자동등록하는 뷰 리졸버

1순위 = BeanNameViewResolver
빈 이름으로 뷰를 찾아서 반환한다
2순위 = InternalResourceViewResolver
JSP를 처리할 수 있는 뷰를 반환한다

스프링 MVC 시작하기

스프링이 제공하는 컨트롤러는 어노테이션 기반으로 동작해서 매우 유연하고 실용적이다!

@RequestMapping

스프링은 어노테이션을 활용한 매우 유연하고 실용적인 컨트롤러를 만들었는데 이것이 바로 @RequestMapping 어노테이션을 사용하는 컨트롤러이다 ! ! !

  • RequestMapping
    • RequestMappingHandlerMapping
    • RequestMappingHandlerAdapter
package hello.servlet.web.springmvc.v1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}
  • @Controller
    • 스프링이 자동으로 스프링 빈으로 등록
    • 스프링 MVC에서 어노테이션 기반 컨트롤러로 인식(RequestMappingHandlerMapping에서 핸들러 정보를 꺼낼 수 있는 대상이 됨)
  • @RequestMapping
    • 요청 정보를 매핑
  • ModelAndView
    • 모델과 뷰 정보를 담아 반환

실용적인 방식

package hello.servlet.web.springmvc.v3;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/new-form")
    public String newForm() {
        return "new-form";
    }

    @RequestMapping("/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";
    }

    @RequestMapping
    //@RequestMapping("/") 하면 왜 에러일까
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();
        model.addAttribute("members", members);
        return "members";
    }
}

0개의 댓글