프론트 컨트롤러 도입 전
여러개의 컨트롤러 서블릿이 모두 흩어져있어서 입구가 여러개라고 할 수 있음.
프론트 컨트롤러 도입 후
맨 앞에 입구 역할을 하는 컨트롤러 서블릿을 하나 두는데 이 컨트롤러가 프론트 컨트롤러임.
프론트 컨트롤러 덕분에 실제 역할을 하는 컨트롤러들은 서블릿을 직접 사용하지 않게 되고 이전처럼 직접 클라이언트의 호출을 받지 않고 프론트 컨트롤러의 호출에 따라 호출됨.
스프링 웹 MVC의 핵심인 DispatcherServlet
이 FrontController 패턴으로 구현되어 있음
뷰 이름을 가지고 실제 물리 뷰 경로로 변경해주면서 동시에 실제 물리 경로에 있는 view 객체를 반환해줌
대략 이런식으로...
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
만약 다양한 컨트롤러 방식을 동시에 사용하고 싶으면 어떻게 될까? -> 어댑터 패턴을 사용하자!
어댑터 패턴을 쓰면 컨트롤러뿐만 아니라 어떠한 것이라도 중간에 어댑터가 있기 때문에 다 처리할 수 있으므로 컨트롤러를 더 넓은 범위인 handler라고 지칭하게 됨.
adapter=원래라면 호환이 불가능한 것들이지만 맞는 어댑터를 중간에 연결하면 호환이 되게 됨. 대표적으로 유럽과 한국의 전기 콘센트
위에서 컨트롤러=handler라고 지칭한다고 했으니 handler adapter는 해당 컨트롤러에 맞는 어댑터임. 맞는 어댑터만 찾으면 어떤 형식이라도 프로젝트에 연결할 수 있음.
둘이 용어만 다를뿐 사실상 쓰임새는 똑같음.
DispatcherServlet
=front controller상속관계가 DispatcherServlet
->FrameworkServlet
-> HttpServletBean
->HttpServlet
이므로 오버라이드된 HttpServlet
의service()
가 호출되면서 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에 데이터를 넣어서 클라이언트에게 보여줌
}
}
밑의 번호가 적을수록 우선순위가 높고 이 순서대로 자동 적용(등록)됨
@RequestMapping
애노테이션을 단 클래스를 찾으면 핸들러로 자동으로 적용됨@RequestMapping
애노테이션을 단 클래스를 찾으면 자동으로 핸들러 어댑터로 등록함HttpRequestHandlerAdapter
의 support()
의 true로 리턴될 HandlerAdapter
객체를 찾아서 핸들러 어댑터로 자동 등록함. 그리고 DispatcherServlet
의 doDispatch()
에서 handle()
가 실행할때 이 핸들러 어댑터의 오버라이딩한 메소드가 실행됨SimpleControllerHandlerAdapter
의 supports()
가 true로 리턴될 어노테이션이 아닌 Controller 인터페이스 객체를 찾아서 핸들러 어댑터로 자동 등록함. DispatcherServlet
의 doDispatch()
에서 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 인터페이스를 사용하고 있으므로 해당 클래스가 핸들러 어댑터가 되고 나중에 DispatcherServlet
이 handler()
을 호출할때 해당 )
DispatcherServlet
의 doDispatch()
에서의 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());
...
}
}
일반적으로 jsp를 사용할때 application.properties
에
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
이런 jsp 경로를 직접 설정해주기만해도 작 동작하는 이유는 기본적으로 view resolver가 InternalResourceViewResolver
를 호출하기 때문에 저렇게 prefix,suffix를 설정해놓으면 알아서 경로에 맞는 jsp파일을 찾기 때문임.
그리고 InternalResourceViewResolver
는 InternalResourceView
를 리턴하는데, InternalResourceView
는 forward()
를 해주기 때문에 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);
..
}
}