Spring MVC - 구조 이해

유병익·2022년 10월 23일
0
post-thumbnail

1. Spring MVC 전체 구조


1.1 직접 만든 MVC Framework VS Spring MVC


1.1.1 직접 만든 MVC Framework 구조


1.1.2 Spring MVC 구조


1.1.3 비교


  • FrontController → DispatcherServlet
  • handlerMappingMap → HandlerMapping
  • MyHandlerAdapter → HandlerAdapter
  • ModelView → ModelAndView
  • viewResolver → ViewResolver
  • MyView → View



1.2 DispatcherServlet


  • Spring MVC도 Front Controller Pattern으로 구현되어 있다.
    • Spring MVC의 Front ControllerDispatcherServlet

💡 DispatcherServlet = Spring MVC의 핵심



1.2.1 DispacherServlet Servlet 등록

  • DispacherServlet 도 부모 클래스에서 HttpServlet 을 상속 → Servlet으로 동작
    • DispatcherServlet → FrameworkServlet → HttpServletBean → HttpServlet
  • Spring Boot는 DispacherServlet을 Servlet으로 자동 등록
    • 모든 경로(urlPatterns="/")에 대해서 Mapping

📌 참고

  • 자세한 경로가 우선순위가 높음.
    • 기존에 등록한 Servlet도 함께 동작한다.



1.2.2 요청 흐름


  1. Servlet 호출
  2. HttpServlet이 제공하는 serivce() 호출
    • Spring MVC는 DispatcherServlet의 부모인 FrameworkServlet에서service() Override
  3. FrameworkServlet.service() → 여러 Method 호출
  4. DispacherServlet.doDispatch() 호출



1.2.3 DispacherServlet.doDispatch() 코드 분석


  • Code

    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);
    }
  • 동작 순서

    1. Handler 조회

      • Handler Mapping을 통해 요청 URL에 Mapping된 Handler(Controller) 조회
    2. Handler Adapter 조회

      • Handler를 실행할 수 있는 Handler Adapter를 조회
    3. Handler Adapter 실행

      • Handler Adapter를 실행
    4. Handler 실행

      • Handler Adapter가 실제 Handler를 실행
    5. ModelAndView 반환

      • Handler Adapter는 Handler가 반환하는 정보를 ModelAndView로 변환해서 반환
    6. viewResolver 호출

      • viewResolver를 찾고 실행

      📌 JSP의 경우, InternalResourceViewResolver가 자동 등록되고, 사용된다.

    7. View 반환

      • viewResolver는 View의 논리 이름을 물리 이름으로 변환
      • Rendering을 담당하는 View 객체 반환

      📌 JSP의 경우, InternalResourceView(JstlView)를 반환

      • 내부에 forward() 로직이 있음
    8. View Rendering

      • View를 통해서 View를 Rendering



1.2.4 Interface


📌 Spring MVC의 큰 강점

  • DispatcherServlet 코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다
  • 지금까지 설명한 대부분을 확장 가능할 수 있게 Interface로 제공
  • 이 Interface를 구현해DispatcherServlet에 등록 → 새로운 Controller를 만들 수 있음



  • 주요 Interface
    • HandlerMapping
      • org.springframework.web.servlet.HandlerMapping
    • HandlerAdapter
      • org.springframework.web.servlet.HandlerAdapter
    • ViewResolver
      • org.springframework.web.servlet.ViewResolver
    • View
      • org.springframework.web.servlet.View



1.2.5 정리


  • MVC는 개발자들의 요구 사항에 맞추어 계속해서 기능을 확장해왔다.
    • 따라서, Web Application을 만들 때 필요로 하는 대부분의 기능이 이미 다 구현되어 있다. 📌 위 기능을 직접 확장하거나 나만의 Controller를 만드는 일은 거의 없음.
  • 그렇지만, 핵심 동작 방식을 제대로 알아두어야 한다. 📌 문제가 발생했을 때 어떤 부분에서 문제가 발생했는지 파악하고, 문제를 해결할 수 있다.
  • 지금은 전체적인 구조에 대해 이해하면 된다.

2. Handler Mapping & Handler Adapter


스프링이 제공하는 간단한 컨트롤러로 Handler Mapping & Handler Adapter를 이해해보자.

2.1 Controller Interface


과거에 주로 사용했던 스프링이 제공하는 간단한 컨트롤러

2.1.1 Code


  • Controller Interface - org.springframework.web.servlet.mvc.Controller
    public interface Controller {
    
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
    
    }
  • OldController
    package hello.servlet.web.springmvc.old;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @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;
    		 }
    }

