스프링 3.0 GA 나오기 며칠 전에 쓰는 스프링 3.0 이야기.

Spring 3.0에 JavaConfig이 통합되는 과정은 그리 간단하지 않았다. M4까지 수년간 개발해왔던 JavaConfig을 완전히 코어 스프링 답게 대대적으로 변경시켜서 애노테이션 빈 설정의 자연스러운 한 가지 기능으로 통합시켰다. 기존 JavaConfig에서의 모습과는 그래서 큰 차이가 있다. 기본 아이디어만 가져왔을 뿐 완전히 새롭게 개발했다고 해도 지나친 말은 아닐 것이다.

아무튼. 자바코드를 통한 컨텍스트 설정이라는 이름으로 최종 정리된 @Configuration/@Bean 조합은 기존 @Component와 스테레오 타입 기반의 스캐너 자동인식 등록기능과 @Autowired로 대표되는 애노테이션 설정기능과 매우 긴밀하게 연결되어버렸다. 이 세가지는 거의 대부분 호환된다.

당연히 자동인식 @Component 클래스에는 @Autowired 같은 애노테이션 설정이 가능하다. 마찬가지로 @Configuration으로 만들어지는 자바코드 설정용 빈도 @Autowired를 비롯한 각종 애노테이션 설정이 먹는다. 그 덕분에 여러 개의 @Configuration 클래스 사이에 상호 DI가 가능해졌다. 또 @Configuration은 @Component를 메타 애노테이션으로 가지고 있다. 그 덕분에 자동 스캔의 대상이 된다. 굳이 XML에 빈으로 등록해줄 필요가 없다.

@Component가 붙은 클래스가 빈으로 등록되듯이 @Configuration 클래스도, 비록 설정만을 위한 것임에도 정식 빈으로 등록된다. 언제든 getBean()으로 가져와 사용할 수 있다. 이쯤 되면 @Configuration은 @Component의 일종이라는 것을 눈치챘을 것이다.

그런데 그 반대도 가능하다. @Autowired가 <bean>으로 등록되는 일반 빈이나, @Component 자동스캔 대상 빈이나, @Configuration의 설정을 위한 빈에 모두 적용되는 것처럼, 메소드 레벨에서 빈을 정의하는 @Configuration용 @Bean도 일반 빈에 그대로 적용된다.

무슨 말인가하면 @Configuration이 붙지 않은 빈, 예를 들어 @Component 자동 스캔 대상 클래스의 메소드에도 @Bean을 붙이면 그 자체로 하나의 빈 설정이 되버린다는 사실이다. 굳이 @Component 조차도 필요없다. 어떤 빈이든 @Bean만 붙이면 그 자체로 완벽한 팩토리 방식의 메소드를 이용한 빈 설정으로 인식되서 새로운 빈이 하나 추가된다.

이제는 굳이 <bean> 태그에 factory-method를 선언해줄 필요도 없게 되었다는 말이다.

 

하지만 일반 빈에서 @Bean을 사용하는 것은 사실 혼란의 가능성이 있다. 기억을 더듬어보면 3.0 개발 중에 @FactoryBean이라는 이름의 애노테이션이 존재했던 적이 있다. 그것을 더 일반화 시켜서 그냥 @Bean이라는 이름으로 통합한 것이다.

하지만 @Configuration에서 사용되는 @Bean과 @Component에서 사용되는 빈은 그 성격이 다르다.

최신 레퍼런스 매뉴얼에 보면 이런 설명이 나온다. 굵은 글씨로 표시한 부분을 잘 이해해야 한다.

The @Bean methods in a Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that@Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Configuration classes. @Bean methods create bean metadata references to collaborating objects. Methods are not invoked with normal Java semantics. In contrast, calling a method or field within a @Component classes @Bean method has standard Java semantics.

@Configuration안의 @Bean은 메소드를 호출하는 것으로 DI가 가능하다. JavaConfig 방식이다. 이를 위해서 @Configuration빈은 CGLib을 통해서 enhanced 된다. 왜냐면 @Bean메소드로 정의된 빈이 싱글톤이라면 메소드를 여러번 호출해도 항상 동일한 오브젝트가 리턴되는 것이 보장되야 하기 때문이다. 따라서 이건 normal Java semantics가 아니다. @Configuration 클래스는 자바코드 표현을 차용한 DSL일 뿐이다. 자바코드의 semantic과 다르게 동작한다.

다음은 @Configuration 안에서 빈을 정의한 코드이다. 여기서 printer() 메소드를 호출하는 것으로 ref=”printer”와 같은 효과를 낸다. 하지만 자바의 일반 동작방식과는 달리 아무리 여러번 printer()를 호출해도 매번 동일한 오브젝트가 돌아온다.

