Backend

스프링 MVC 구조, DispatcherServlet, @RequestMapping..

연_우리 2021. 12. 1. 21:54
반응형

목차

     

    스프링MVC

    이전에서의 구현 역할 스프링에서의 구현
    FrontController handlerMappingMap을 생성하고 초기화한다.
    handlerAdapter를 생성하고 초기화한다.
    HttpServlet을 상속받아 service메서드를 재정의한다
    handlerMappingMap에서 찾은 각 Handler를 Adapter로 넘기고
    반환받은 ModelView에서 viewName을 get해서 MyView객체 생성한다
    MyView객체의 render메서드를 실행하여 페이지를 이동한다.
    DispatcherServlet
    handlerMappingMap 주소와 Handler(=각 Controller)들을 맵핑함
    (MemberFormControllerV3, MemberSaveControllerV4...)
    HandlerMapping
    MyHandlerAdapter request대신 사용할 paramMap과 model을 생성한다
    Handler(=각 Controller)의 process메서드를 실행하여 view경로를 문자로 받아와서 ModelView객체를 반환함
    HandlerAdapter
    ModelView request의 저장공간을 대신함 ModelAndView
    viewResolver 고정경로와 viewName을 조합하여 MyView객체를 반환함 ViewResolver
    MyView render메서드를 구현하고 viewName으로 RequestDispatcher를 생성하고
    forward메서드로 페이지를 이동한다
    View

     

     

    DispatcherServlet Interface구조

    org.springframework.web.servlet.DispatcherServlet

     

    스프링 부트는 @Web(urlPatterns="/") DispatcherServlet을 서블릿으로 자동 등록한다.

     

    DispatcherServlet ▶ FrameworkServlet ▶ HttpServletBean ▶ HttpServlet 

    결국 HttpServlet을 상속받는다. (service메서드를 재정의해야한다)

     

    DispatcherServlet은 코드의 변경 없이 원하는 기능을 변경하거나 확장할 수 있다.

     

    DispatcherServlet의 doService(request, response)메서드를 실행하면 doDispatch(request, response)메서드가 호출된다.

     

    doDispatch 메서드에서는 

    핸들러를 조회하고

    핸들러 어댑터를 조회하고

    핸들러 어댑터를 실행해서(handle메서드) 어댑터에서 핸들러 실행하고(process메서드) ModelAndView 반환받음

    ModelAndView를 담아 processDispatchResult메서드를 실행한다

     

    processDispatchResult메서드에서는

    ModelAndView를 담아 뷰 렌더링을 호출한다(render메서드)

     

    render메서드에서는 

    ModelAndView에서 viewName을 get하고

    뷰 리졸버로(resolveViewName메서드) 고정경로와 핵심경로를 조합하여 View객체 반환받고

    View객체의 render 메서드를 수행한다

     

    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		
    		// ★★★★★★ 핸들러 조회
    		mappedHandler = getHandler(processedRequest);
    		if (mappedHandler == null) {
    			noHandlerFound(processedRequest, response);
    			return;
    		}
    
    		// ★★★★★★ 핸들러 어댑터 조회
    		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    		// ★★★★★★ 핸들러 어댑터 실행 -> 어댑터에서 핸들러 실행 -> ModelAndView반환
    		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    				
    		// ★★★★★★ 뷰 렌더링 호출
    		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		
    	}
        
        
        
       	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    			@Nullable Exception exception) throws Exception {
    
    		// ★★★★★★ render 메서드 실행
    		if (mv != null && !mv.wasCleared()) {
    			render(mv, request, response);
    			if (errorView) {
    				WebUtils.clearErrorRequestAttributes(request);
    			}
    		}
    	} 
        
        
        
        protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    	
    		View view;
    		String viewName = mv.getViewName();
    		if (viewName != null) {
    			// ★★★★★★ 뷰 리졸버로 view 반환받음
    			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    		}
    
    		try {
    			if (mv.getStatus() != null) {
    				response.setStatus(mv.getStatus().value());
    			}
    			// ★★★★★★ 뷰 렌더링
    			view.render(mv.getModelInternal(), request, response);
    		}
    	}

     

     

    Handler Mapping과 Handler Adapter

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

    HandlerMapping HandlerAdapter
    0순위 : RequestMappingHandlerMapping
              @RequestMapping에서 사용된다

    1순위 : BeanNameUrlHandlerMapping
              스프링 빈의 이름으로 핸들러를 찾는다



    0순위 : RequestMappingHandlerAdapter
              @RequestMapping에서 사용된다

    1순위 : HttpRequestHandlerAdapter
              HttpRequestHandler 인터페이스에서 사용된다

    2순위 : SimpleControllerHandlerAdapter
              Controller인터페이스에서 사용된다

     

    BeanNameUrlHandlerMapping & SimpleControllerHandlerAdapter

    1. OldController클래스의 빈 이름을 "/springmvc/old-controller"로 지정한다

    2. localhost:8080/springmvc/old-controller로 접속하면 

    3. 일단 @RequestMapping애노테이션이 있는지 확인한다. => 없다

    4. 스프링이 해당되는 핸들러를 빈 이름으로 조회한다 => BeanNameUrlHandlerMapping

    5. BeanNameUrlHandlerMapping가 핸들러인 OldController를 DispatcherServlet에게 반환한다

    6. DispatcherServlet이 핸들러(OldController)를 처리할 수 있는 어댑터를 찾는다  => SimpleControllerHandlerAdapter

    7. SimpleControllerHandlerAdapter에서 핸들러(OldController)를 처리하면서 handleRequest 메서드를 실행한다.

    @Component("/springmvc/old-controller")
    public class OldController implements Controller {
    	//@Component애노테이션 : 컴포넌트스캔 시 스프링 빈으로 등록한다,
    	//빈 이름을 /springmvc/old-controller로 지정한 것이다
    	//@WebServlet애노테이션을 지정하지 않았는데
    	//localhost:8080/springmvc/old-controller로 접속이 가능하다.. 어떻게..????
    
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            System.out.println("OldController.handleRequest");
            return null;
        }
    }

     

     

    BeanNameUrlHandlerMapping & MyHttpRequestHandler

    1. MyHttpRequestHandler클래스의 빈 이름을 "/springmvc/request-handler"로 지정한다

    2. localhost:8080/springmvc/request-handler로 접속하면 

    3. 일단 @RequestMapping애노테이션이 있는지 확인한다. => 없다

    4. 스프링이 해당되는 핸들러를 빈 이름으로 조회한다 => BeanNameUrlHandlerMapping

    5. BeanNameUrlHandlerMapping가 핸들러인 MyHttpRequestHandler를 DispatcherServlet에게 반환한다

    6. DispatcherServlet이 핸들러(MyHttpRequestHandler)를 처리할 수 있는 어댑터를 찾는다  => HttpRequestHandlerAdapter

    7. HttpRequestHandlerAdapter에서 핸들러(MyHttpRequestHandler)를 처리하면서 handleRequest 메서드를 실행한다.

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

     - 사실 @RequestMapping이 가장 많이 사용된다

     

     

    View Resolver

    src/main/resources/application.properties 파일에 아래 코드를 추가해야한다.

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

    spring.mvc.view.suffix=.jsp

     

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

    뷰 리졸버는 application.properties에 등록한 prefix, suffix 설정정보를 바탕으로 등록된다.

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

    ★ JSP의 경우 forward()를 통해서 해당 JSP로 이동해야 렌더링된다. 

    ★ JSP를 제외한 나머지 뷰 템플릿은 forward()과정 없이 바로 렌터링된다

     

     

    1. SimpleControllerHandlerAdapter에서 핸들러(OldController)를 처리하면서 handleRequest메서드를 실행한다.

    2. 반환받은 ModelAndView객체에서 "new-form" 논리경로를 get한다

    3. RequestDispatcher에서 viewResolver를 호출하고, BeanNameViewResolver가 "new-form" 으로 지정된 빈을 찾는다 => 없음

    4. InternalResourceViewResolver가 실행되고 InternalResourceView를 반환하고

    5. InternalResourceView에서 render처리 후 forward메서드를 호출하여 jsp를 실행한다.

    @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 new ModelAndView("new-form");
        }
    }

     

     

     

    @RequestMapping("주소")

    메서드와 함께 사용된다. 메서드명은 임의로 지으면된다.

    클라이언트에서 주소를 호출하면 메서드가 호출된다.

     

    @RequestMapping에 해당되는 핸들러는 RequestMappingHandlerMapping, 

    @RequestMapping에 해당되는 어댑터는 RequestMappingHandlerAdapter 이다.

     

    RequestMappingHandlerMapping의 isHandler메서드는

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

     

    스프링MVC적용

    v1

     - 각 컨트롤러들에 @Controller, @RequestMapping을 적용했다

     - @Controller는 클래스레벨에, @RequestMapping은 메서드레벨에 붙이고 공통경로도 모두 작성했다.

     - ModelAndView를 사용하여 request.setAttribute를 대신하고 View경로를 저장했다

     

    v2

     - 각 컨트롤러들을 한 파일로 통합한다.

     - @RequestMapping을 클래스레벨에 붙이고 공통경로를 지정해서 각 메서드에 붙는 @RequestMapping은 핵심경로만 지정하면된다.

     

    v3

     - ModelAndView의 저장부분을 Model 파라미터로 대신한다

     - 각 메서드의 반환값을 ModelAndView에서 String으로 변환하여 viewName을 직접 반환한다.

     - String username = request.getParameter("username")을 @RequestParam("username") String username 으로 변환했다. (Integer.parseInt와 같은 형변환은 자동으로 처리해준다)

     - @RequestMapping(method = RequestMethod.GET) 으로 HTTP Method도 구분할 수 있다. 

       이것을 @GetMapping, @PostMapping으로 더 쉽게 작성할 수 있다.

     

     

     

     

    인프런 - 스프링MVC 백엔드 웹개발 핵심기술

     

    반응형

    'Backend' 카테고리의 다른 글

    Spring Validation 정리  (0) 2021.12.08
    스프링MVC 기본기능  (0) 2021.12.03
    FrontController, View, Model, Adapter, Handler  (0) 2021.12.01
    서블릿, JSP, MVC패턴  (0) 2021.11.25
    서블릿, HTTP Request, HTTP Response, ObjectMapper  (0) 2021.11.24
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기