스프링 MVC 전체 구조

zhzkzhffk·2022년 6월 2일
0

본 포스팅은 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 보고 정리한 내용입니다.


스프링 MVC 구조

DispatcherServlet 구조

스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어 있다.
프론트 컨트롤러 → DispatcherServlet

스프링 부트는 DispatcherServlet을 서블릿은 자동 등록하면서 모든 경로에 대해서 매핑한다.

  • 스프링 부트가 내장 톰캣을 띄울 때 서블릿을 자동 등록하면서 WAS를 띄운다.
  • 자세한 경로가 우선순위가 높다. 기존의 서블릿으로 만약 등록하면 @WebServlet 함께 동작한다.

view.render(mv.getModelInternal(), request, response);

  • 서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
  • DispatcherServlet는 FrameworkServlet 추상클래스에서 추상메서드로 정의한 doService 메서드를 구현을 하고 있다.
  • DispatcherServlet은 doService는 DispacherServlet.doDispatch()를 호출한다.
public class DispatcherServlet extends FrameworkServlet {

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	
        //...
        //...
    	try {
			doDispatch(request, response);
		}
		finally {
        	//...
            //...
        }
    }
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	
    	try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request. 
                // 핸들러 조회
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response); // response.sendError(HttpServletResponse.SC_NOT_FOUND);
					return;
				}
                
                // Determine handler adapter for the current request. 
                // 핸들러 어댑터 조회 - 핸들러를 처리할 수 이쓴 어댑터
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                
                // dispatcher 
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler. 
                // 핸들러 어댑터 실행 -> 핸들러 어댑터를 통해 핸들러 실행 -> ModelAndView 반환
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				
                // 
				processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}

	}
    
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}
    
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
            
            //...
       
            // Did the handler return a view to render?
            // 뷰 rendering 호출
			if (mv != null && !mv.wasCleared()) {
				render(mv, request, response);
                if (errorView) {
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("No view rendering, null ModelAndView returned.");
                }
            }
            
            //...
            
	}
    
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    	View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name.
            // 뷰 Resolver를 통해서 뷰 찾기, View 반환
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            //....
        }
        
        //...
        
        // 뷰 rendering
        view.render(mv.getModelInternal(), request, response);
	}
            

동작순서

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

* ThymeleafView *

ContentNegotiationViewResolver 를 통해 ThymeleafView 객체를 찾고 찾은 Thymeleaf 객체를 통해 동적 렌더링을 시작한다.

ThymeleafView 객체는 렌더링을 하기 위해 여러가지 정보들을 가지고 있는 Context 객체를 만든다. Context 객체 안에는 Model 정보 뿐 아니라 Locale 과 관련된 정보, Thymeleaf Template 파일을 찾기 위한 TemplateResolver , Request 와 Response 같은 정보도 포함하고 있다.

	@Bean
    @ConditionalOnMissingBean(name = "thymeleafViewResolver")
    ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
    	ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine);
        resolver.setCharacterEncoding(properties.getEncoding().name());
        resolver.setContentType(
        appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
        resolver.setProducePartialOutputWhileProcessing(
        properties.getServlet().isProducePartialOutputWhileProcessing());
        resolver.setExcludedViewNames(properties.getExcludedViewNames());
        resolver.setViewNames(properties.getViewNames());
        // This resolver acts as a fallback resolver (e.g. like a
        // InternalResourceViewResolver) so it needs to have low precedence
        resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
        resolver.setCache(properties.isCache());
        return resolver;
        }
profile
Backend Developer

0개의 댓글