스프링 MVC 구조 이해

inhalin·2022년 10월 25일
0

김영한님의 스프링 MVC 1편 강의를 듣고 정리한 내용입니다.

스프링 MVC 구조

직접 만든 프레임워크

  • FrontControllerServlet이 HttpServlet 상속
    • HandlerMappingMap을 Map으로 만들고 initHandlerMappingMap()에서 직접 목록을 추가
    • MyHandlerAdapter를 List로 만들고 initHHandlerAdapters()에서 직접 목록을 추가
  • servie()안에서getHandler(), getHandlerAdapter() 해서 가져온 핸들러로 처리한 결과를 ModelView로 반환받는다.
  • ModelView의 getViewName()으로 뷰이름 받아서 MyView 생성
  • MyView의 render()안에서 RequestDispatherforward() 해준다.
// 프론트 서블릿
public class FrontControllerServletV5 extends HttpServlet {
    private final Map<String, Object> handlerMappingMap = new HashMap<>();
    private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();

    private void initHandlerMappingMap() {
        handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put(...);
        ...
    }
    
    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(...);
        ...
    }
    
    protected void servie(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object handler = getHandler(request);
        MyHandlerAdapter adapter = getHandlerAdapter(handler);
        
        //실제 처리
        ModelView mv = adapter.handle(request, response, handler);
        
        MyView view = viewResolver(mv.getViewName());
        
        // 뷰 렌더링
        view.render(mv.getModel(), request, response);
    }
}

// 뷰
public class MyView {
	public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ...
        dispatcher.forward(request, response);
    }
}

Spring MVC

  • DispatcherServlet이 HttpServlet 상속
    • HandlerMapping을 List로 만들고initHandlerMappings()에서 빈을 가져옴
    • HandlerAdapter를 List로 만들고 initHandlerAdapters()에서 빈을 가져옴
  • DispatcherServlet의 doService()안에서 doDispatch() 호출
    • doDispatch() 안의 getHandler(), getHandlerAdapter()로 받아온 핸들러로 처리한 결과를 ModelAndView로 반환 받음
    • processDispatchResult()에 인자로 넘기면 그 안에서 render()
      • render()안에서 resolvViewname()으로 View를 받환 받음
  • 반환 받은 View의 render()로 화면을 렌더링
// 서블릿
public class DispatcherServlet extends FrameworkServlet {

    @Nullable
    private List<HandlerMapping> handlerMappings;
    @Nullable
    private List<HandlerAdapter> handlerAdapters;
    
    private void initHandlerMappings(ApplicationContext context) {
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(...);
    }
    
    private void initHandlerAdapters(ApplicationContext context) {
		Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(...);
    }
    
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ...
    	doDispatch(request, response);
    }
    
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = null;
        mappedHandler = getHandler(processedRequest);
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        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(mv, request, response);
    }
    
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	    View view;
		String viewName = mv.getViewName();
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        view.render(mv.getModelInternal(), request, response);
    }
}

// 뷰
public interface View {
	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

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

스프링에서 컨트롤러를 호출하려면

  1. HandlerMapping에서 스프링 빈 이름으로 핸들러(=컨트롤러)를 찾아온다.
  2. HandlerAdapter에서 1번에서 찾은 핸들러를 실행할 수 있는 어댑터를 찾아온다.

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

HandlerMapping

  1. RequestMappingHandlerMapping: @RequestMapping 애노테이션 붙은 핸들러 찾음
  2. BeanNameUrlHandlerMapping: url이랑 같은 이름의 스프링 빈 이름으로 핸들러 찾음

이렇게 해서 찾은 컨트롤러를 핸들러로 가져온다. 그 다음 이 핸들러를 처리할 수 있는 어댑터를 다시 찾아야 한다.

HandlerAdapter

  1. RequestMappingHandlerAdapter: @RequestMapping 애노테이션 붙은 어댑터 찾음
  2. HttpRequestHandlerAdapter: HttpRequestHandler 처리
  3. SimpleControllerHandlerAdapter: Controller 인터페이스 처리(과거)

뷰 리졸버

스프링부트가 application.properties에 있는 설정정보를 가져와서 InternalResourceViewResolver라는 뷰 리졸버를 자동 등록한다.

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

// DispatcherServlet.properties
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

// InternalResourceViewResolver.java
public class InternalResourceViewResolver extends UrlBasedViewResolver {
    public InternalResourceViewResolver(String prefix, String suffix) {
		this();
		setPrefix(prefix); // -> UrlBasedViewResolver.setPrefix()
		setSuffix(suffix); // -> UrlBasedViewResolver.setSuffix()
	}
}

// UrlBasedViewResolver.java
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
    public void setPrefix(@Nullable String prefix) {
		this.prefix = (prefix != null ? prefix : "");
	}
    public void setSuffix(@Nullable String suffix) {
		this.suffix = (suffix != null ? suffix : "");
	}
}

HandlerAdapter로 논리적 이름으로 뷰 리졸버가 호출되면

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

0개의 댓글