📌 @Component 를 사용해 요청 URL을 이름으로 스프링 빈 등록



2.1.2 동작 과정


  1. HandlerMapping 에서 Handler조회

  2. 이 경우 빈 이름으로 Handler를 찾아야 함

    → 빈 이름으로 Handler를 찾아주는BeanNameUrlHandlerMapping 실행

    OldController 반환

  3. HandlerAdapter 조회

    • HandlerAdaptersupports()를 순서대로 호출
  4. SimpleControllerHandlerAdapterController Interface를 지원하므로 대상이 됨

  5. HandlerAdapter 실행

  6. dispatcherServle이 조회한 SimpleControllerHandlerAdapter를 실행

    • Handler 정보도 함께 넘겨준다.
  7. SimpleControllerHandlerAdapter는 핸들러인 OldController를 내부에서 실행

    • 그 결과를 반환



2.2 HttpRequestHandler Interface


HttpRequestHandlerServlet과 가장 유사한 형태의 핸들러

2.2.1 Code


  • HttpRequestHandler
    public interface HttpRequestHandler {void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
    }
  • MyHttpRequestHandler
    package hello.servlet.web.springmvc.old;
    import org.springframework.stereotype.Component;
    import org.springframework.web.HttpRequestHandler;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    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");
    		 }
    }


2.2.2 동작 과정


  1. HandlerMapping 에서 Handler조회
  2. 이 경우 빈 이름으로 Handler를 찾아야 한다.
    • 빈 이름으로 Handler를 찾아주는BeanNameUrlHandlerMapping 실행
    • MyHttpRequestHandler반환
  3. HandlerAdapter 조회
    • HandlerAdaptersupports()를 순서대로 호출
  4. SimpleControllerHandlerAdapterHttpRequestHandler Interface를 지원하므로 대상이 된다.
  5. HandlerAdapter 실행
  6. dispatcherServle이 조회한 HttpRequestHandlerAdapter를 실행
    • Handler 정보도 함께 넘겨준다.
  7. HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행
    - 그 결과를 반환


2.3 정리


2.3.1 요약


  • 특정 Controller가 호출되려면 크게 두 가지가 필요하다.
    1. HandlerMapping
      • HandlerMapping에서 해당 Handler(Controller)를 찾을 수 있어야 함
    2. HandlerAdapter
      • HandlerMapping을 사용해 해당 Handler(Controller)를 실행할 HandlerAdapter가 필요

스프링은 이미 필요한 HandlerMappingHandlerAdapter를 대부분 구현해두었다.

개발자가 직접HandlerMapping과 핸HandlerAdapter를 만드는 일은 거의 없다.



2.3.2 Spring Boot가 자동 등록하는 Handler Mapping & Handler Adapter


  • Handler Mapping

    • RequestMappingHandlerMapping
      • Annotation 기반의 Controller인 @RequestMapping에서 사용
    • BeanNameUrlHandlerMapping
      • 스프링 빈의 이름으로 Handler를 찾는다.
  • Handler Adapter

    • RequestMappingHandlerAdapter
      • Annotation기반의 Controller인 @RequestMapping에서 사용
    • HttpRequestHandlerAdapter
      • HttpRequestHandler 처리
    • SimpleControllerHandlerAdapter
      • Controller Interface(Annotation X, 과거에 사용) 처리

@RequestMapping

  • 가장 우선순위가 높은 Handler Mapping, Handler Adapter ⇒ RequestMappingHandlerMapping , RequestMappingHandlerAdapter
  • Spring에서 주로 사용하는 Annotation 기반의 Controller를 지원하는 Mapping & Adapter
    • 거의 대부분 이 방식의 Controller 사용



3. View Resolver


Spring 은 ViewResolverViewInterface로 제공

Spring Boot가 자동으로 등록하는 View Resolver (일부)

  • BeanNameViewResolver
    • 빈 이름으로 View를 찾아서 반환
  • InternalResourceViewResolver
    • JSP를 처리할 수 있는 View 반환
  • InternalResourceViewResolver

    • Spring Boot는 InternalResourceViewResolver라는 View Resolver를 자동으로 등록
    • 이때 application.properties 에 등록한 spring.mvc.view.prefix , spring.mvc.view.suffix 설정
      정보를 사용해서 등록
  • 동작 과정

    1. Handler Adapter를 통해 View의 논리 이름 획득
    2. ViewResolver 호출
      • View의 논리 이름으로 viewResolver를 순서대로 호출
      • JSP 를 처리하기 위해 InternalResourceViewResolver 호출
    3. InternalResourceViewResolver
      • InternalResourceView 반환
    4. InternalResourceView
      • JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용
    5. view.render()
      • view.render() 호출 InternalResourceView 는 forward()를사용해서 JSP 실행

