1.12.3 @Bean 사용 by ks

@Bean은 메소드 레벨 어노테이션이고 <bean/> 요소의 직접적인 유사점이다. 이 어노테이션은 * init-method * destroy-method * autowiring * name 같은 <bean/> 에 의해 제공되는 속성중의 몇가지를 제공한다.

@Configuration 이나 @Component로 어노테이드된 클래스에서 @Bean어노테이션을 사용할 수 있다.

Bean 선언

빈을 선언하기 위해서, @Bean 어노테이션을 메소드에 어노테이트할 수 있다. 메소드의 리턴 값으로서 구체화된 타입의 ApplicationContext안에 bean 정의를 등록하기 위해 이 메소드를 사용할 수 있다. 기본적으로 빈 이름은 메소드 이름과 같다. 아래는 @Bean 메소드 선언 예제이다.

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

이는 XML로 표현하자면,

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

두 선언 모두 transferService라는 빈을 ApplicationContext에 사용가능한 형태로 만들고, 아래 예제처럼 TransferServiceImple 타입의 인스턴스 오브젝트로 바운드한다.

TransferServiceImpl 오브젝트로 transferService bean을 만들겠다는 의

transferService -> com.acme.TransferServiceImpl

또한 기본 클래스나 인터페이스 리턴타입으로 @Bean메소드를 선언할 수 있다.

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

하지만, (TransferService)라는 구체화된 인터페이스 타입을 사전에 예견하기 위한 가시성이 떨어진다. 그런 다음 전 타입으로 컨테이너에 알려진 TransferServiceImple와 영향을 받는 단일 싱글톤 빈이 초기화된다. 늦게 초기화되는 빈(non-lazy singletone bean)이 아닌 애들은 선언된 순서에 따라 초기화되어서, 다른 컴포넌트가 선언되지 않은 타입(가령 transferService 빈이 한번 초기화된 @Autowired TransferServiceImpl 같이 ) 과 매치하려고 할 때 다른 매칭 결과를 보여줄 수도 있다.

그러니까, 구체화한 클래스가 아니라 인터페이스로 반환하는 방식으로 빈을 선언하면 그 인터페이스를 구체화한 클래스가 많은 경우 생성 시기가 다르니까 내가 원했던건 A라는 빈인데 같은 인터페이스를 확장하는 B가 매칭될 수 도 있다는 말인거같아! 아래 참조도 같은 이야기!

만약 일관되게 선언된 서비스 인터페이스를 통해 참조한다면, @Bean 반환 타입이 디자인 결정에 안전하게 참여할 수 있다. 하지만 잠재적으로 확장 타입으로 참조된 컴포넌트나 몇몇 인터페이스를 확장하는 컴포넌트들을 위해, 가장 구제척인 사용 가능한 반환타입을 선언하는게 더 안전하다. ( 적어도 빈이 참조할 주입 포인트가 요구될 만큼 구체적이어야 한다.)

빈 의존성

@Bean 이 어노테이트된 메소드는 빈을 빌드하기 위해 요구되는 의존성을 작성하는 임의의 파라미터들을 몇개 가질수 있다. 예를 들어서, 만약 TransferService 가 AccountRepository를 요구한다면, 우리는 아래 예제와 같이 메소드 파라미터를 가지는 의존성을 구체화할 수 있다.

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

매커니즘은 꽤 생성자 기반 의존성 주입과 비슷하다. the relevant section 을 참조하자.

라이프사이클 콜백 받기

@Bean 어노테이션으로 정의된 모든 클래스는 보통의 라이프 사이클 콜백을 지원하고, @PostConstruct과 @PreDestroy 어노테이션을 JSR-250에서 사용할 수 있다. 자세한 사항은 JSR-250 annotations 를 보자.

보통 스프링 라이크사이클 콜백은 완전하게 잘 지원된다. 만약 빈이 InitializingBean, DisposableBean이나 LifeCycle을 확장한다면, 대표적인 메소드는 컨테이너에 의해 불려질 것이다.

*Aware 인터페이스 (가령 BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware)의 기준 또한 완전히 제공된다.

@Bean어노테이션은 구체화한 임의의 초기화와 파괴 콜백 메소드를 스프링 XMl의 Bean 요소에서 init-method와 destroy-method 속성과 마찬가지로 원한다.

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

실행해보니까,

BeanOne 빈 생성

BeanOne - init() 호출

BeanTwo 빈 생성

뜨고, 앱 종료하면

BeanTwo - cleanup() 호출

이 출력

기본적으로, public close나 shutdown 메소드를 가지는 자바 configuration으로 정의된 빈은 자동적으로 파괴 콜백을 받는다. 만약 public close나 shutdown 메소드를 가지고 있고 컨테이너가 종료될때 불러지기를 원하지 않는다면, @Bean(destroyMethod="")를 빈 정의에 기본 inferred모드에 추가하면 된다.