@Bean Hello hello() {

  Hello h = new Hello();

  h.setPrinter(printer());

  h.setSubPrinter(printer());

}

@Bean Printer printer() {

  return new Printer();

}

반면에 같은 @Bean이지만 @Component나 일반 빈에서 사용되면 그 때는 standard Java semantics로 동작한다. 그 말은 enhanced되지 않는 다는 뜻이며 메소드 호출로 DI를 구성하는 것은 심각한 오류를 낳게 될 수 있다는 점이다.

이 때문에 @Component의 @Bean 안에서의 DI구현은 다른 빈을 메소드 파라미터 인젝션을 사용해서 레퍼런스를 받은 후 그것을 사용해야만 한다. 안그러면 아주 심각한, 찾기 힘든 버그가 발생할 것이다. 예를 보자. @Configuration과 비슷해보이지만 DI부분은 확연히 다르다. 여기서 publicInstance빈이 싱글톤 스코프를 보장받으려면 이를 참조하는 다른 @Bean에서는 절대 메소드를 직접 호출해서는 안된다.

이 클래스의 설정의 의미를 명확히 이해할 수 있고 @Configuration에서 쓸 때와의 차이점을 명확히 구분할 수 있으며, 이런 식으로 실전에서 자유자재로 구성할 수 있다면 스프링 3.0의 심오한 DI 세계를 마스터하는데 첫 발은 디딘 것으로 자신해도 좋을 것이다.

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters

    @Bean @BeanAge(1)
    protected TestBean protectedInstance(@Qualifier("public") TestBean spouse,
                                         @Value("#{privateInstance.age}") String country
) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(tb);
        tb.setCountry(country);
        return tb;
    }

    @Bean @Scope(BeanDefinition.SCOPE_SINGLETON)
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION,
                 proxyMode = ScopedProxyMode.TARGET_CLASS)
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

한가지 아쉬운 것은 이런 혼란을 피하기 위해서 @Component에서는 @Bean 대신에 다른 이름을 사용하는 것이 좋지 않았을까 하는 점이다. 뭐.. 나름 고민 끝에 @Bean으로 통일한 것이긴 하겠지만.

 

Spring 3.0은 2.5에서 일부 시도한 DI의 변화를 거의 극한까지 밀어 붙여서 지금까지 나온 어떤 DI기술보다 가장 심오하고 이상적인 레벨의 IoC/DI 프레임워크로 변신했다. 스프링 쫌 한다는 사람도 3.0을 제대로 쓴다고 말하려면 IoC공부에 꽤 많은 시간을 다시 투자할 각오를 하는게 좋을 것이다.

나도 아직 헷갈릴 정도로.. 쉽지 않다.

Related posts:

  1. Spring 3.0 (33) JavaConfig의 통합과 변신. 메타-빈(meta-bean) 개념의 등장.
  2. Spring 3.0 (54) 드디어 등장한 ConfigurationClassApplicationContext
  3. S1A 2008 셋째날 – Spring JavaConfig
  4. Spring 3.0 (58) Static Class를 XML없이 빈으로 등록하기
  5. Spring 상식퀴즈 (1) – DI 태클하기
  6. Spring 3.0 EL (Spel)을 이용한 AssertThrows 확장 (1)
  7. Inside Spring (5) PropertyPlaceholderConfigurer를 @Bean으로 정의해서는 안되는 이유
  8. 미리 보는 Spring 3.0.1의 변경사항
  9. 스프링 빈의 이름은 한글로 지어도 된다
  10. Spring 3.0 (52) 반쪽짜리 3.0 RC1 공개
  11. Spring 3.0 (46) Spring 3.0 M4 릴리스
  12. Spring 3.0 (28) R-669 Update
  13. Spring ROO 대충대충 분석 (3) ROO의 Inter-type declaration
  14. InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (1)
  15. Google App Engine for Java에서 Spring 3.0 사용하기

Facebook comments:

to “Spring 3.0 (56) @Bean 사용의 주의사항”

  1. This site was… how do you say it? Relevant!! Finally I’ve found something that helped me. Thank you!

  2. Hi, Neat post. There’s a problem with your web site in internet explorer, would test this… IE still is the market leader and a big portion of people will miss your wonderful writing due to this problem.

  3. Im thankful for the article.Thanks Again. Great.

  4. I have recently started a website, the info you offer on this website has helped me greatly. Thank you for all of your time & work.

  5. I’d must test with you here. Which is not something I usually do! I enjoy reading a publish that may make folks think. Additionally, thanks for permitting me to remark!

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