Spring-MVC 읽기 #4. AbstractContextLoaderInitializer
이전 글에서 WebApplicationInitializer 구조에 대해 봤다. 이번 글은 WebApplicationInitializer 구현체 중 하나인 AbstractContextLoaderInitializer 클래스에 대해 이야기해볼 예정이다.
코드를 보자.
AbstractContextLoaderInitializer
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
ServletContext에
ContextLoaderListener
를 등록한다
라고 주석에 쓰여 있다. 그리고 이 클래스는 Spring 버전 3.2에 만들어졌고, 클래스 작성을 Arjen Poutsma, Chris Beams, Juergen Hoeller
라는 개발자들이 작성했다. 오픈소스를 보며 유명 개발자가 작성한 코드를 보는 것 또한 하나의 재미다.
- AbstractContextLoaderInitializer#registerContextLoaderListener 메소드는
ContextLoaderListener
를 생성한다. - ContextLoaderListener에 ApplicationContextInitializer<?>[]를 주입(
ContextLoaderListener#setContextInitializers
메소드)한다. - AbstractContextLoaderInitializer#getRootApplicationContextInitializers 메소드는
null
을 반환한다. - ServletContext에 ContextLoaderListener를 추가(
ServletContext#addListener
메소드)한다.
ContextLoaderListener
ContextLoaderListener 클래스는 ContextLoader를 상속(extends)하고, ServletContextListener를 구현(implements)하고 있다.
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
- ContextLoaderListener#contextInitialized 메소드는 ServletContextListener 인터페이스의 구현 메소드다.
- ContextLoaderListener#contextInitialized 메소드의 이름과 주석을 보니
root Web ApplicationContext 초기화
하는 메소드이다. - ContextLoaderListener#contextInitialized 메소드는
ContextLoader#initWebApplicationContext
메소드를 호출한다. - ContextLoaderListener#contextDestroyed 메소드는 ServletContextListener 인터페이스의 구현 메소드다.
- ContextLoaderListener#contextDestroyed 메소드의 이름과 주석을 보니
root Web ApplicationContext를 종료
하는 메소드이다.
아직 자세한 코드를 보지 못해 추정만 할 뿐이다. 두 개의 메소드 중 contextInitialized
메소드만 한번 살펴보자.
ContextLoader#initWebApplicationContext
ContextLoader#initWebApplicationContext
메소드는 ContextLoaderListener 클래스의 부모 클래스인 ContextLoader가 포함하고 있다. 중요한 행동이라고 생각되는 부분을 제외한 나머지 코드는 중략
한다.
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// ... 중략 ...
try {
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// ... 중략 ...
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
- context(WebApplicationContext)가 null이면, WebApplicationContext를 생성(
ContextLoader#createWebApplicationContext
메소드) 한다.- ContextLoader는 AbstractContextLoaderInitializer에서 WebApplicationContext를 생성자로 주입했기 때문에 null이 아니다.
- AbstractContextLoaderInitializer#registerContextLoaderListener 메소드를 다시 보자.
- 부모 WebApplicationContext(
cwac.getParent
메소드)가 null이면, 부모 WebApplicationContext을 load(loadParentContext
메소드) 후 주입(cwac.setParent
메소드) 한다. - WebApplicationContext에 설정(configure)과 초기화(refresh)를 한다. (
configureAndRefreshWebApplicationContext
메소드)
ContextLoader#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// ... 중략 ...
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
- Environment를 WebApplicationContext에 초기화(
((ConfigurableWebEnvironment) env).initPropertySources(sc, null)
) 한다. WebApplicationContext#refresh
메소드를 호출한다.
WebApplicationContext가 자주 등장한다. 이전 글에서도 말했다시피 ApplicationContext 설명은 이 글을 참고하자.
그런데 ServletContextListener#contextInitialized 메소드는 어디서 실행될까?
ContextLoaderListener가 결국 WebApplicationContext#refresh
를 하는 건 알았다. 그런데 초기화하기 위한 ServletContextListener#contextInitialized
메소드는 어디서 누가 실행하는 걸까? 결론부터 말하자면, WAS(Web Application Server)가 실행한다. 나는 Tomcat을 기준으로 코드를 봤는데, Tomcat 코드까지 코드를 분석하는 건 너무 시간이 오래 걸릴 거 같고 아주 좋은 분석 글이 있어 자세한 설명은 Tomcat & Spring Bootstrapping Sequence — 2편 Spring 글로 대신하고, 나는 이 글을 토대로 대략적인 설명만 해야겠다.
- ContextLoaderListener#registerContextLoaderListener 메소드를 다시 보면
servletContext.addListener(listener);
이런 코드가 있다. 이 코드는 ServletContext에 ContextLoaderListener 클래스를 추가(add) 하는 메소드다. - ServletContext는 인터페이스다.
- Tomcat에 ServletContext 인터페이스의 구현체인 ApplicationContext라는 클래스가 있다.
- Spring의 ApplicationContext가 아니다.
- Tomcat의 ApplicationContext다.
ApplicationContext#addListener
메소드를 보면 ServletContextListener 객체를 StandardContext 클래스의addApplicationLifecycleListener
메소드로 추가한다.- ContextLoaderListener는 ServletContextListener 구현체다.
- 그러므로
StandardContext#addApplicationLifecycleListener
메소드로 ContextLoaderListener 추가가 가능하다.
- Tomcat & Spring Bootstrapping Sequence — 2편 Spring 글에 따르면, Tomcat의 라이프사이클에 의해
StandardContext#startInternal
메소드가 호출된다고 한다.StandardContext#startInternal
메소드는 Spring-MVC 읽기 #3. Spring-MVC의 시작 글에서 봤던onStartup
메소드를 호출한다.StandardContext#startInternal
메소드는StandardContext#listenerStart
메소드를 호출한다.StandardContext#listenerStart
메소드는ServletContextListener#contextInitialized
메소드를 호출한다.ServletContextListener#contextInitialized
메소드는 root Web ApplicationContext를초기화(refresh)
한다.
마무리
마지막 Tomcat에서 ServletContextListener#contextInitialized
메소드를 호출하는 부분을 자세히 설명하지 않아 이해하기 조금 어려울 수 있지만, 이해가 안 되면 Tomcat & Spring Bootstrapping Sequence — 2편 Spring을 읽고 다시 읽어보면 좀 더 이해가 잘될 거라 생각한다. 그래도 이해 안 가면 댓글 ㄱ
출처 : https://blog.woniper.net/367?category=699184
'Spring Framework > Spring boot' 카테고리의 다른 글
스프링부트로 이메일보내기(비밀번호 찾기 / 회원가입 이메일 인증) (0) | 2022.05.24 |
---|---|
Spring-MVC 읽기 #7. HandlerMapping (0) | 2020.09.04 |
Spring-MVC 읽기 #6. DispatcherServlet - @Controller는 어떻게 실행될까? (0) | 2020.09.04 |
Spring-MVC 읽기 #5. AbstractDispatcherServletInitializer와 AbstractAnnotationConfigDispatcherServletInitializer (0) | 2020.09.04 |
Spring-MVC 읽기 #3. Spring-MVC의 시작 (0) | 2020.09.04 |
Spring-MVC 읽기 #2. 빌드 (0) | 2020.09.04 |
Spring-MVC 읽기 #1. 나는 왜 오픈소스를 읽을까? (0) | 2020.09.04 |
실행 중인 Spring Boot pid 파일 생성 (0) | 2020.09.04 |