BeanFactoryPostProcessor 구현체를 하나 만들었습니다.
메소드 구현 내용은 이렇습니다.

@Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
   throws BeansException {
beanFactory.createBean(ActionDefinition.class);
}

ActionDefinition 클래스는 bean 설정파일에 정의되지 않은 임의의 클래스 입니다.
또한 컴포넌트 스캔 대상도 아닙니다. 단, @Autowired 는 사용하고 있습니다.

여기서 발생하는 문제는 @Autowired 가 작동되지 않는 것 입니다.
이유는 간단합니다.

bean 생성 시점이 BeanFactoryPostProcessor 이기 때문입니다.
BeanFactoryPostProcessor 가 수행되는 시점은 @Autowired 를 처리하기 위한 AutowiredAnnotationBeanPostProcessor 가 등록되기 전입니다.
Posted by 째코

댓글을 달아 주세요

  1. 기선 2008.11.02 22:29 신고  댓글주소  수정/삭제  댓글쓰기

    우와.. 그렇군요. 흠. 근데 어떤 이유로 인해 빈을 저런 방법으로 등록하는지 궁금합니다. 어떤 경우인거죠?

    • 째코 2008.11.03 00:15 신고  댓글주소  수정/삭제

      쿼리와 뷰만으로 CURD를 가능하게 하기 위한 무언가를 만들고 있습니다. 설정방식을 최소화 시킬 생각으로 자바클래스와 애노테이션을 이용하고 있는데 바로 이 설정이 되는 자바클래스를 스프링 초기화 타임에 리플렉션으로 애노테이션을 읽어서 내부적으로 사용될 설정객체를 생성하고 있습니다. ConfigurableListableBeanFactory 인터페이스에 아주 막강한 기능이 많더군요...

먼저 두개의 bean을 보겠습니다.

masterName 프로퍼티의 경우 아빠와 아들의 경우 설정이 중복 됩니다.
이때 parent속성으로 부모 bean을 지정하게 되면 설정 내용들을 상속 받게 됩니다.
(단, 동일한 설정에 대해서는 오버라이드 되지 않습니다.)

여기서 포인트가 있는데 자식 bean의 class가 명시되어 있다면 부모 bean의 설정 내용들만 상속 받게 됩니다.
이 말은 부모 bean이 다른 클래스여도 상관 없고 class가 지정안된 bean일지라도 상관 없다는 뜻입니다.
아래 예제가 바로 그런 경우 입니다.

master는 class가 지정되지 않았기 때문에 인스턴스화 될 수 없으므로 abstract="true"을 사용해 인스턴스 생성을 방지 하고 있습니다.
abstract가 설정된 bean은 스스로 인스턴스화 될 수 없고 오직 자식 bean에서만 사용가능하게 됩니다.

만약 자식 bean의 class가 명시되어 있지 않다면 부모 bean의 class를 상속 받게 됩니다.
따라서 부모 bean은 완전한 형태여야만 합니다.

Posted by 째코

댓글을 달아 주세요

없으면 에러 낼거야
이건 2.5이전부터 있던건데 그냥 한번 짚고 넘어 갑니다.
@Required 가 붙은 setter 메소드는 반드시 XML 설정에 의존성 삽입이 정의 되어 있어야 합니다.
(@Autowired도 안됩니다.)
XML 설정에 명시적인 의존성 삽입이 없다면 런타임 예외가 던져 집니다.
이 애노테이션을 처리하기 위한 후처리기는 다음과 같습니다.
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>


자동으로 묶어 줄게
@Autowried 는 타입에 일치하는 bean을 의존성 삽입 해줍니다.
필드에 사용 할 경우 setter가 존재하지 않아도 됩니다.
@Autowried
private Foo foo;

여러개의 인자를 가진 메소드에 사용 할 수 있습니다.
@Autowried
public void setUp(Foo foo, Bar bar) {
    this.foo = foo;
    this.bar = bar;
}

@Autowired의 속성인 required의 기본값이 true이기 때문에 발견되는 bean이 없다면 예외가 던져 집니다.
발견 되는 bean이 없더라도 예외가 던져지지 않기를 원하면 required 속성을 fasle로 설정하면 됩니다.
@Autowried(required=false)
private Foo foo;

