잘못된 spring configuration문제로 StackOverflowException 황당한 예외 이 글을 읽다가 궁금해서 테스트를 해봤습니다.

package contextImport;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextTest {

    @Test
    public void testname() throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("contextImport/applicationContext.xml");
        assertNotNull(applicationContext);
       
        A a = (A) applicationContext.getBean("bean1");
        assertEquals("whiteship2000", a.getName());
    }
}

간단합니다. A라는 객체의 속성 값을 보고 어느 파일에서 세팅된 녀석을 가져온 건지 확인하기 위한 코드 입니다.

Application Context 설정 파일은 세 개입니다.

applicationContext.xml : bean1(A.class, name=keesun), bean2(B.class)

    <import resource="applicationContext2.xml" />
    <import resource="applicationContext3.xml" />
    <bean id="bean1" class="contextImport.A">
        <property name="name" value="keesun" />
    </bean>
    <bean id="bean2" class="contextImport.B" />

applicationContext2.xml : bean1(B.class)

    <bean id="bean1" class="contextImport.B"/>

applicationContext3.xml : bean1(A.class, name=whiteship2000)

    <bean id="bean1" class="contextImport.A">
        <property name="name" value="whiteship2000" />
    </bean>

자.. 지금 bean1 이라는 id로 등록되어 있는 빈이 전부 세개 이고 그 중에서 두 개는 A 클래스 하나는 B 클래스 입니다.

결론부터 말씀드리자면, 저는 StackOverFlow를 목격하지 못했습니다. Casting Exception은 목격할 수 있지만 운좋게 피해 갈 수도 있습니다.

1. 위와 같이 설정 해 놓고 getBean("bean1")을 호출하면 applicationContext.xml에 정의되어 있는 bean1을 돌려줍니다.

놀랐습니다. 에러가 날 줄 알았는데, 에러가 안나서.. 그런데 생각해보니 참 잘 만들었다는 생각이 들었습니다.  오오~ import 한 건 무시하고 오버라이딩 했나봐~~ 근데 이건 착각이었습니다.

오버라이딩이라고 하기도 뭐한것이... import는 엄연히 자기가 가지고 있는 빈들과 동급으로 처리하는 거지 상속 구조 처럼 상위 하위와 같은 게층 구조가 아니기 때문입니다.

사실은 오버라이딩 보다 더 단순한 규칙이 적용됩니다. 이 글을 끝까지 보시면 알 수 있습니다.

2. applicationContext.xml에 있는 bean1 설정을 주석처리하거나 없애버리고 getBean("bean1")을 호출하면, applicationContext3.xml에 정의 되어 있는 bean2를 돌려줍니다.

오호.. 이거 뭐야!! 완전 똑똑하자나!! 어떻게 알았어!! 내가 bean1을 A로 캐스팅 할 줄 알고 applicationContext3.xml 에 있는 bean1을 준거야?? 그런거야?? 너 정말 그렇게 천재인거야???

아니죠. 그렇게 똑똑 할 수는... 없습니다. 불가능하죠. 빈을 만드는 시점(ApplicationContext 인데다가 싱글톤이니까 초기에 생성하겠죠)에서는 밖에서 누굴 부를지 알 수가 없습니다. 글쵸?

암튼, 그럼 어떻게 된 일이냐...

3. 2번 상황에서 import문의 위치를 바꿔서 applicationContext3.xml이 위로 가고 applicationContext2.xml이 아래로 오게 해놓고 테스트를 했습니다.

java.lang.ClassCastException: contextImport.B
    at contextImport.ApplicationContextTest.testname(ApplicationContextTest.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)
    at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:98)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
    at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:88)
    at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
    at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
    at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

이야!!! 역시.. 뭔가 수상하다 했어!!
 

import는 import를 한 쪽에서 다른쪽에 정의 되어 있는 모든 bean 설정을 그대로 딱 import문을 사용한 고 위치에 XML 설정을 삽입하는 겁니다.

같은 이름의 bean이 있을 때 동작하는 방식도 무지 단순합니다. 같은 타입이건 아니건 간에 무조건 맨 아래 쪽에 정의 되어 있는 bean이 짱입니다.

스택오버플로우가 어떻게 발생한건지 더 궁금해져 갑니다 ㅠ.ㅠ
Ologist님 알려주세요~ 어떻게 된거지요???

퀴즈. 그렇다면, 다른 건 동일하고 applicationContext.xml만 다음과 같이 정의하고 위의 코드를 돌리면 어떤 결과가 발생할까요?

    <bean id="bean1" class="contextImport.A">
        <property name="name" value="keesun" />
    </bean>
      
    <bean id="bean2" class="contextImport.B" />

    <import resource="applicationContext3.xml" />
    <import resource="applicationContext2.xml" />