프론트 컨트롤러 도입 전

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

맨 앞에 입구 역할을 하는 컨트롤러 서블릿을 하나 두는데 이 컨트롤러가 프론트 컨트롤러임.
프론트 컨트롤러 덕분에 실제 역할을 하는 컨트롤러들은 서블릿을 직접 사용하지 않게 되고 이전처럼 직접 클라이언트의 호출을 받지 않고 프론트 컨트롤러의 호출에 따라 호출됨.
스프링 웹 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);
..
}
}