Spring MVC | Controller는 어떻게 요청을 처리하는 걸까? | Controller와 Servlet
지금까지 개발하면서 아무 생각 없이 Controller를 사용해 왔다.
Controller의 동작 원리를 모른 채로!
그래서 지금부터
Spring의 Controller는 어떤 방식으로 동작하는지,
그러니까 클라이언트의 요청을 Controller가 어떤 과정으로 처리하게 되는 건지를 정리하고 넘어간다.
1. Controller가 어떤 과정으로 클라이언트 요청을 받는지
2. Controller가 어떤 과정으로 응답을 내놓는지
(=> 즉 Spring MVC의 동작구조를 살펴보자는 것임)
Spring MVC
다들 알 것이다. Model, View, Controller!
클라이언트 요청 관련 역할분담을 M, V, C로 한 것이다.
명확한 역할을 나누어 개발하기 때문에 웹개발을 쉬워지게 한다.
1. Controller가 어떤 과정으로 클라이언트 요청을 받는지
어쨌든 요청을 처리하는 가장 기본적인 것은 Servlet !
클라이언트의 요청을 처리하는 가장 기본적인 기반이 Servlet인 것은 이미 배웠다. 클라이언트의 요청은 Servlet의 어떠어떠한 과정을 통해 처리되고 응답으로 돌아온다.
>> 자세한 것은 패스!
Spring MVC 구조를 사용하면 Servlet을 직접 작성하여 요청을 받을 일은 없다.
'Front Controller'라고 해서, 앞단에 있는 하나의 Controller가 요청을 받아주기 때문이다.
그런데 결국 Front Controller는 내부적으로 하나의 Servlet을 가지고 있고, 그 Servlet이 요청을 처리한다.
요청을 처리하는 가장 기본은 Servlet이긴 하지만,
우리가 Servlet을 직접 작성하지 않아도 된다는 것이다!
"스프링 MVC는 Front Controller Pattern을 사용해서 Dispatcher Servlet을 제공한다"
라고 요약할 수 있겠는데, 무슨 말인지 차근차근 공부해 보자.
Spring MVC 패턴에서는
요청을 받기 위해 Servlet을 직접 작성하지 않고
Controller를 작성해 사용한다.
Spring 기능을 사용하면 우리가 직접 Servlet 하나하나를 작성하지 않아도 된다.
Spring MVC에서는, 요청을 받기 위한 Controller를 작성하면 된다.
그럼 Spring이 우리가 작성한 Controller를 호출하여 클라이언트 요청을 처리한다.
이때, 우리가 작성한 Controller들을 처리하기 위해 가장 앞단에 대표적으로 'Front Controller'를 하나 둔다.
요청은 가장 먼저 Front Controller로 들어오고, 조건에 맞게 우리가 작성한 각각의 Controller로 연결(위임)해주는 원리로 요청을 처리하게 된다.
그럼 이 Front Controller가 어떻게 적합한 Controller로 위임해 줄 수 있을까?
이를 알기 위해 Front Controller의 내부 구조를 알 필요가 있다.
Front Controller 내부의 Dispatcher Servlet ★
Front Controller에는 'Dispatcher Servlet'이라는 Servlet이 있다.
지난 실습을 기억해 보면, Servlet이 http 요청을 받아서 뭐 service() 호출해서 doGet(), doPost() 같은 거 호출하고 어떻게 어떻게 해서 응답을 내놓았다.
Front Controller 내부에 있는 Dispatcher 'Servlet'도 클라이언트 요청을 처리한다고 보면 된다.
∨ Dispatcher Servlet이 요청을 받으면 일어나는 일들
① 일단 Servlet이니까 당연히 http 요청을 받는다.
② Servlet은 요청에 맞는 (우리가 작성한) Controller를 찾아서 요청처리를 위임한다.
③ 처리를 위임받은 Controller가 이제 알아서 로직수행 후에 결과를 리턴할 것이다. Spring MVC 패턴을 사용하고 있으므로, Model과 View가 만들어진다. 화면(View)에 전달할 Model을 만들고, View에다가 전달한다. (참고: 다양한 형식의 View가 만들어질 수 있다. jsp뷰일 수도, xml 뷰일 수도, json뷰일 수도.. )
④ Controller는 Model과 View를 Dispatcher Servlet에게 전달한다.
⑤ 그럼 Dispatcher Servlet은 Model과 View를 가지고 콘텐츠를 만든다. 완성된 View를 http response body에 담아서 클라이언트에게 전달한다.
정말 든든한 Dispatcher Servlet이구나..
클라이언트 요청을 받으면 기본적인 것은 다 Dispatcher Servlet이 처리해주고 있다.
(어떠한 Controller로 처리할지, 예외가 발생하면 어떻게 할지 등)
참고) Dispatcher Servlet을 만드는 방법
크게 두 가지가 있다.
우리가 Servlet을 등록할 때와 동일하다.
1. web.xml에 등록하기 (링크 참고)
2. WebApplicationInitializer를 구현하여 override onStartup()하기 (=> ApplicationContext에다가 Serlvlet을 등록)
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DistpatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
dispatcher servlet을 만들어서 context에 등록시키면 된다.
그리고 registration.addMapping("/app/*")해주면 app 아래에 있는 요청은 모두 다 해당 Dispatcher Servlet으로 가게 된다. (이런 방식으로 dispatcher servlet을 여러개 등록할 수도 있다.)
어떤 dispatcher servlet을 뒀다면, 그 dispatcher servlet이 요청처리를 위임할 하위의 Controller를 다양하게 작성하는 방법으로 요청을 처리한다.
지금까지
앞단의 Front Controller가 우리가 작성한 Controller로 처리를 위임한다는 것을 알았고,
조금 더 자세히, Front Controller 내부적으로 Dispatcher Servlet이 그 역할을 한다는 것을 알았다.
그러면 조금 더 들어가서,
어떻게 Dispatcher Servlet은 적절한 Controller와 연결해 줄까?
Dispatcher Servlet은 주로 요청의 url을 기준으로 어떤 Controller와 매핑시킬지 결정한다.
(물론 파라미터 정보랑 http method 또한 고려함)
이제부터 Handler라는 용어를 알아야 한다. 별 거 아니고 Spring에서 Controller를 Handler라고도 부른다. 이 용어를 사용하여 다시 설명하면, Dispatcher Servlet은 클라이언트 요청을 어떤 Handler(=>Controller)가 처리할지 매핑해 주어야 한다. 이를 말 그대로 'HandlerMapping'이라고 한다!
참고)
Handler Mapping의 결정도 여러 방법이 있는데, 기본적으로 많이 쓰는 것이 'RequestMappingHandlerMapping'이다. 말 그대로 Request를 Mapping하는 HandlerMapping인 것이다.
∨ 아무튼 매핑 어노테이션을 사용하면 된다
Controller를 작성할 때를 생각해 보면, 메서드나 클래스에 @RequestMapping 어노테이션을 사용했다.
참고: @GetMapping 등은 메서드에만 작성할 수 있다.
요청 url이 들어오면, 어떤 Controller의 @RequestMapping에 명시한 url이 존재하는지 찾아보고, 그 Controller랑 연결한다. 즉 @RequestMapping을 사용하면 클라이언트 요청을 어느 Handler(Controller)가 처리할지 결정된다. HandlerMapping에 의해서!
처리할 Controller가 결정이 되었다면,
수행할 메서드와 파라미터는 어떻게 전달되는지?
Handler Adapter가 잘 처리하여 메서드를 수행시킨다.
Handler(Controller)가 호출될 때, 그 Handler가 가지고 있는 method의 파라미터에 맞게 정보가 들어와야 한다.
즉 대상 Handler의 method를 실행하기 위해 'Handler Adapter'가 필요하다.
결정된 Handler 내부의 method를 호출하기 위해 Serlvet과 Handler 사이에 Adapter를 놓는 것이다.
Dispatcher Servlet은 어떤 Handler에 잘 adapt하기 위해서 적절한 HandlerAdapter를 찾아내고,
적절한 Handler Adapter가 대상 Handler의 메서드를 잘 사용할 수 있도록 파라미터 등을 잘 adapt해준다.
(파라미터 잘 넣어주는 건 HandlerAdapter의 ArgumentResolver가 해준다.)
참고)
이때 어떤 Adapter를 사용하게 되는지는 Handler Adapter 전략에 따라 다르다.
Spring MVC는 4종의 Handler(Controller)를 제공하여 HandlerAdapter도 4종류가 있다.
자주(기본적으로) 쓰이는 것은 RequestMappingHandlerAdapter다.
∨ 아무튼 매핑 어노테이션을 사용하면 된다
우리가 Controller 작성할 때 @RequestMapping이라는 어노테이션을 써주면,
저 RequestMappingHandlerAdapter가 우리의 메서드를 보고 알아서 잘 변환해 준다.
잠시 여기까지 정리!
▷ 그래서 @RequestMapping 어노테이션을 사용할 경우,
- RequestMappingHandlerAdapter
- RequestMappingHandlerMapping
이 두 개가 쌍으로 같이 사용된다.
▷ 다시말해 @RequestMapping을 위해서는
- HandlerAdapter도 필요하고
- HandlerMapping도 필요하다.
▷ 좀 더 풀어말하면,
- Handler가 해당 요청을 받을 수 있게 파라미터대로 잘 변환해 주는 Adapter도 필요하고
- 어떤 Handler가 요청을 처리할지 Mapping해주는 HandlerMapping도 필요하다.
지금까지 한 것은
"Controller가 어떤 과정으로 클라이언트 요청을 받는지"이다.
2. Controller가 어떤 과정으로 응답을 내놓는지
keyword : ViewResolver
Controller는 Model과 View를 반환한다.
실행할 View를 찾는 일은 'ViewResolver'라는 친구가 해준다.
Controller가 반환한 View name과 일치하는 View를 이 친구가 찾아나서주는 것이다.
참고) DispatcherServlet에는 여러개의 ViewResovler가 등록되어있다.
체인처럼 여러 ViewResolver가 연결되어있는데, 첫번째 resolver에서 View name과 일치하는 것이 있는지 찾아본다. 없으면 다음 resolver가 찾아본다. 결국 어디선가 해당 View를 찾게되면 그것을 Dispatcher Servlet에 반환한다.
아주 간략하게 나타내자면 이렇다.
Http request, response를 처리하는 전체 과정
① DispatcherServlet이 요청을 받으면
② 핸들러 매핑 전략에 따라서
④ 핸들러를 선택한다.
해당 요청을 처리가능한 핸들러가 결정이 된다.
③ DispatcherServlet이 핸들러 어댑터를 통하여
http요청을 처리할 메서드를 호출한다. 이때 http요청 정보를 통해 메서드 파라미터도 알맞게 넣어준다.
④ 핸들러는 구현된 비즈니스로직을 수행하고 모델과 뷰를 반환한다.
⑤ 뷰 네임도 반환한다.
⑥ 뷰 네임과 모델이 만들어졌으니, DispatcherServlet은 뷰 리졸버에다가 뷰 네임과 모델을 전달한다.
뷰 리졸버는 특정 뷰를 찾아 반환하고,
⑦ 찾은 뷰에 대한 response가 완성된다!
마무리
Controller가 클라이언트의 요청을 받아 처리하는 과정을 공부했다.
Front Controller가 앞단에 존재하여 일단 요청을 받아낸다는 것,
Front Controller 내부에는 Dispatcher Servlet이 존재하고,
역시 요청 처리는 Servlet을 기반으로 이뤄진다는 것을 확인했다.
Dispatcher Serlvet 내부에서 요청을 처리해내는 일련의 과정을 훑었다.
Controller의 다른 이름이 Handler라는 것,
그리고 요청을 처리할 수 있는 Handler를 찾아내는 HandlerMapping,
매핑한 Handler의 메서드를 수행할 수 있도록 적절히 요청데이터를 변환해주는 HanderAdapter,
Handler(Controller)가 반환한 뷰네임과 모델을 통해 최종적으로 뷰객체를 리턴해주는 ViewResolver의 존재까지 알게 되었다!
우리가 Springboot를 쓰면 이런 것들을 전혀 셋업할 필요가 없다.
그냥 Controller만 만들면 알아서 원하는 결과가 나온다.
그래도 클라이언트 요청을 처리하는 흐름은 중요해서 이렇게 정리를 해보았다.
나중에 핸들러나 뷰리졸버같은 것들을 직접 개발할 일도 있을 수 있고,
면접에서도 요청 처리 흐름을 물어보실 수 있다고 한다.