Spring XML 설정 파일에서 import 동작 원리
잘못된 spring configuration문제로 StackOverflowException 황당한 예외 이 글을 읽다가 궁금해서 테스트를 해봤습니다.
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="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)
applicationContext3.xml : bean1(A.class, name=whiteship2000)
<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)
이야!!! 역시.. 뭔가 수상하다 했어!!
같은 이름의 bean이 있을 때 동작하는 방식도 무지 단순합니다. 같은 타입이건 아니건 간에 무조건 맨 아래 쪽에 정의 되어 있는 bean이 짱입니다.
스택오버플로우가 어떻게 발생한건지 더 궁금해져 갑니다 ㅠ.ㅠ
Ologist님 알려주세요~ 어떻게 된거지요???
퀴즈. 그렇다면, 다른 건 동일하고 applicationContext.xml만 다음과 같이 정의하고 위의 코드를 돌리면 어떤 결과가 발생할까요?
<property name="name" value="keesun" />
</bean>
<bean id="bean2" class="contextImport.B" />
<import resource="applicationContext3.xml" />
<import resource="applicationContext2.xml" />