일치하는 타입을 찾기 때문에 발견되는 bean이 여러개인 경우 예외가 던져 집니다.
이를 방지 하기 위해 <bean /> 요소의 추가된 속성인 primary를 설정 합니다.
발견되는 bean이 여러개 일지라도 primary가 설정된 bean이 참조 됩니다.
<bean id="foo" class="example.Foo" primary="true" />
<bean id="foo2" class="example.Foo"/>

이 애노테이션을 처리하기 위한 후처리기는 다음과 같습니다.
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>


정확한 이름을 알려줄게
@Autowired을 사용 할 때 타입으로 bean을 찾기 때문에 여러개의 bean이 발견되면 primary로 해결 했습니다.  하지만 @Qualifier를 이용해 찾으려는 bean의 id를 넘길 수 있습니다.
@Autowried
@Qualifier("foo")
private Foo foo;

필드에만 사용 가능 하기 때문에 메소드에 적용시 다음과 같이 설정 합니다.
@Autowried
public void setUp(@Qualifier("foo") Foo foo, @Qualifier("foo2") Foo foo2) {
    this.foo = foo;
    this.foo2 = foo2;
}

이 애노테이션을 처리하기 위한 후처리기는 @Autowried의 후처리기와 동일 합니다.


JSR-250 애노테이션 지원
-@Resource
-@PostConstruct
-@PreDestroy
위 세가지 애노테이션을 사용하기 위해서는 후처리기를 등록 해야 합니다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>

@Resource
JNDI의 리소스 및 Spring 컨테이너의 bean을 찾아 autowring 합니다.
name 속성을 생략 할 경우 해당 필드명으로 찾고 해당 bean이 발견되지 않는다면 타입으로 찾습니다.

필드에 사용 할 경우 setter가 존재하지 않아도 됩니다.
@Resource(name="dataSource")
private DataSource dataSource;

한개의 인자를 가진 setter 메소드만 적용 할 수 있습니다.
@Resource(name="dataSource")
public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

타입으로 찾지 않길 원한다면 다음과 같은 설정을 합니다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
    <property name="fallbackToDefaultTypeMatch" value="false"></property>
</bean>

JNDI 리소스만 참조 하려면 다음과 같은 설정을 합니다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
    <property name="alwaysUseJndiLookup" value="true"/>
</bean>

@PostConstruct 와 @PreDestroy
기존에는 생명 주기를 다루는 방법이 2가지가 있었습니다.
1.InitializingBean, DisposableBean을 구현하는 방법
2.XML 설정파일에서 <bean /> 요소의 init-method, destroy-method 속성 설정

2.5에서 추가된 두 애노테이션은 기존의 방식과 동일한 결과를 가져 옵니다.
@PostConstruct
public void init() {
    ...
}
   
@PreDestroy
public void preDestroy() {
    ...
}

만약 3가지 방식 모두 사용할 경우
우선순위는 애노테이션->XML설정->인터페이스 구현 입니다.


이거 하나면 OK
위에서 나온 애노테이션을 사용하기 위해서는 3가지의 BeanPostProcessor를 등록해야 했습니다.
하지만 이거  하나면 3개가 모두 등록 됩니다.
<context:annotation-config />


알아서 찾아줄게
위에서 나온 애노테이션으로 XML설정을 줄이더라도 bean 선언은 반드시 해야 했습니다.
하지만 component-scan 을 이용하면 bean선언 조차 생략 할 수 있습니다.
component-scan의 대상이 되기 위해서는 스테레오타입 애노테이션을 사용해야 합니다.

4개의 스테레오타입 애노테이션
@Component
- 스테레오타입 애노테이션의 조상 입니다.
@Controller
-Spring MVC에서 컨트롤러로 인식 합니다.
@Service
-역할부여 없이 스캔 대상이 되는데 비즈니스 클래스에 사용하면 될 것 같습니다.
@Repository
-DAO에 사용되며 DB Exception을 DataAccessException으로 변환해 줍니다.

간단한 예제 입니다.
테스트에 사용될 2개의 클래스
@Component("bar")
public class Bar {
}

@Component("foo")
public class Foo  {
    @Autowired
    private Bar bar;
   
    public Bar getBar() {
        return bar;
    }
}

지정된 패키지의 하위패키지까지 재귀적으로 스테레오타입이 있는 클래스를 찾습니다.
필터를 이용해 제외 검색 대상에서 제외 시킬 수 있으며
<context:annotation-config /> 까지 자동으로 등록됩니다.
<context:component-scan base-package="example" />

테스트는 통과 합니다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"Beans.xml"})
public class ScanTest {
    @Autowired
    private Foo foo;
   