4. Spring MVC


4.1 @RequestMapping


Spring은 Annotation을 활용해 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로
@RequestMapping Annotation을 사용하는 Controller이다.

  • 가장 우선순위가 높은 HandlerMapping & HandlerAdapter → RequestMappingHandlerMapping , RequestMappingHandlerAdapter
  • 실무에서는 거의 모두 이 방식의 Controller 사용

4.2 Spring MVC - 시작하기


4.2.1 SpringMemberFormControllerV1 - 회원 등록 폼


  • Code
    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");
    		 }
    }

4.2.2 SpringMemberSaveControllerV1 - 회원 저장


  • Code
    package hello.servlet.web.springmvc.v1;
    
    import hello.servlet.domain.member.Member;
    import hello.servlet.domain.member.MemberRepository;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Controller
    public class SpringMemberSaveControllerV1 {
    
    		 private MemberRepository memberRepository = MemberRepository.getInstance();
    
    		 @RequestMapping("/springmvc/v1/members/save")
    		 public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
    				 String username = request.getParameter("username");
    				 int age = Integer.parseInt(request.getParameter("age"));
    				 Member member = new Member(username, age); System.out.println("member = " + member);
    				 memberRepository.save(member);
    				 ModelAndView mv = new ModelAndView("save-result");
    				 mv.addObject("member", member);
    				 return mv;
    		 }
    }

4.2.3 SpringMemberListControllerV1 - 회원 목록


  • Code
    package hello.servlet.web.springmvc.v1;
    
    import hello.servlet.domain.member.Member;
    import hello.servlet.domain.member.MemberRepository;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    import java.util.List;
    
    @Controller
    public class SpringMemberListControllerV1 {
    
    		 private MemberRepository memberRepository = MemberRepository.getInstance();
    		
    		 @RequestMapping("/springmvc/v1/members")
    		 public ModelAndView process() {
    				 List<Member> members = memberRepository.findAll();
    				 ModelAndView mv = new ModelAndView("members");
    				 mv.addObject("members", members); return mv;
    		 }
    }

4.2.4 분석


  • @Controller
    • Spring이 자동으로 Spring Bean으로 등록
      • 내부에 @Component Annotation → Componnent Scan의 대상이 됨
    • Spring MVC에서 Annotation기반 Controller로 인식
  • @RequestMapping
    • 요청 정보를 Mapping
    • 해당 URL이 호출되면 @RequestMapping 이 붙은 Method 호출
  • ModelAndView
    • Model과 View 정보를 담아서 반환

RequestMappingHandlerMapping은 Spring Bean 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 Mapping 정보로 인식

4.3 Spring MVC - Controller 통합


@RequestMapping가 클래스 단위가 아니라 Method 단위에 적용된 것을 확인할 수 있다.

→ Controller 클래스를 하나로 통합할 수 있다.

4.3.1 SpringMemberControllerV2


  • Code
    package hello.servlet.web.springmvc.v2;
    import hello.servlet.domain.member.Member;
    import hello.servlet.domain.member.MemberRepository;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.List;
    
    /**
     * 클래스 단위 -> 메서드 단위
     * @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;
    		 }
    }

4.3.2 분석


  • 조합
    • 클래스 레벨에 다음과 같이 @RequestMapping 을 두면 Method 레벨과 조합 가능
      @Controller
      @RequestMapping("/springmvc/v2/members")
      public class SpringMemberControllerV2{
      		...
      		...
      }

4.4 스프링 MVC - 실용적인 방식


스프링 MVC는 개발자가 편리하게 개발할 수 있도록 수 많은 편의 기능을 제공한다.

4.4.1 SpringMemberControllerV3


  • Code
    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.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    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";
    		 }
    }

4.4.2 분석


  • Model
    • Model을 파라미터로 받을 수 있음
  • ViewName 직접 반환
    • View의 논리 이름을 반환할 수 있음
  • @RequestParam 사용
    • HTTP Request Parameter를 @RequestParam으로 받을 수 있다음
    • GET Query Parameter, POST Form 방식 모두 지원
  • @RequestMapping → @GetMapping, @PostMapping
    • @RequestMapping 의 기능
      • URL 매칭 기능
      • HTTP Method 구분 기능 → @GetMapping , @PostMapping 등을 사용해 더 편리하게 사용 가능
profile
Backend 개발자가 되고 싶은

0개의 댓글