ApplicationContext vs DispatcherServlet 2 : 컨텍스트 간의 bean 공유?

ApplicationContext와 DispatcherServlet은 부모와 자식 관계로 연결되어 있는데, 왜 component-scan은 양쪽 다 해야할까? 두 컨텍스트는 같은 빈들을 가리키는 걸까? 밑에 예제에서는 ApplicationContextHolder 라는 클래스를 만들어서 두 컨텍스트를 가지고 있다가, 두 컨텍스트가 가지고 있는 빈의 인스턴스가 같은지 비교해보았다.


application-context.xml


<?xml version="1.0" encoding="UTF-8"?>


<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.tistory"></context:component-scan>

    

    <bean class="com.tistory.jekalmin.common.ApplicationContextHolder"/>

<bean class="com.tistory.jekalmin.dao.TestJdbcTemplate"/>

</beans>

TestJdbcTemplate은 applicationContext에만 등록하고, dispatcherServlet에는 등록하지 않기 위해 어노테이션을 사용하지않고 application-context.xml에서 bean으로 등록하였다.


spring-servlet.xml


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.tistory.jekalmin"/>

<context:annotation-config/>

<bean class="com.tistory.jekalmin.common.ApplicationContextHolder"/>

<bean class="com.tistory.jekalmin.dao.TestJdbcTemplate"/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/view/"/>

<property name="suffix" value=".jsp"/>

</bean>

</beans>

application-context.xml과 spring-servlet.xml 양쪽에 component-scan을 하였다.


ApplicationContextHolder.java


package com.tistory.jekalmin.common;


import java.util.ArrayList;

import java.util.List;


import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;


public class ApplicationContextHolder implements ApplicationContextAware{

public static List<ApplicationContext> list = new ArrayList<ApplicationContext>();

@Override

public void setApplicationContext(ApplicationContext ctx)

throws BeansException {

list.add(ctx);

}


}

두 컨텍스트에서 ApplicationContextHolder 빈을 각각 생성할 때, ApplicationContextHolder 클래스에서 컨텍스트에 접근할 수 있도록 컨텍스트를 등록하였다.


TestDao.java


package com.tistory.jekalmin.dao;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Repository

public class TestDao {

@Autowired

public TestJdbcTemplate jdbcTemplate;

}

TestDao는 어노테이션을 이용해 등록했으므로 양쪽 컨텍스트에서 component-scan을 이용해 등록될 것이다.


TestJdbcTemplate.java


package com.tistory.jekalmin.dao;

public class TestJdbcTemplate {

}

TestJdbcTemplate은 위에 application-context.xml에서만 등록하였다.


TestController.java


package com.tistory.jekalmin.controller;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationContext;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import com.tistory.jekalmin.common.ApplicationContextHolder;

import com.tistory.jekalmin.dao.TestDao;

@Controller

public class TestController {

@Autowired

private TestDao testDao;

@RequestMapping("/")

public void index(){

for(ApplicationContext ctx : ApplicationContextHolder.list){

System.out.format("[%s] testController hashCode : %d\n", ctx.getId(), ctx.getBean("testController").hashCode());

System.out.format("[%s] testDao hashCode : %d\n", ctx.getId(), ctx.getBean("testDao").hashCode());

System.out.format("[%s] testJdbcTemplate hashCode : %d\n", ctx.getId(), testDao.jdbcTemplate.hashCode());

}

}

}

이제 url을 호출하여 테스트 해보자. 테스트 내용은 기존에 ApplicationContext에서 가져온 빈과 DispatcherServlet에서 가져온 빈의 hashCode를 비교해 인스턴스가 같은지 다른지 확인하였다.

url 호출 결과는 아래와 같다.


[org.springframework.web.context.WebApplicationContext:] testController hashCode : 631271355

[org.springframework.web.context.WebApplicationContext:] testDao hashCode : 1666005388

[org.springframework.web.context.WebApplicationContext:] testJdbcTemplate hashCode : 1060031077

[org.springframework.web.context.WebApplicationContext:/dispatcherServlet] testController hashCode : 319851274

[org.springframework.web.context.WebApplicationContext:/dispatcherServlet] testDao hashCode : 896263012

[org.springframework.web.context.WebApplicationContext:/dispatcherServlet] testJdbcTemplate hashCode : 1060031077

결과를 보면 testJdbcTemplate만 hashCode가 같고, testController와 testDao는 다른 것을 확인 할 수 있다.

결론 : ApplicationContext와 DispatcherServlet에서 component-scan을 이용해서 등록된 빈들은 다른 인스턴스이다. 다만 ApplicationContext에만 등록된 빈의 경우 DispatcherServlet에서 검색해보고 없으면 부모인 ApplicationContext에서 가져온다.

ApplicationContext vs DispatcherServlet : 컨텍스트 간의 관계

웹쪽 자원을 관리하는 DispatcherServlet과 그 외의 자원을 관리하는 ApplicationContext는 어떤 관계가 있을까? 예제에서는 DispatcherServlet과 ApplicationContext의 정보를 가져오기 위해 ApplicationContextAware를 구현한 클래스를 양쪽에 등록하고, 두 컨텍스트의 정보를 출력해봤다.

ApplicationContextHolder.java


package com.tistory.jekalmin.common;


import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;


public class ApplicationContextHolder implements ApplicationContextAware{

@Override

public void setApplicationContext(ApplicationContext ctx)

throws BeansException {

System.out.println("================================================");

System.out.println("applicationContext : " + ctx.getId());

System.out.println("applicationContext hashCode : " + ctx.hashCode());

ApplicationContext parent = ctx.getParent();

if(parent != null){

System.out.println("parent : " + parent.getId());

System.out.println("parent hashCode : " + parent.hashCode());

}

System.out.println("================================================");

}


}



web.xml


<context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>

        classpath:spring/application-context.xml

        </param-value>

    </context-param>


    <listener>

        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

    

    <servlet>

        <servlet-name>dispatcherServlet</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>

            <param-name>contextConfigLocation</param-name>

            <param-value>classpath:spring/spring-servlet.xml</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

    </servlet>


결과는 다음과 같다.


================================================ applicationContext : org.springframework.web.context.WebApplicationContext: applicationContext hashCode : 966881334 ================================================ ================================================ applicationContext : org.springframework.web.context.WebApplicationContext:/dispatcherServlet applicationContext hashCode : 1233412676 parent : org.springframework.web.context.WebApplicationContext:/SpringTest parent hashCode : 966881334 ================================================


applicationContext 먼저 생성되고, dispatcherServlet은 applicationContext를 부모로 가지고 있는다.