context:component-scan 엘리먼트는 annotation-config도 등록해줌.


이클립스에서 F2를 이용해서 읽어봤습니다.

Scans the classpath for annotated components that will be auto-registered as
 Spring beans. By default, the Spring-provided @Component, @Repository,
 @Service, and @Controller stereotypes will be detected. Note: This tag implies the
 effects of the ‘annotation-config’ tag, activating @Required, @Autowired,
 @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and
 @PersistenceUnit annotations in the component classes, which is usually desired
 for autodetected components (without external configuration). Turn off the
 ‘annotation-config’ attribute to deactivate this default behavior, for example in order
 to use custom BeanPostProcessor definitions for handling those annotations. Note:
 You may use placeholders in package paths, but only resolved against system
 properties (analogous to resource paths). A component scan results in new bean
 definition being registered; Spring’s PropertyPlaceholderConfigurer will apply to
 those bean definitions just like to regular bean definitions, but it won’t apply to the
 component scan settings themselves.

Content Model : (include-filter*, exclude-filter*)

수확이 있었군, 스프링 공부할 때 살펴볼 클래스가 하나 등장했다. PropertyPlaceholderConfigurer
설정 파일 읽어서 BeanDefinition 객체로 만든다는것 까진 알고 있었는데, 어디서 누가 하는진 몰랐는데 저 녀석이 하고 있었나 봅니다. 캬오~ JavaDoc 귿이야..

BeanLifeCycle 인터페이스를 없애보자.

BeanLifeCycle에 등장하는 여러 인터페이스 들 중에서 BeanFactoryAware, ApplicationContextAware, MessageSoruceAware, ApplicationEventPublisherAware, ResourceLoaderAware 인터페이스를 사용하지 않고도 이들이 해주는 일과 똑같은 작업을 할 수 있습니다.

@Component
public class Bean {

    @Autowired
    ApplicationContext applicationContext;
   
    @Autowired
    MessageSource messageSource;
   
    @Autowired
    ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    ResourceLoader resourceLoader;
   
    @Autowired
    BeanFactory beanFactory;
   
}

이런식으로 @Autowired를 사용하시면 됩니다. 물론 저 중에서 BeanFactory를 뺀 나머지는 ApplicationContext를 사용할 때 이용할 수 있겠죠. 라이프사이클 중에 InitializingBean 인터페이스는 bean 엘리먼트의 init-method 또는 @PostConstruct를 사용하면 대체할 수 있습니다.

꼭 필요하진 않고, 있을 때만 주입하고 싶다면, @Autowired(required=false) 이렇게 설정하면 되겠죠.

다음은 위 코드의 테스트 코드입니다,.

@ContextConfiguration(locations=”springContext.xml”)
public class BeanTest extends AbstractJUnit4SpringContextTests{

    @Autowired
    Bean bean;
   
    @Test
    public void lifecycle() {
        assertNotNull(bean.beanFactory);
        assertNotNull(bean.applicationContext);
        assertNotNull(bean.messageSource);
        assertNotNull(bean.applicationEventPublisher);
        assertNotNull(bean.resourceLoader);
    }
   
}

테스트는 당연히 잘 돌아갑니다.

아차. 빈 설정파일은 딱 두 줄 한 줄 입니다.

    <context:component-scan base-package=”org.opensprout.sandbox.lifecycle” />
   
    <context:annotation-config />

Spring XML 설정 파일에서 import 동작 원리

잘못된 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” />

3.11.2. Using filters to customize scanning

bean으로 등록할 class를 스캔할 때 기본적인 방식(기본으로 사용하는 필터)은 다음과 같습니다.

base-package 이하의 package에 속해 있는 모든 클래스 중에서 @Component 또는 @Repository 애노테이션이 붙어 있으면 모두 bean으로 사용.

이 때 명시적으로 다른 필터를 사용하여 위의 기본 방식을 변경하거나 확장할 수 있습니다. 필터 사용은 매우 간단하게 <component-scan /> 태그의 하위 태그로 정의 할 수 있습니다.

필터 태그의 종류는 include-filter와 exclude-filter 두 가지 이며 각각 type과 expression을 가지고 있습니다. type은 필터의 타입을 나타내며 expression은 말그대로 표현식을 나타냅니다.

필터에서 사용할 수 있는 타입과 표현식은 다음과 같습니다.
사용자 삽입 이미지

<context:component-scan base-package=”whiteship” use-default-filters=”false”  />

이렇게 기본 필터의 사용을 fasle로 설정하면 @Component나 @Repository 애노테이션을 사용한 클래스 조차 bean으로 인식하지 않습니다.

    <context:component-scan base-package=”whiteship” use-default-filters=”false”>
        <context:include-filter type=”annotation” expression=”whiteship.Bean”/>
    </context:component-scan>

Bean이라는 annotation을 만들고, @Bean 애노테이션이 붙은 것을 bean으로 읽도록 정의해 두었습니다. @Bean 애노테이션 생성은 간단합니다.

public @interface Bean {

}

요렇게 만들어 두면 되죠. 그리고 이제 그냥 사용하면 됩니다.

@Bean
public class MovieFinder {

}

@Bean
public class SimpleMovieLister {

    @Autowired
    private MovieFinder movieFinder;
   ….
}

매우 간단하네요.

3.11. Classpath scanning for beans

3.10에서 살펴봤던 @Autowired 애노테이션을 사용할 때에도 기본 bean 설정은 XML을 통해서 했었습니다. 하지만 이번에는 암묵적으로 클래스패스에서 bean을 등록하는 방법과 필터와 매칭 되는 bean을 등록하는 방법을 살표보겠습니다.

3.11.1. @Component and @Repository

@Component 와 @Repository 애노테이션 사용해서 bean 등록하는 방법

http://whiteship.tistory.com/1042

3.11.2. Using filters to customize scanning

@Component 와 @Repository 애노테이션이 붙어 있으면 무조건 bean으로 등록이 되지만, filter를 사용하여 특정 클래스들을 bean으로 사용하는 것에서 제외할 수 있습니다.

3.11.3. Naming auto-detected beans

bean이름을 설정할 수 있습니다.

3.11.4. Providing a scope for auto-detected beans

bean의 scope을 지정해 줄 수 있습니다.