ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SpringMVC (5) - 스프링 MVC 구조 이해
    Web/Spring 2023. 10. 2. 16:43

    출처: 스프링mvc (인프런) - 김영한

     

    - 직접 만든 프레임 워크와 스프링 MVC는 큰 차이가 없다 

    - 이름만 조금 다를 뿐 구조는 매우 비슷함.

     

    > DispatcherServlet 구조 살펴보기

     

    -org,springframwork.web.servlet.DispatcherServlet 

    프론트 컨트롤러 패턴으로 구현됨

    스프링 MVC의 핵심

     

    - dispatcherservlet도 httpservlet을 상속받아서 사용하고, 서블릿으로 동작한다.

    - 스프링 부트는 dispatcherservlet을 서블릿으로 자동등록하며, 모든 경로에 대해서 맵핑한다.

     

    * 더 자세한 경로가 우선순위 높음 따라서 기존에 등록한 서블릿도 함께 동작함

     

    > 요청 흐름

     

    - 서블릿이 호출되면, service()가 호출됨

    - frameworkServlet.service()를 시작으로 여러 메서드 호출 -> dispacherServlet에 doDispatch()호출됨

    - 이 메서드가 dispatcher의 핵심코드이다.

     

    protected void doDispatch(HttpServletRequest request, HttpServletResponse 
    response) throws Exception {
    
    // 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);
    }

    핸들러 조회 -> 어댑터 조회 -> 어댑터 실행 -> 핸들러 실행 -> ModelAndView반환 -> viewResolver호출 -> view 객페 반환 -> 뷰 랜더링 

     

    - 인터페이스 : 스프링 MVC의 강점은 DispatcherServlet의 코드 변경없이 기능 확장 가능 아래 인터페이스만 구현해서 Dispatcher에 등록하면 컨트롤러 만들 수 있다.

     

    주요 인터페이스 목록

    핸들러 매핑:org.springframework.web.servlet.HandlerMapping

    핸들러 어댑터: org.springframework.web.servlet.HandlerAdapter

    뷰 리졸버: org.springframework.web.servlet.ViewResolver

    뷰: org.springframework.web.servlet.View

     

     

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

     

    - 핸들러 매핑과 어댑터가 어떤 것들이 사용되는지 알아봅시다.

    - 과거에 주로 사용했던, 간단한 컨트롤러로 핸들러 매핑과 어댑터 알아보자

     

    2.1 Controller 인터페이스 

     

    org.springframwork.web.servlet.mvc.Controller

    public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse 
    response) throws Exception;
    }

     

    - OldController

    @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;
    
    }
    }

    - @Conponent : 이 컨트롤러는 /springmvc/old-controller라는 이름으로 스프링 빈 등록

    - 빈의 이름으로 url 매핑함

     

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

    - HandlerMapping, HandlerAdapter

    스프링은 oldController형식의 컨트롤러를 처리할 수 있는 핸들러맵핑방식과 어댑터를 구현해 둠

     

    출처: 스프링MVC(인프런) - 김영한

      - 직접 맵핑정보를 등록하지 않아도 스프링 빈으로 등록시, 자동으로 등록됨

      - 어댑터 또한 직접 구현하지 않아도, Controller인터페이스 구현한 핸들러를 처리할 수 있는 어댑터가 이미 있음 이걸로 처리

     

    * 핸들러 맵핑은 어노테이션 @RequestMapping에서 한번 찾고 없으면 -> 빈 이름으로 핸들러 찾음

       핸들러 어댑터는 -> handlerAdapter의 supports()호출하는데, 2번에서 걸림 

       핸들러 어댑터 조회에 성공했으면, 핸들러 어댑터 실행하면서 핸들러(OldController) 넘김

     

    결론적으로 Controller 사용하면 

    BeanNameUrlHandlerMapping과 SimpleControllerHandlerAdapter가 작동해서 처리해준다.

    따라서 Contoller 구현 + @Conpnent만 잘 지키자

     

    2.2 HtpRequestHandler

     

    - 서블릿과 가장 유사한 형태의 핸들러이다.

    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 {
     System.out.println("MyHttpRequestHandler.handleRequest");
     }
    }

    HttpRequestHandler를 구현한 컨트롤러 (핸들러)를 등록하면,

    BeanNameUrlHandlerMapping과 HttpRequestHandlerAdapter가 작동하여 핸들러를 처리함

     

    2.3 @RequestMapping

     

    - 가장 우선순위 높은 RequestMappingHandlerMapping과 Adapter을 통해 실행됨

    - 거의 위 어노테이션 기반으로 컨트롤러와 메서드들을 등록함

     

    3. 뷰 리졸버 

     

    - OldController를 통해 뷰를 조회하도록  바꿔보자 

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

    - 컨트롤러는 정성 호출 -> 랜더는 실패 -> 추가 설정 필요

     

    application.properties에 다음 코드 추가 

    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp

     

    > 뷰 리졸버 - InternalResourceViewResolver

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

    //ServletApplcation 내에서 아래와 같은 뷰리졸버 등록한다 -> 뷰리졸버가 따로 등록된게 없으면 이거 씀
    @Bean
    ViewResolver internalResourceViewResolver(){
       return new InternalResourceViewResolver("prefix","suffix")
    }

    스프링 부트가 얘를 호출해줌 

     

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

    출처: 스프링MVC (인프런) -김영한

    1. 핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름 획득

    2. new-form이름을 빈으로 등록된 뷰객체 가 있는지 찾기 -> 없으면 다음 뷰 리졸버 호출(이미 빈에 자동 등록된)

    3. InternalResourceViewResolver가 InternalResourceView객체를 반환

    4. InternalResourceView는 JSP처럼 포워드 forwad() 호출해서 처리할  수 있는 경우 사용 (MyView랑 거의 같은 역할) 

    4. view.render() -> 포워딩해서 jsp 실행 (이때 모델에 담긴 값 request로 옮김)

     

    * 만약 jstl라이브러리가 있으면, JstlView가 반환됨 (InternalResourceViewResolver를 통해)

    * View인터페이스를 구현한 View 객체를 빈으로 등록해두고, 컨트롤러가 해당 view객체 빈 이름을 return하면, 뷰 리졸버가 해당 View객체 리턴함 

     

     

    4. 스프링 MVC 시작하기 

     

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

     

    - @RequestMapping : 컨트롤러는 이 어노테이션을 사용함 

    - @Controller : 어노테이션 기반 컨트롤러로 인식 -> @RequestMapping을 처리할 핸들러 맵핑 맵과, 어댑터 동작하도록 알림 

    > 클래스 레벌에 @RequestMapping, @Controller를 매핑 정보로 인식한다.

     

    * @Contorller를 통해 어노테이션 기반 매핑맵과, 어댑터 사용해야함 알림 

       > 해당 컨트롤러 처리할 어댑터를 통해 내부적으로 다시 request에서 url 뽑고 

       > 컨트롤러내에 url매핑된 메서드 실행

     

    *이전까지 구조를 보면서 프론트 컨트롤러 어댑터 부분들 알아봤다면,

    이제 스프링MVC에서 제공하는 기능을 사용하는 방법임 (컨트롤러 구성방법)

    어노테이션 기반 컨트롤러 인식하고 처리하는 맵핑맵과 어댑터 있기 때문에

    @Controller와 @RequestMapping을 통해 보다 자유롭게 

    컨트롤러 만들 수 있음 (Controller 구현x, 메서드 명 및 매개변수 자유)

    어댑터가 내부적으로 매핑정보에 따라 메서드 호출 + 매개변수 주입 해준다.

     

    4.1 회원 등록 폼

     

    @Controller
    public class SpringMemberFormControllerV1 {
     @RequestMapping("/springmvc/v1/members/new-form")
     public ModelAndView process() {
     return new ModelAndView("new-form");
     }
    }

    @Controller : 스프링 빈으로 등록

                          어노테이션 기반 컨트롤러(핸들러)임을 인식

    @RequestMapping: 요청 정보 매핑

                                     해당 url이 호출되면, 이 메서드가 호출됨

    ModelAndView : 모델과 뷰 정보를 담아서 변환.

     

    RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어있는 경우 매핑 정보로 인식한다.

     

    따라서 아래 코드도 정확하게 동일하게 작동

    @Component //컴포넌트 스캔을 통해 스프링 빈으로 등록
    @RequestMapping
    public class SpringMemberFormControllerV1 {
     @RequestMapping("/springmvc/v1/members/new-form")
     public ModelAndView process() {
     return new ModelAndView("new-form");
     }
    }

     

    // ServletApplication
    // componentscan없이 직접 등록
    
    @Bean
    SpringMemberFormControllerV1 springMemberFormControllerV1() {
    return new SpringMemberFormControllerV1();
    }
    
    
    @RequestMapping
    public class SpringMemberFormControllerV1 {
     @RequestMapping("/springmvc/v1/members/new-form")
     public ModelAndView process() {
     return new ModelAndView("new-form");
     }
    }

    * 스프링 3.0부터는 이거 안됨 @Controller 필요

     

    4.2 회원 저장 및 목록 

     

    @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;
     }
     
     
     / 회원 목록 
     @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;
     }
    }
    
     
    }

    mv.addObject()를 사용하면, ModelAndView 내부 Model에 손쉽게 데이터 추가 가능 

     

    5. 스프링 MVC - 컨트롤러 통합

     

    - @RequestMapping은 클래스 단위가 아니라 메서드 단위로 적용 따라서 컨트롤러 클래스를 

    하나로 통합할 수 있다.

     

    @Controller
    @RequestMapping("/springmvc/v2/members")
    public class SpringMemberControllerV2{
    
    	private MemberRepositroy 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;
        
        }
    }

     

    6. 스프링 MVC 실용적인 방식 

     

    -보통 이 방식 사용

     

    @controller
    @RequestMapping("springmvc/v3/members")
    public class SpringMemberControllerV3{
    
    		private MemberRepositroy 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 = memberRepositroy.findAll();
                model.addAttribute("members",members);
                return "members";
              }
    }

     

    - Model

      : 어댑터가 만들어서 넣어줌 (예전 ControllerV4처럼) 

     

    - ViewName 직접 반환

     : V4처럼 컨트롤러가 ViewName만 반환하면 알아서 이를 토대로 

       ModelAndView만들어줌 (어댑터)

     

    - RequestParam

     :  Http요청 파라미터를 위와 같이 받을 수 있음 (request.getParameter랑 같음)

     

    - @RequestMapping, @GetMapping,@PostMapping

     : HTTP method 구분할 수 있음 

     

     

    *Controller 인터페이스 구현해서 컨트롤러 만들면 메서드 오버라이드 해야해서 유연성 떨어지고,

      Mapping별로 컨트롤러 구현체 만들어야함 

     

      어노테이션 기반: pojo도 하나만 두고 어댑터가 여러 형식을 처리를 지원해줘서 

                                   String,ModelAndView 리턴해도되고, 메서드명 자유임! 

                                   매개변수도 request,response,Model 등등 약간의 자유가 있다.

     

      > 어댑터는 컨트롤러(핸들러) 실행 결과로 ModelAndView만 만들면 그만임 

         -- 디스팻쳐 역할---

         리졸버 호출 -> 리졸버가 ModelAndView에서 viewName꺼낸 후 View객체 만듦 

         랜더 호출 -> View에 render는  ModelAndView에서 Model꺼내가서 랜더링함 

     

    > 어댑터는 요청 아까도 봤듯이 컨트롤러(핸들러)에 맞춤형으로 파라미터들 생성해서 넘겨주고 

        결과 받아서 ModelAndView 리턴하는 역할 

     

     

     

    참고자료:https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

     

    스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

    웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

    www.inflearn.com

     

Designed by Tistory.