본문 바로가기

SpringFramework Core - I. IoC 컨테이너/10. 클래스패스 스캐닝과 관리받는 컴포넌트들

10.5. 컴포넌트 안에서 bean 메타데이터 정의하기

원문: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factorybeans-annotations

 

 

스프링 컴포넌트들은 컨테이너에게 bean 정의 메타데이터 또한 제공한다. 여러분은 @Configuration이 붙은 클래스에서 bean의 메타데이터를 정의하는 @Bean 어노테이션을 통해 이 일을 할 수 있다. 다음 예시는 그 방법을 보여준다.

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
    
    public void doWork() {
        // 컴포넌트의 메소드 구현은 생략됨
    }
}

위의 클래스는 doWork() 메소드 안에 코드를 가지고 있는 스프링 컴포넌트다. 그런데 이 클래스는 publicInstance() 메소드를 참조하는 팩토리 메서드를 가지고 있는 bean 정의의 역할도 해낸다. @Bean 어노테이션은 팩토리 메서드와 @Qualifier 어노테이션의 수식값 등의 여타 bean 정의 프로퍼티들을 식별한다. 메소드 수준의 또다른 어노테이션들로는 @Scope, @Lazy, 커스텀 수식 어노테이션들이 있다.

컴포넌트 초기화 역할에 더해, @Autowired나 @Inject로 표시된 주입 지점들에 @Lazy 어노테이션을 붙일 수 있다. 이 context에서는 lazy-resolution 프록시의 주입을 일어난다.

Autowired되는 필드들과 메소드들은 autowire된 @Bean 메소드들의 추가적인 지원을 받는다. 다음 예시는 그 방법을 보여준다.

@Component
public class FactoryMethodComponent {

    private static int i;
    
    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicIntance");
    }
    
    // 커스텀 수식자와 autowiring 메소드 매개변수 사용하기
    @Bean
    protected TestBean protectedInstance(
                    @Qualifier("public") TestBean spouse,
                    @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }
    
    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }
    
    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }

예시에서는 문자열인 메소드 매개변수 'country'를 privateInstance라는 다른 bean의 'age'라는 프로퍼티의 값과 autowire하고 있다. SpEL 요소는 '#{ <표현식> }' 표기법을 통해 프로퍼티의 값을 정의한다. @Value 어노테이션에서는, 표현식 분석기가 표현 문구를 분석할 때 bean의 이름을 찾아볼 수 있도록 미리 설정되어 있다. 

 

스프링 프레임워크 4.3에서는, 현재의 bean을 생성하는 주입 지점으로 접근하기 위해 InjectionPoint 타입의 팩토리 메서드 매개변수를 선언할 수 있다(또는 InjectPoint의 더 구체적인 서브클래스인 DependencyDescriptor). 하지만 이는 실제로 bean 인스턴스들을 생성할 때만 적용되고, 이미 존재하는 인스턴스들의 주입에는 적용되지 않는다는 점을 주의하라. 결과적으로 이 기능은 프로토타입 scope의 bean에 있어 가장 합리적이다. 다른 scope들에게는, 팩토리 메서드가 오직 주어진 scope의 새로운 bean 인스턴스만을 생성하도록 하는 주입 지점을 바라본다(예를 들어, lazy한 싱글턴 bean을 생성하는 의존성). 그런 경우들에서는, 의미적인 처리가 첨부되어 제공된 주입 지점 메타데이터를 사용할 수 있다. 다음 예시는 어떻게 InjectionPoint를 사용하는지 보여준다.

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

일반적인 스프링 컴포넌트 안의 @Bean 메소드들은 스프링 @Configuration 클래스 안의 것들과는 다르게 처리된다. @Component 클래스들은 메소드와 필드의 호출을 가로채는 CGLIB에 의해 확장되지 않는다. CGLIB의 프록시 작업은 @Configuration 클래스의 @Bean 메소드 내부의 메소드나 필드를 호출하여, 협력하는 객체에 대한 bean 메타데이터 참조들을 만들어내도록 한다. 그런 메소드들은 보통의 자바에서는 호출되지 않으며, 스프링 bean들의 일반적인 라이프사이클 관리와 프록시 작업을 위해 컨테이너를 통해 호출된다. 심지어 @Bean 메소드를 프로그램적으로 호출하는 식으로 다른 bean들을 참조할 때도 마찬가지이다. 이와는 대조적으로, 평범한 @Component 클래스의 @Bean 메소드 안에 있는 메소드나 필드를 호출하는 것은 특별한 CGLIB 처리나 다른 제약 사항이 없는 표준적인 자바에서의 의미를 지닌다. 

 

※ @Bean 메소드들을 static으로 선언할 수도 있을 것이다. 그러면 그들을 포함하는 설정 클래스를 인스턴스로 만들지 않고도 호출할 수 있게 된다. 이는 특히 후처리기 bean들을 만들때 의미가 있다(예를 들어, BeanFactoryPostProcessor나 BeanPostProcessor). 그런 bean들은 컨테이너의 라이프사이클 상 일찍 초기화되며, 또한 그 시점에서는 다른 설정 부분들에 영향을 주지 않도록 해야하기 때문이다.

 

static한 @Bean 메소드들을 호출하는 것은 컨테이너에 의해 인터셉트되지 않는다. 심지어는 @Configuration 클래스들 안에서도 인터셉트되지 않는데, 여기에는 기술적인 한계가 있기 때문이다. 바로 CGLIB 서브클래스화가 non-static 메소드들만 오버라이드할 수 있다는 것이다. 결과적으로 다른 @Bean 메소드의 직접 호출은 표준적인 자바의 맥락을 지니게된다. 이는 팩토리 메소드로부터 직접 독립된 인스턴스를 반환받게 해준다.

 

자바 언어의 @Bean 메소드에 대한 가시성이, 스프링 컨테이너 안의 bean 정의라는 결과에 곧바로 영향을 주지는 않는다. 이미 본 것처럼, 여러분은 자유롭게 팩토리 메소드들을 non-@Configuration 클래스들로 선언할 수 있으며 정적 메소드들을 아무데서나 선언할 수 있다. 하지만 @Configuration 클래스들의 보통의 @Bean 메소드들은 오버라이드되어야 한다. 즉, 그들은 private이나 final로 선언되어서는 안 된다.

 

@Bean 메소드들은 자바 8 기본 메소드들과 더불어, 주어진 컴포넌트나 설정 클래스와 같은 base 클래스들에서 찾을 수 있다. 이는 복합적인 설정들을 구성하는 유연성을 크게 늘려준다. 심지어 스프링 4.2에서는 자바 8의 기본 메소드들을 통해 다중 상속도 가능하다.

 

결국, 단일한 클래스가 여러 @Bean 메소드들을 데리고 있을 수 있다. 런타임 시에 사용가능한 의존성에 따라 복수의 팩토리 메소드를 사용하기 위해서이다. 이는 다른 설정 상황에서, "가장 탐욕스런" 생성자 또는 팩토리 메소드를 선택하는 알고리즘과 같다. 가장 많이 만족시킬 수 있는 의존성의 변종들이 생성 시점에 선택되는 상황이 그것이다. 이는 컨테이너가 여러 @Autowired 생성자들 중에 어떤 것을 택하는가와 유사하다.