    @Test
    public void fooTest() {
        assertNotNull(foo.getBar());
    }
}

@Scope를 함께 사용 하여 Scope를 설정 할 수 있습니다.
@Scope("request")
@Component("bar")
public class Bar {
}
singleton인 foo에서 request scope인 bar를 사용 하면 어떻게 될 까요?
역시 scope문제가 발생 합니다.

이 문제를 해결하기 위해 <context:component-scan /> 요소의 scoped-proxy 속성이 존재 합니다.
scoped-proxy 속성은 <aop:scoped-poxy/> 요소처럼 WebApplicationContext 에서만 유효하며
"session", "globalSession", "request" 이외의 scope는 무시 됩니다.

아래 3가지 값을 지정 할 수 있습니다.
no - 디폴트값, proxy를 생성하지 않습니다.
interfaces - JDK Dynamic Proxy를 이용한 Proxy 생성
targetClass - CGLIB를 이용한 Proxy 생성

Bar는 인터페이스 기반이 아니기 때문에 CGLIB를 이용 했습니다.
<context:component-scan base-package="example" scoped-proxy="targetClass"/>

만약 스캔된 bean들 중 @Scope의 값이 "session", "request" 일 때
scoped-proxy 속성 값이 "targetClass", "interfaces" 가 아닐경우 예외가 던져지고 초기화에 실패 합니다.

component-scan을 이용하면 method-lookup도 불가능 하므로 참조되는 bean이 web-scope가 아니고
prototype일 경우의 문제는 해결 할수 없을것 같습니다.
Posted by 째코

댓글을 달아 주세요

  1. kekedie 2008.01.08 11:31 신고  댓글주소  수정/삭제  댓글쓰기

    테스트에서 NullPointerException이 나는데요..
    테스트 하신거 익스포트해서 올려주시면 안되나요?^^; 바쁘실텐데 죄송합니다.

  2. 째코 2008.01.08 12:10 신고  댓글주소  수정/삭제  댓글쓰기

    StackTrace 를 올려주시면 원인이 뭔지 금방 파악 될 것 같습니다.
    포스팅중에 라이브코딩한거라 소스코드는 따로 없습니다.

  3. kekedie 2008.01.08 13:05 신고  댓글주소  수정/삭제  댓글쓰기

    아..우선 @RunWith(SpringJUnit4ClassRunner.class) 이 부분에서 컴파일 에러나는데요.
    Type mismatch: cannot convert from Class<SpringJUnit4ClassRunner> to Class<? extends Runner>

    메세지는 이렇구요. 윗부분 주석 처리하고 테스트 돌리면 assertNotNull(foo.getBar()); 부분에서 NullPointerException납니다.

    @RunWith 저 부분 에러만 잡으면 될 것 같은데..잘 안되네용...;;

  4. kekedie 2008.01.08 14:37 신고  댓글주소  수정/삭제  댓글쓰기

    해결했습니다. junit버전 문제더군요..;;;; 4.1이였는데 4.4로 올리니까 돌아가네요.;;;;;
    감사합니다!!

  5. 째코 2008.01.14 09:40 신고  댓글주소  수정/삭제  댓글쓰기

    이런... 덧글 이제 봤네요 ㅋㅋ


지금 까지 Spring의 기본에 대해서 글을 썻습니다.
아마도 이 글은 지금까지의 글에 대한 증거(?)가 될 수도 있겠네요
ApplicationContext의 초기화 과정을 보면 이전 글들에서 다뤘던
LifeCycle, MessageSource, BeanPostProcess에 대한 증거들을 보실 수 있습니다.
워낙 잘 짜여진 소스라 그리 어렵진 않습니다.

ApplicationContext의 구현체중 하나인 ClassPathXmlApplicationContext을 생성 하면 아래의 마지막으로
실행되는 생성자는 다음과 같습니다.

refresh 인자가 false라면 초기화 하지 않을 것이고 초기화 되지 않은 상태에서 어떠한 요청을 하게 되면
IllegalStateException 가 던져 집니다. refresh 인자가 true일 경우 refresh() 메소드가 호출 됩니다.
refresh() 메소드에서 하는 일이 초기화의 전부 입니다.

다음은 refresh() 메소드의 구현내용 입니다.

순서 대로 알아 보도록 하겠습니다.
prepareRefresh()
-멤버 필드인 active를 true로 설정 합니다. 활성화 됐다는 뜻이죠

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
-내부에서 쓰이는 BeanFactory를 refresh 합니다.

