어제 글에 이어서.

KSUG 메일링리스트에 나머지 두가지 방법을 한번 찾아보라고 글을 올렸는데, 스프링 고수 두 분이 답을 주셨다. 대단한 분들이다.

 

@Configuration을 적용하는 방법을 여러가지로 시도해보려고 한 이유는 두가지인데, 하나는 스프링 책에 들어갈 예제에 써먹기 위해서이고(XML 없이 자바코드로만 스프링을 사용하는 예제가 필요했다), 다른 하나는 이전 글에서 이야기 한 것처럼 XML에 annotation-config 설정만으로 이 기능이 적용되는지가 궁금했기 때문이다.

 

레퍼런스에 나온 방법은 XML에 annotation-config 태그를 다는 것이고, @Configuration에 대한 스프링 내부의 테스트 코드에는 두번째로 이야기한 DefaultListableBeanFactory와 ConfigurationClassPostProcessor를 이용하고 있다.

 

ApplicationContext

세번째로 해볼 것은 이 BeanFactory를 이용한 방법을 ApplicationContext를 이용하는 것으로 바꾸는 것이다. BeanFactory는 스프링의 핵심이긴 하지만, 실전에서 직접 사용되는 경우가 거의 없다. 왜냐하면 이를 확장한 ApplicationContext를 쓰면 되기 때문이다.

두가지를 비교해보면 ApplicationContext는 인터페이스이고, 이는 BeanFactory인터페이스를 상속한 것이다. 즉 ApplicationContext는 BeanFactory이다. List가 Collection인것과 마찬가지이다.

그래서 쉽게 생각하기를 ApplicationContext를 구현한 클래스는 마찬가지로 BeanFactory를 구현한 클래스를 상속해서 기능을 확장해서 쓸 것이라고 생각하기 쉽다.

스프링의 IoC기능을 담당하는 BeanFactory를 가장 완벽하게 구현한 클래스는 DefaultListableBeanFactory이다. BeanFactory가 독자적으로 XML파일을 읽고 초기화 될 수있도록 이를 한번 더 확장한 XmlListableBeanFactory가 있기는 하지만, 이건 거의 테스트용도 외에는 사용되지 않는다.

그럼 상식적으로 생각했을 때 BeanFactory의 기능을 모두 포함하며 기능을 더 확장한 ApplicationContext의 구현 클래스들은 바로 이 DefaultListableBeanFactory를 상속해서 만들 것이라고 생각할 수 있다. 실제로 ApplicationContext는 BeanFactory인터페이스를 바로 상속하는 것이 아니고, 그 확장 판인 ListableBeanFactory인터페이스를 확장한다. 그 말은 ListableBeanFactory인터페이스 구현의 결정판인 ListableBeanFactory의 기능이 ApplicationContext에서 모두 요구된다는 것이다.

재밌는 것은 이 ApplicationContext를 바로 구현한 클래스는 없다는 점이다. 그것을 다시 확장한 ConfigurableApplicationContext 인터페이가 있고 이 것을 구현한 AbstractApplicationContext가 있을 뿐이다.

 

이를 BeanFactory쪽과 비교해서 보면 이해하기 쉬운데,

ApplicationContext 는 ListableBeanFactory를 상속했다.

ApplicationContext의 서브 인터페이스인 ConfigurableApplicationContext 는 ListableBeanFactory의 서브 인터페이스인 ConfigurableApplicationContext를 상속했다. 이 ConfigurableApplicationContext를 구현하지 않는 ApplicationContext는 존재하지 않는다. 따라서 실제로 ApplicationContext라고 불리는 모든 것들은 사실 ConfigurableApplicationContext라고 불러도 그만이다.

ConfigurableApplicationContext를 BeanFactory계층과 연결해보자. ApplicationContext는 최소한 ListableBeanFactory를 구현해야 한다. ConfigurableApplicationContext도 인터페이스의 상속구조만 보자면 ListableBeanFactory의 하위 인터페이스인 ConfigurableListableBeanFactory을 구현하도록 되어있어야 하는데 실제 정의를 보면 그렇지 않다. 이름은 Configurable~인데 Configurable한 BeanFactory 인터페이스를 상속하지 않는다? 뭔가 이상해보인다.

 