JNDI로 얻은 리소스는, 라이프사이클이 앱 외부에서 관리되기 때문에 기본적으로 수행할수도있다. 특히 Java EE 앱 서버에 문제가 있는 것으로 알려진 DataSource는 항상 수행해야한다.

아래는 DataSource에 대한 자동 파괴 콜백을 지하는지 보여준다.

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

앱이 종료되면 close나 shutdown 메소드를 자동으로 부르는데 위의 예제에서는 ""로 안에 아무것도 안적고 선언을 해버려서 파괴될 때 부를 메소드가 없기 때문에 자동으로 파괴되지 않게끔 하는 거 같아. Datasource는 자동으로 없어지면 안되니까 우리가 원할 때 파괴되게끔 저런 방식을 쓰는 예제를 보여준거고.

또한 @Bean 메소드를 사용하면 일반적으로 Spring의 JndiTemplate 또는 JndiLocatorDelegate 헬퍼를 사용하거나 JndiObjectFactoryBean 변형을 사용하지 않고 프로그래밍 방식의 JNDI 조회를 사용다 (JndiObjectFactoryBean 변형은 반환하지 않는다.). 실제 유형 대신 FactoryBean 유형으로 선언해야다. 여기에 제공된 리소스를 참조하려는 다른 @Bean 메서드에서 상호 참조 호출에 사용하는 것이 더 어렵게 만든다.

JNDI (Java Naming and Directory Interface)

Directory 서비스에서 제공하는 데이터 및 객체를 발견하고 참고(lookup)하기 위한 자바 API.

JNDI는 일반적으로 다음의 용도로 쓰인다:

  • 자바 애플리케이션을 외부 디렉터리 서비스에 연결 (예: 주소 데이터베이스 또는 LDAP 서버)

  • 자바 애플릿이 호스팅 웹 컨테이너가 제공하는 구성 정보를 참고.[1]

예제에서 BeanOne 의 경우에는 동등하게 init() 메소드를 생성동안 직접적으로 부르는 것이 유효하다.

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

자바에서 직접적으로 작업할 때 객체로 원하는 모든것을 할수 있고 항상 컨테이너 라이프 사이클에 의존할 필요는 없다.

Bean scope 지정

스프링은 @Scope어노테이션을 포함해서 빈의 scope을 구체화할 수 있다.

- @Scope 어노테이션을 사용하는 것

@Bean어노테이션으로 정의된 빈들은 구체적인 scope를 가진다. Bean Scopes 에서 구체화된 표준 스코프중에 어떤거라도 사용할 수 있다.

기본 스코프는 싱글톤이지만 @Scope 어노테이션으로 오버라이드할 수 있다.

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
  • @Scope와 scoped-proxy

스프링은 scoped proxies 를 통해 스코프된 의존성과 동작하는 편리한 방법을 제공한다. 그런 프록시를 생성하는 가장 쉬운 방법은 XML configuration을 <aop:scoped-proxy> 요소를 사용하는 것이다. @Scope 어노테이션으로 자바에서 configuring하는 것은 proxyMode 속성을 지원하는 도구를 제공한다. 기본은 no proxy(ScopedProxyMode.NO)이지만 ScopedProxyMode.Target_CLASS 나 ScopedProxyMode.INTERFACES를 구체화할 수도 있다.

만약 XML 레퍼런스 문서에서 자바의 @Bean을 사용해서 스코프화된 프록시 예제를 이식 한다면, 아래를 보자.

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

@SessionScope 클래스에 들어가보니까,

public abstract org.springframework.context.annotation.ScopedProxyMode proxyMode() default org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS;

아래와같이 되어있었어. ScopedProxyMode라는 어노테이션이 기본이라서 내 생각엔 프록시를 사용할 수 있도록 내부적으로 동작하게 해주는 거 같아.

빈 이름 커스터마이징

기본적으로, configuration 클래스는 결과 빈의 이름으로서 @Bean 메소드의 이름을 사용한다. 이는 기능적으로 override되지만 name 속성을 사용할수도있다.

@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}

빈 알리아싱 (별명 붙이기)

Naming Beans 에서 얘기한 것 처럼, 때떄로 하나의 빈에 여러 이름을 붙이고 싶을 수 있다, 마찬가지로 빈 알리아싱(별명) 으로 알려져있다. @Bean의 name속성은 이러한 목적을 위해 문자열을 받는다. 아래 예제를 보자.

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

빈 설명

때때로, 빈의 더 상세화된 설명을 제공하는게 도움이 된다. 이는 부분적으로 빈이 모니터링을 목적으로 노출될때(아마도 JMX를 통해서) 유용하다.

@Bean에 설명을 추가하기 위해 @Description 어노테이션을 사용하자.

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}

Last updated