prepareBeanFactory(beanFactory)
-beanFactory를 사용 가능한 상태로 준비 합니다. 이 메소드에서는 beanFactory에 PropertyEditor와
ClassLoader, BeanPostProcessor 들이 등록 되어 집니다.

postProcessBeanFactory(beanFactory)
-템플릿 메소드입니다. 자식 클래스의 재정의에 따라 행동이 변합니다.
ApplicationContext에서는 아무 작업을 하지 않습니다만 WebApplicationContext 에서는
Web Scope가 등록 되어 집니다.

invokeBeanFactoryPostProcessors(beanFactory)
-등록된 bean들 중에 BeanFactoryPostProcessor인 것을 찾아 생성하고 실행 합니다. 생성 순서는 XML에 정의된 순서 겠지만 메소드 호출은 PriorityOrdered, Ordered, Non-Ordered 순으로 수행 됩니다.

registerBeanPostProcessors(beanFactory)
-먼저 BeanPostProcessorChecker를 생성하여 beanFactory에 등록 합니다.
(BeanPostProcessorChecker가 하는 역할은 bean 생성시 로그 메세지를 출력하는 것입니다.)
이후에 등록된 bean들 중에 BeanPostProcessor인 것을 찾아 생성한 후 beanFactory에 등록 합니다.
등록 순서는 PriorityOrdered, Ordered, Non-Ordered 입니다.

initMessageSource()
-등록된 bean들 중에 id가 messageSource인 것을 찾아 생성한 후 MessageSource로 캐스팅 하여 내부 프로퍼티인 messageSource에 할당 합니다. 타입체크 없이 무조건 캐스팅 하므로 주의가 필요한 부분입니다.
만약 발견되지 않는다면 DelegatingMessageSource를 생성하여 할당 합니다.
(DelegatingMessageSource는 추측인데 단지 Message가 없는 위임용 객체 같습니다.)

initApplicationEventMulticaster()
-등록된 bean들 중에 id가 applicationEventMulticaster인 것을 찾아 생성한 후 ApplicationEventMulticaster로 캐스팅 하여 내부 프로퍼티인 applicationEventMulticaster에 할당 합니다.
이것 역시 타입체크없이 바로 캐스팅 하므로 주의가 필요 합니다. 만약 발견되지 않는다면
SimpleApplicationEventMulticaster를 생성하여 할당 합니다.

initLifecycleDependentBeans()
-등록된 bean들 중에 타입이 LifeCycle인 것을 찾아 해당 bean이 의존하는 다른 bean이 존재한다면
해당 bean과 의존하는 bean의 id를 묶은 의존성 정보를 등록 시킵니다. 이 부분은 잘 모르겠습니다.

onRefresh()
-템플릿 메소드입니다. 자식 클래스의 재정의에 따라 행동이 변합니다.
ApplicationContext에서는 아무 작업을 하지 않습니다만 WebApplicationContext에서는 themeSource를
설정 합니다.

registerListeners()
-등록된 bean들 중에 타입이 ApplicationListener인 것을 찾아 생성한 후 리스너로 등록 시킵니다.

finishBeanFactoryInitialization(beanFactory)
-지금 까지의 과정에서 포함되지 않은 일반적인 bean(Singleton, non-lazy-init)을 생성되고 의존성이 삽입 됩니다. 생성 과정에서 LifeCycle 인터페이스들을 구현 했다면 해당 메소드가 호출되고 등록된 BeanPostProcessor를 거치게 됩니다. Spring에는 기본적으로 ApplicationContextAwareProcessor가 등록 되어 있으므로 이곳도 거치게 됩니다.

finishRefresh()
-모든 과정이 끝났습니다. 마지막으로 위 과정에서 등록된 ApplicationListener들에게 초기화가 끝났다는
신호인 ContextRefreshedEvent를 전파 합니다.


'Spring > Core' 카테고리의 다른 글

abstract, parent 속성  (0) 2008.01.07
Spring2.5 애노테이션 기반 설정  (5) 2007.12.25
ApplicationContext의 초기화 과정  (2) 2007.12.20
MessageSource 사용하기(다국어 지원)  (0) 2007.12.19
불가능은 없다. FactoryBean  (0) 2007.12.19
LifeCycle 확장  (0) 2007.12.17
Posted by 째코
TAG 초기화