하지만 여기서 매우 중요한 설계적인 결정사항을 발견할 수 있다. 그것은 인터페이스의 상속구조가 아니라 메소드의 정의에서 ConfigurableListableBeanFactory이 등장한다는 점이다.

ConfigurableApplicationContext에는 다음과 같이 정의된 메소가 보인다.

ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

결국 무슨 얘기냐 하면 ApplicationContext가 BeanFactory(정확히는 ListableBeanFactory)인터페이스의 계층구조와 병렬적으로 상속구조를 가지고 발전하는게 아니라, 사실은 위임구조로 변신한다는 것이다. ConfigurableListableBeanFactory, 즉 사실상 스프링의 BeanFactory가 실질적으로 구현해야 하는 핵심인터페이스의 구현 부분은 ApplicationContext 인터페이스의 계층구조에서 나타나지 않고, 이를 내부에 참조할 수 있는 구조로 바꾸었다는 것이다. 바로 저 메소드가 그 증거이다. ConfigurableApplicationContext는 이제 직접 ConfigurableListableBeanFactory을 구현하지 않고, 내부에 ConfigurableListableBeanFactory을 구현한 객체를 프로퍼티로 가지고 있다가 이를 반환할 수 있기만 하면 되도록 했기 때문이다. 그 말은 또 이미 상속구조를 가지는 ListableBeanFactory의 인터페이스의 메소드들은 모두 내부의 ConfigurableListableBeanFactory 구현 객체에 위임(delegate)하는 구조로 되어있어야 한다는 것이다.

 

결론적으로 ApplicationContext와 BeanFactory의 관계는 상속(is-a) 관계라기보다는 위임(delegate) 관계이다. 모든 ApplicationContext는 BeanFactory 정확히는 ConfigurableListableBeanFactory를 구현한 객체를 내부에 가지고 있다는 것을 기억하자. 싫으면 말고.

모든 ApplicationContext구현 클래스의 수퍼클래스는 AbstractApplicationContext이고 추상클래스이므로 실전에서 쓰일 수 있도록 이를 상속한 클래스는 AbstractRefreshableApplicationContext(이것도 추상이네)와 GenericApplicationContext뿐이다. 둘다 내부에 BeanFactory의 대표주자인 DefaultListableBeanFactory(ConfigurableListableBeanFactory을 구현한)을 가지고 있는데, 차이는 전자는 refresh(!)가 가능하고, 후자는 final로 고정이라는 것이다. 그런데 스프링의 모든 Generic~이 붙는 클래스는 사실상 실전용이 아니다. 대부분 테스트나 특별한 경우에 내부적으로 사용하도록 만든 별볼일 없는 것들이다. 대체로 무시해도 좋다. 결국 남은 것은 AbstractRefreshableApplicationContext이고 여기서 쭉 이어져 내려가서 ClasspathXmlApplicationContext도 나오고 XmlWebApplicationContext도 나오고 하는 것이다.

 

아무튼 이 스프링의 핵심인 BeanFactory-ApplicationContext의 구조는 등장하는 인터페이스와 클래스만 한 100개는 되는 것 같은데, 들여다보면 참 재밌다. 그림을 하나 그려보고 싶긴한데, 귀찮아서.

 

서론이 길었는데, 그래서 앞에서 DefalutListableBeanFactory로 했던 @Configuration적용을 ApplicationContext를 전면에 등장하는 것으로 바꾸어보자.

먼저 GenericApplicationContext의 오브젝트를 만든다. Generic~이 붙은 건 바로 이럴 때 써먹는 것이다. ClasspathXml~ 이런걸 쓰려면 XML을 뭔가 만들어야 하니까. 실전에서는 당연히 그렇겠지만, 이렇게 간단하게 테스트 해볼 때는 Generic~, Static~이런 것들을 쓰면 된다.

GenericApplicationContext ctx = new GenericApplicationContext();

