IoC Container
- 자바 Configuratio file 로 web.xml 로 대체가 가능하다.
- 을 통해서 AnnotationConfigWebApplicationContext 로 바꿔준뒤 (왜냐면 우리는
@Configuration
를 사용할 것이기 때문에)
- 추가 해준뒤 에 contextConfigLocation 을 해준뒤 해당 위치를 추가해준다.
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.
AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>ConfigurationFilePath</param-vlaue>
</context-param>
- 이렇게 해서 실행시키게 되면
ContextLoaderListener
가 AnnotationConfigWebApplicationContext
를 생성할 때 ConfigurationFilePath
를 기반으로 해당 객체를 생성하게 됩니다. 그러면 ConfigurationFilePath
에 적혀있는 Bean
파일들이 해당 Context 안에 존재하게 됩니다.
- 이 과정은 Spring 본문 옮기기 를 참조하여 읽으면 좋다. (내가 번역하고 있는 건데.. 오역이 있을 수도 있다. 확실히 MVC 를 공부하면서 저걸 참조하여 보니 더 공부가 잘된다. 조금이라도 번역해두길 잘했다.)
- 코드를 살펴보면
WebApplicationContext
가 초기화 되는 시점에 ROOT_WEB_APPLICATION_CONTEXT_ ATTRIBUTE
를 Application Context
를 등록하는데 우리가 이 ROOT_WEB_APPLICATION_CONTEXT
_ATTRIBUTE
로 꺼내쓸수 있는 것이다.
ApplicationContext ac = getServletContext().getAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
- 이제야 IoC 에 대해서 조금씩 이해가 오기시작한다. 그니까 우리가
Bean
을 만들고 관리하는 것이 아닌 IoC Container
에 맡긴 뒤 우리는 해당 컨테이너에서 받아와서 쓰는 입장이 되는것이다. 그래서 Inversion of Controll (제어의 역전) 이라고 부르는 것 같다.
Front Controller
- 우리는 Servlet 을 추가해줄 때 마다 Web.xml 에 해당 정보를 추가해주어야 한다. (이런것도 일련의 중복이라고 볼 수 있겠다.)
- 여하튼 이렇게 하다보면 동일작업을 처리해줘야 하는 일도 생길 수 있는데, 그 때 마다 Filter 로 하기에는 너무 복잡하고, 계속하여 중복될 것이다. 그래서
FrontController
라는 개념이 등장하게 되었다.
- 만약 Client 로 부터 Request 가 들어오면
FrontController
가 해당 요청을 처리해야할 Controller
에게 Dispatch
해준다. Spring 은 해당 FrontController 를 Dispatcher Servlet
으로 구현해 두었다.
Root WebApplicationContext
- DispatcherServlet 안에서 만드는 Servlet 이나 그런것들은 DispatcherServlet 안으로 Scope가 제한된다. 그래서 다른 DispatcherServlet 에서는 해당 DispatcherServlet 에 대해 모르고 있다. 그런데 우리가 DispatcherServlet 간 같은 정보를 공유해야 할 때가 있는데 그래서 RootWebApplicationContext 를 구분지어 놓은 것이다. 해당
RootWebApplicationContext
의 내용은 서로 다른 DispatcherServlet 에서도 공유되어 사용될 수 있다. 그래서 RootWebApplicationContext
에 담긴 정보는 Web 에 관한 정보는 없으며, Service 와 Repositories 에 관련된 정보들이 존재한다.
Servlet WebApplicationContext
- 여기에는 Dispatcher Servlet 안에서 생긴 Bean 들이 존재하는데
Controllers
, ViewResolver
, HandlerMapping
등 웹과 관련된 빈들의 정보가 담긴다.
위의 두 얘기를 살짝 종합하면..
- Service, Repositories 등등은 ContextLoaderListener 쪽에 등록되어야 하고, Controller, ViewResolver, HandlerMapping 등은 이제 Dispatcher 쪽에 등록되어야 한다. 아래에서 init-param 을 통해서 해당 과정을 구현해볼 것이다.
Spring Dispatcher Servlet
- 우리는 이제 Spring 이 제공해주는 Dispatcher Servlet 을 등록해야 한다. 우리가 적은 Annotation 을 해석해주고, 우리가 return 하는 값들을 제대로 해석해줄 수 있는
Dispatcher Servlet
을 말이다.
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass<param-name>
<param-value>ACWA</param-value>
<init-param>
<init-param>
<param-name>contextConfigLocation<param-name>
<param-value>contextConfigFilePath</param-value>
<init-param>
</servlet>
- 뭐 이렇게 되면 ContextLoader 의 경우 Configuration File 에 Component Scan 을 통해서
exclude Filters = @ComponentScan.Filter(Controller.class)
를 통해서 Controller / ViewResolver / HandlerMapping 등등의 파일을 등록해야 하지 않을 것이다.
- Dispatcher 는 반대로 저것들만 등록하면 될것이다~ 그건 자신의 현재파일에 맞게맞게 설정!
- 이제 mapping information 을 통해서 내가 만든 dispatcher servlet 은 roach url 에 관련한 모든 요청을 처리하도록 하자!
<servlet-mapping>
<servlet-name>roach</servlet-name>
<url-pattern>/roach/*</url-pattern>
</servlet-mapping>
만약 이러한 상속 구조가 귀찮다면..
- Dispatcher Servlet 에 모든 Bean 에 관한 정보를 등록할 수도 있다. 최근에는 Dispatcher Servlet 에 모든 Bean 을 등록하는 추세라고 한다. 하지만 이런 사항들도 알아두면 기술을 이해하는데 좋을 것 같다.
동작원리
HandlerMapping
- Debugger 를 통해 DispatcherService 를 찾아가다보면 기본적으로 2가지 Handler 를 만들어서 사용하는데 거기에는
BeanNameUrlHandlerMapping
과 RequestMappingHandlerMapping
이 있는데 우리가 BeanName 을 Url 과 동일하게 적었을때 해석해주는게 BeanNameUrlHandlerMapping
이녀석 이였나 보다. RequestMappingHandlerMapping
얘는 GetMapping 등등의 어노테이션을 해석해주는 녀석이다. 그리고 이 핸들러가 찾아올 수 있는지 일련의 테스트 과정을 거치면서 어떤 handler 를 써야하는지도 찾는다.
HandlerAdapter
- 위의 과정을 거쳐 찾은 Handler 를 누가 실행시킬 수 있는가? 를 찾는 작업이다.
- 기본적으로
HttpRequestHandlerAdapter
, SimpleControllerHandlerAdapter
, RequestMappingHandlerAdapter
가 존재한다.
참고..
- 도중에 GET 일 경우 캐싱기능이용이 가능하므로, 그거에 대해 판단하는 것도 있다.
- 이 부분을 코드로본건 처음이다. 이론만 알고 있었는데
처리
- 이제 찾아온 HandlerAdapter 를 통해 요청을 처리한다.
- 그리고
invokeHandlerMethod
를 통해서 해당 Controller 내에서 처리할 수 있는 Method 를 호출하게 된다. (자바의 리플렉션을 이용한다고 한다.) 그래서 특정 값이 리턴되면 이걸 또 return 값을 처리해줄 수 있는 Handler 를 찾게된다. 여기에는 15가지 정도가 있는데.. 이건 적지는 않겠다 너무 많다.
간단 요약
- 요청을 분석한다
- 요청을 처리할 핸들러를 찾는다.
- 해당 핸드러를 실행할 수 있는 핸들러 어뎁터를 찾는다
- 핸들러 어댑터를 사용해서 요청을 처리한다
- 예외가 발생했다면, 예외처리 핸들러에 요청 처리를 위임한다.
- 핸들러의 리턴값을 보고 어떻게 처리할지 판단하다.
- 뷰 이름에 해당하는 뷰를 찾아서 렌더링 한다
@ResponseEntity
가 있다면 Converter 를 사용해서 응답 본문을 만든다.
- 응답을 보낸다.
회고
- 한번 더 공부하면서 느끼는 것들이 많다. 사실 스프링은 잠깐 개념적인거만 본 수준이라 처음 해본 수준이랑 비슷하다. 개인적으로 서블릿을 공부하면서 내가 이전에 SpringDocumentation 에 번역해놓은 내용들을 다시보니 확실히 어떤 내용들인지 이해가 됬다.
- 이펙티브 자바에서 이제 끝단원인 병렬 부분을 하고 있는데 아이템 하나를 이해하는데 시간이 상당히 오래걸린다. 그 만큼 배우는 것도 많은데 쓰레드라는게 참 왜 우리가 커스텀하게 쓰면 안좋다는지 알것 같다.
- 이제 내일부터 코드스쿼드 스프링 과정이 시작되는데 스프링 과정동안에는 최대한 JavaDoc 방식으로 주석을 첨부해보려고한다.