spring mvc 용어와 구조 정리

bo04·2022년 6월 15일
0

spring-mvc1

목록 보기
8/9

용어

front Controller

  • 프론트 컨트롤러 도입 전

    여러개의 컨트롤러 서블릿이 모두 흩어져있어서 입구가 여러개라고 할 수 있음.

  • 프론트 컨트롤러 도입 후

맨 앞에 입구 역할을 하는 컨트롤러 서블릿을 하나 두는데 이 컨트롤러가 프론트 컨트롤러임.
프론트 컨트롤러 덕분에 실제 역할을 하는 컨트롤러들은 서블릿을 직접 사용하지 않게 되고 이전처럼 직접 클라이언트의 호출을 받지 않고 프론트 컨트롤러의 호출에 따라 호출됨.

스프링 웹 MVC의 핵심인 DispatcherServlet이 FrontController 패턴으로 구현되어 있음

view resolver

뷰 이름을 가지고 실제 물리 뷰 경로로 변경해주면서 동시에 실제 물리 경로에 있는 view 객체를 반환해줌

대략 이런식으로...

private MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

handler

만약 다양한 컨트롤러 방식을 동시에 사용하고 싶으면 어떻게 될까? -> 어댑터 패턴을 사용하자!

어댑터 패턴을 쓰면 컨트롤러뿐만 아니라 어떠한 것이라도 중간에 어댑터가 있기 때문에 다 처리할 수 있으므로 컨트롤러를 더 넓은 범위인 handler라고 지칭하게 됨.

handler adapter

adapter=원래라면 호환이 불가능한 것들이지만 맞는 어댑터를 중간에 연결하면 호환이 되게 됨. 대표적으로 유럽과 한국의 전기 콘센트

위에서 컨트롤러=handler라고 지칭한다고 했으니 handler adapter는 해당 컨트롤러에 맞는 어댑터임. 맞는 어댑터만 찾으면 어떤 형식이라도 프로젝트에 연결할 수 있음.

구조

직접 만든 mvc 구조

실제 spring mvc 구조

둘이 용어만 다를뿐 사실상 쓰임새는 똑같음.

DispatcherServlet=front controller

상속관계가 DispatcherServlet->FrameworkServlet-> HttpServletBean->HttpServlet이므로 오버라이드된 HttpServletservice()가 호출되면서 DispatcherServlet.doDispatch()가 호출됨.

public class DispatcherServlet extends FrameworkServlet {

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
      ...
      doDispatch(request, response);
      ...
    }

	protected void doDispatch(HttpServletRequest request, HttpServletResponse
  response) throws Exception {
      // 핸들러(컨트롤러) 찾기
      // 찾은 핸들러에 맞는 핸들러 어댑터 찾기
      // 핸들러 어댑터가 실행됨->핸들러 어댑터가 실행되면서 핸들러가 실행됨->modelAndView 반환
      // view resolver를 통해 뷰 객체 리턴
      // 맞는 view에 데이터를 넣어서 클라이언트에게 보여줌
    }
}

handlerMapping과 handlerAdapter의 우선순위

밑의 번호가 적을수록 우선순위가 높고 이 순서대로 자동 적용(등록)됨

  • HandlerMapping
  1. @RequestMapping 애노테이션을 단 클래스를 찾으면 핸들러로 자동으로 적용됨
  2. 1번을 못찾으면 스프링 빈 이름으로 핸들러를 찾아서 자동 적용됨
  • HandlerAdapter
  1. @RequestMapping 애노테이션을 단 클래스를 찾으면 자동으로 핸들러 어댑터로 등록함
  2. 1번을 못찾으면 HttpRequestHandlerAdaptersupport()의 true로 리턴될 HandlerAdapter 객체를 찾아서 핸들러 어댑터로 자동 등록함. 그리고 DispatcherServletdoDispatch()에서 handle()가 실행할때 이 핸들러 어댑터의 오버라이딩한 메소드가 실행됨
  3. 2번을 못찾으면 SimpleControllerHandlerAdaptersupports()가 true로 리턴될 어노테이션이 아닌 Controller 인터페이스 객체를 찾아서 핸들러 어댑터로 자동 등록함. DispatcherServletdoDispatch()에서 handle()가 실행할때 이 핸들러 어댑터의 오버라이딩한 메소드가 실행됨

예제

@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;
    }
}
  • 위의 메소드가 localhost:8080/springmvc/old-controller에서 동작할 수 있는 이유

HandlerMapping의 2번에 해당됨(빈 이름으로 찾아서 핸들러로 적용됨) + HandlerAdapter의 3번에 해당됨(Controller 인터페이스를 사용하고 있으므로 해당 클래스가 핸들러 어댑터가 되고 나중에 DispatcherServlethandler()을 호출할때 해당 )

  • DispatcherServletdoDispatch()에서의 handle() 부분
public class DispatcherServlet extends FrameworkServlet {
	...
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    ...
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    ...
  }
}

view resolver

일반적으로 jsp를 사용할때 application.properties

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

이런 jsp 경로를 직접 설정해주기만해도 작 동작하는 이유는 기본적으로 view resolver가 InternalResourceViewResolver를 호출하기 때문에 저렇게 prefix,suffix를 설정해놓으면 알아서 경로에 맞는 jsp파일을 찾기 때문임.

그리고 InternalResourceViewResolverInternalResourceView를 리턴하는데, InternalResourceViewforward()를 해주기 때문에 view.render()가 호출될때 해당forward()가 호출돼서 해당 jsp를 클라이언트에게 보여줄 수 있음.

public class InternalResourceViewResolver extends UrlBasedViewResolver {
	public InternalResourceViewResolver(String prefix, String suffix) {
		this();
		setPrefix(prefix);
		setSuffix(suffix);
	}
    @Override
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		InternalResourceView view = (InternalResourceView) super.buildView(viewName);
		if (this.alwaysInclude != null) {
			view.setAlwaysInclude(this.alwaysInclude);
		}
		view.setPreventDispatchLoop(true);
		return view;
	}
}
public class InternalResourceView extends AbstractUrlBasedView {
	@Override
	protected void renderMergedOutputModel(
      ...
      rd.forward(request, response);
      ..
    }
}

0개의 댓글