그리고 @Configuration이 붙은 설정용 팩토리 클래스를 등록해주는데, 이것은 원래 DefaultListableBeanFactory가 구현한 BeanDefinitionRegistry의 메소드이다. 따라서 ApplicationContext에서 저 빈 팩토리를 꺼내서 사용해야 하는데, 문제는 내부적으로는 DefaultListableBeanFactory클래스 타입으로 정의되어있지만 AC의 getBeanFactory에 돌려주는 타입은 그냥 ConfigurableListableBeanFactory이다. 그럼 이 오브젝트의 정체를 아니까 BeanDefinitionRegistry인터페이스로 캐스팅을 하든, DefaultListableBeanFactory 클래스 타입으로 캐스팅을 해야 두번째 방법에서 썼던 것처럼 registerBeanDefinition을 쓸 수 있다. 지저분해지는 느낌.

그러나 친절한 스프링 개발자들은 GenericApplicationContext가 BeanDefinitionRegistry를 구현하도록 하고, 내부의 DefaultListableBeanFactory에 위임하도록 만들어 놨다. 따라서 설정 클래스 등록을 바로 할 수 있다.

따라서 DefaultListableBeanFactory를 썼을 때와 마찬가지로

ctx.registerBeanDefinition("config", new RootBeanDefinition(Config.class));

하면 된다. 그럼 내부의 DefaultListableBeanFactory에 빈 설정용 클래스 빈 등록이 완료 된다.

 

다음은 ConfigurationClassPostProcessor을 적용할 차례이다. 사실 여기서 두가지 방법으로 나뉠 수가 있는데, 그렇게 분리하다보면 4가지가 아니라 한 10가지쯤 될 것 같아서 그냥 한가지 방법으로 묶고 두가지 세부 방법으로 나누도록 한다.

첫번째 방법은 DefaultListableBeanFactory를 꺼내올 수 있으니, BeanFactory에서 수동으로 하듯이 팩토리 후처리기(BeanFactoryPostProcessor)를 만들고 여기의 후처리 작업에 빈 팩토리를 넣어서 돌려주는 것이다. ApplicationContext로부터 시작했지만 내부의 BeanFactory를 노출시키는 방법이다.

ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(ctx.getBeanFactory());

AC에서 빈팩토리를 꺼내는 것만 다르고 이전 방법이랑 같다.

 

두번째 방법은 ApplicationContext의 자동화기능을 이용하는 것이다. AC는 BF에 비해서 자동으로 해주는 것이 많다. 싱글톤의 인스턴스 생성도 자동으로 해주고, 각종 포스트프로세서의 처리도 자동으로 해준다. AC에 필요한 것을 등록해주기만 하면 된다.

그래서 이번에는 ConfigurationClassPostProcessor을 만들어서 AC에 등록해주는 방법을 쓰도록 한다.

ctx.addBeanFactoryPostProcessor(new ConfigurationClassPostProcessor());

이렇게 등록한 PP는 refresh를 해주는 순간 모두 적용된다. refresh는 대략 13가지 종류의 초기화 작업을 해주는데 자세한 것은 나중에.. 그중에 하나가 바로 invokeBeanFactoryPostProcessors 작업이다. BFPP적용.

그래서 등록 후에 한번

ctx.refresh();

해주면 된다.

그러면 모든 작업 끝.

 