댓글을 달아 주세요

  1. 헤르메스의날개 2012.05.22 10:37 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사합니다. 담아가요~~

  2. 나모찾기 2015.02.02 20:31  댓글주소  수정/삭제  댓글쓰기

    관리자의 승인을 기다리고 있는 댓글입니다

ApplicationContext 인터페이스는 MessageSource 인터페이스를 상속하여 메세징 기능을 제공 합니다.
MessageSource에 정의된 메소드 입니다

String getMessage(String code, Object[] args, String default, Locale loc)
-가장 기본적인 메소드 입니다. 특정 로케일내에서 발견되는 메세지가 없다면
디폴트 메세지가 반환 됩니다.

String getMessage(String code, Object[] args, Locale loc)
-위 메소드와 동일 합니다만 디폴트 메세지가 없기 때문에 메세지가 발견 되지 않는다면 NoSuchMessageException 던져 집니다.

String getMessage(MessageSourceResolvable resolvable, Locale locale)
-위 메소드의 모든 프로퍼티를
MessageSourceResolvable에 설정해 사용 합니다.

ApplicationContext 컨테이너가 초기화 될 때 MessageSource를 구현한 bean을 찾아서 내부 프로퍼티 인
MessageSource messageSource에 등록 합니다.

이 과정에서 두가지 문제점이 있습니다.
첫번째로 MessageSource 타입의 bean을 찾는것이 아니라 bean의 id가 "messageSource" 인 것을
찾습니다. 설령 MessageSource의 구현체를 bean으로 등록했다 하더라도 id가 "messageSource" 가 아니라면 MessageSource가 없다고 판단 합니다.

두번째로 bean의 id가 "meesageSource"일 경우 즉시 MessageSource로 캐스팅을 시도 하기 때문에 MessageSource의 구현체가 아니라면 BeanNotOfRequiredTypeException이 던져지고 컨테이너 초기화에 실패 하게 됩니다.

위의 두가지 문제가 없이 발견 된다면 내부 프로퍼티인 messageSource 에게 모든 메세징 작업이 위임 될 것입니다. 만약 MessageSource bean이 발견되지 않는다면 어떻게 될까요?
ApplicationContext의 메세징 작업들은 내부 프로퍼티인 messageSource 위임 되어 있는
상태이기 때문에 반드시 어떠한 구현체가 하나라도 등록이 되어야 합니다.
(null인 상태라면 IllegalStateException이 납니다.)
그러한 이유로 메세지가 없이 비어 있는 DelegatingMessageSource 가 생성 되어 등록 됩니다.
(DelegatingMessageSource는 컴포지션으로 내부에 MessageSource를 가지고 위임하고 있는데 저걸 어떻게 이용 할 수 있는지는 잘 모르겠습니다. 참고로 여러가지 상황으로 테스트 해봤는데 내부의 MessageSource는 항상 null 입니다. 지극히 제 생각인데 컨테이너 초기화 로직에 의한 MessageSource할당이 불가능 하므로 별도의 코드로 사용자가 MessageSource를 주입해줘야 될 것 같습니다.)

Spring에는 두개의 MessageSource구현체가 존재 합니다.ResourceBundleMessageSource와 StaticMessageSource인데 래퍼런스에 따르면 후자는 거의 사용되지 않는 다는군요.

다국어를 지원하는 간단한 예제를 보겠습니다.
한국과 미국의 간단한 message 파일 입니다.
파일명에 추가로 _ko_KR 과 _en_US는 다국어를 지원하기 위해 필요한 네이밍 규칙 입니다.
message_ko_KR.properties
message = \uc548\ub155 \ub0b4 \uc774\ub984\uc740 {0} \ub77c\uace0 \ud574
message_en_US.properties
message = hi my name is {0}

XML설정 파일 입니다.

테스트 파일 입니다.

실행결과.
안녕 내 이름은 째코 라고 해
hi my name is jjaeko

일반적인 bean에서 MessaageSourceAware 인터페이스를 구현 하면 bean 생성 시점에서 MessageSource가 주입 될 것입니다.

'Spring > Core' 카테고리의 다른 글

Spring2.5 애노테이션 기반 설정  (5) 2007.12.25
ApplicationContext의 초기화 과정  (2) 2007.12.20
MessageSource 사용하기(다국어 지원)  (0) 2007.12.19
불가능은 없다. FactoryBean  (0) 2007.12.19
LifeCycle 확장  (0) 2007.12.17
LifeCycle  (0) 2007.12.17
Posted by 째코

댓글을 달아 주세요