테스트 코드를 좀 모아보면, 첫번째 방법은

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition("config", new RootBeanDefinition(Config.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(ctx.getBeanFactory());
String hello = ctx.getBean("hello", String.class);
assertThat(hello, is("Hello"));

두번째 방법은

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition("config", new RootBeanDefinition(Config.class));
ctx.addBeanFactoryPostProcessor(new ConfigurationClassPostProcessor());
ctx.refresh();
String hello = ctx.getBean("hello", String.class);
assertThat(hello, is("Hello"));

이다.

 

참, 정상혁님이 제안해주신 한가지 방법이 더 있는데 그것은 GenericApplicationContext의 하위 클래스인 StaticApplicationContext를 사용하고 registerBeanDefinition 대신에 registerSingleton을 이용하는 것이다. 내부적으로 registerSingleton은 GenericBeanDefinition을 만들어서 registerBeanDefinition을 호출한다.

ctx.registerBeanDefinition("config", new RootBeanDefinition(Config.class)); 

이걸 다음과 같이 바꿀 수 있다는 것이다.

ctx.registerSingleton(“config”, Config.class);

결국 그거나 그거나. 용도로 보자면 GenericApplicationContext는 실전에서는 별로 쓸 일 없는 그냥 일반적인 AC기능을 최소한 모아놓은 것이고, StaticApplicationContext는 거기다가 BeanDefinition을 읽어오는 방식말고 코드에서 직접 빈을 등록하는 방법을 추가해준 것이다. 둘다 이런 학습적인 용도와 테스트에만 쓴다. 아직까지는 실전에서 쓰는 것을 본 일이 없다.

 

 

휴.. 내용이 길어졌으니 네번째 방법은 내일 다시.

Related posts:

  1. InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (3)
  2. InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (1)
  3. InsideSpring (3) 스프링 밖에서 WebApplicationContext에 접근하기
  4. 테스트 할 수 없는 것을 테스트 하기. Spring ROO와 static method mocking.
  5. InsideSpring (4) 빈 스캐너는 클래스를 로딩할까?
  6. InsideSpring (2) AutoProxyCreator는 Pointcut의 ClassFilter만 사용하지 않는다
  7. Spring 3.0 (54) 드디어 등장한 ConfigurationClassApplicationContext
  8. 스프링 빈의 이름은 한글로 지어도 된다
  9. Spring코드의 역사
  10. Spring 3.0 (33) JavaConfig의 통합과 변신. 메타-빈(meta-bean) 개념의 등장.
  11. Inside Spring (5) PropertyPlaceholderConfigurer를 @Bean으로 정의해서는 안되는 이유
  12. Inside Spring (6) 애노테이션 설정 지원 스프링 웹 테스트용 DispatcherServlet 만들기
  13. Spring 2.0의 XML확장기능 (1)

Facebook comments:

to “InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (2)”

  1. mzm4Jd qvjlhocxyftb, [url=http://wfefibdcwpex.com/]wfefibdcwpex[/url], [link=http://duqlacuvdzro.com/]duqlacuvdzro[/link], http://ahjtvjpjmbck.com/

  2. Hop Over To Here
    [url=http://www.tianhaiyi.net/data/testo.php?/authentic-hermes-handbagshermesdiscounts.html]authentic hermes handbags[/url]
    authentic hermes handbags

  3. thanks for share!

  4. Click To Find Out More
    [url=http://www.baolongfamen.com/member/editibaseinfow.php?/air-jordan-flight-club-release-dates-a-range-of-hues-styles-and-designs.html]air jordan flight club release dates-A range of hues, styles and designs[/url]
    air jordan flight club release dates-A range of hues, styles and designs

  5. Check These Guys Out
    [url=http://jiaxinwang.com/member/ccblo.php?/nike-air-max-90-ct-premium-le-not-only-beautiful-but-also-cozy-enough-2013.html]nike air max 90 ct premium le-Not only beautiful but also cozy enough 2013[/url]
    nike air max 90 ct premium le-Not only beautiful but also cozy enough 2013

  6. thank you for share!

  7. Click HERE For MORE INFO

  8. thanks for share!

  9. Investigate THIS Site
    [url=http://www.ksancp.com/member/loadingk.php?/healthy-weight-loss-meizitang-coupons-codes.html]healthy weight loss-meizitang-coupons codes[/url]
    healthy weight loss-meizitang-coupons codes

  10. Try here
    [url=http://bootssaleukonline.com]ugg boots uk sale[/url]
    ugg boots uk sale

  11. Check Out HERE
    [url=http://bootsforwomenoutlet.com]womens ugg boots[/url]
    womens ugg boots

  12. mbt outlet InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (2) » Toby’s Epril

  13. massai barefoot technology InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (2) » Toby’s Epril

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha