4.3 빈(Bean) 개요
스프링 IoC 컨테이너는 하나 이상의 beans을 관리한다. 이러한 빈은 XML <bean/> 정의 같은 컨테이너에 제공한 설정 메타데이터로 생성된다.
컨테이너 내부에서 이러한 빈 정의는 BeanDefinition 객체로 나타나고 이 객체는 (다른 정보들과 함께) 다음의 메타데이터를 포함하고 있다.
- 패키지에 최적화된(package-qualified) 클래스 명: 보통 정의된 빈의 실제 구현클래스이다.
- 빈의 행동에 대한 설정 요소들. 컨테이너에서 빈이 어떻게 동작해야 하는가에 대한 상태.(범위, 라이프사이클 콜백 등등)
- 빈이 동작하는 데 필요한 다른 빈들에 대한 참조. 이러한 참조들을 협력객체(collaborators)나 의존성(dependencies)이라고 부른다.
- 새로 생성된 객체에 설정해야 하는 그 외 설정값. 예를 들면 커넥션 풀을 관리하는 빈에서 사용해야 하는 커넥션의 수나 풀의 용량제한 등이다.
Table 4.1. 빈 정의
프로프티 | 설명한 곳 |
---|---|
class | |
name | |
scope | |
constructor arguments | |
properties | |
autowiring mode | |
lazy-initialization mode | |
initialization method | |
destruction method |
어떻게 특정 빈을 생성하는가에 대한 정보를 담고 있는 빈 정의에 대해 추가로 ApplicationContext 구현체도 사용자가 컨테이너 밖에서 생성해서 이미 존재하는 객체들을 등록할 수 있다. getBeanFactory() 메서드로 어플리케이션 컨텍스트의 BeanFactory에 접근해서 이러한 객체들을 등록한다. getBeanFactory() 메서드는 BeanFactory를 구현한 DefaultListableBeanFactory를 리턴한다. DefaultListableBeanFactory는 registerSingleton(..)와 registerBeanDefinition(..) 메서드로 이러한 등록을 지원한다. 하지만 일반적인 어플리케이션은 메타데이터 빈 정의에서 정의된 빈으로만 동작한다.
4.3.1 빈 이름짓기
모든 빈에는 하나 이상의 식별자가 있다. 이러한 식별자는 빈을 제공하는 컨테이너 내에서 반드시 유일해야 한다. 보통 빈에는 단 하나의 식별자가 있지만 하나 이상이 필요하다면 추가적인 식별자로 별칭(alias)을 생각해 볼 수 있다.
XML기반의 설정 메타데이터에서 id나 name 속성으로 빈의 식별자를 명시한다. id 속성으로 정확하게 하나의 id를 지정할 수 있다. 관례적으로 이 이름은 문자와 숫자로 작성하지만 ('myBean', 'fooService' 등등) 특수문자도 사용할 수 있다. 빈에 별도의 별칭을 사용하고 싶다면 name 속성으로 지정하면 되고 여러 개를 입력할 때는 콤마(,)나 세미콜론 (;)이나 공백으로 구분한다. 변경사항 문서를 보면 스프링 3.1 이전의 버전에서는 id 속성을 사용할 수 있는 문자를 제한하기 위해 xsd:ID로 사용했다. 스프링 3.1에서는 xsd:string 을 사용한다. XML 파서를 사용하지 않더라도 여전히 컨테이너가 빈 아이디가 유일해야 한다는 조건을 강제한다.
빈에 id나 name을 반드시 제공해야 하는 것은 아니다. id나 name을 명시하지 않는다면 컨테이너는 빈에 유일한 이름을 부여한다. 하지만 ref 요소나 Service Locator 스타일의 검색을 사용해서 빈을 이름으로 참조하고 싶다면 이름을 반드시 지정해야 한다. name을 지정하지 않는 이유는 내부 빈 과 협력객체 오토와이어링를 사용하는 것과 연관이 있다.
4.3.1.1 외부에서 정의된 빈의 별칭짓기
빈 정의에서 id 속성에서 명시한 이름과 조합하거나 name 속성에 여러 이름을 작성해서 빈에 하나 이상의 이름을 작성할 수 있다. 이러한 이름들은 같은 빈에 대한 별칭이다. 별칭은 컴포넌트 자체에 명시한 빈 이름을 사용해서 공통 의존성을 참조하는 각각의 컴포넌트를 어플리케이션에서 사용하는 상황 등에서 유용하다.
빈이 실제로 정의된 곳에 모든 별칭을 명시하는 것이 항상 적절한 것은 아니다. 때로는 다른 곳에서 정의된 빈에 대해서 별칭을 지정하는 것이 적절한 때도 있다. 이는 설정이 자신만의 객체 정의의 세트가 있는 여러 서브시스템에 분리된 대형 시스템에서 일반적이다. XML 기반의 설정 메타데이터에서 외부에서 정의된 빈에 별칭을 부여하기 위해 <alias/>를 사용할 수 있다.
빈 작명 관례
빈에 이름을 지을 때는 인스턴스의 필드 이름에 대한 표준 자바 관례를 사용한다. 즉, 빈 이름은 소문자로 시작하고 카멜케이스를 사용한다. 예를 들어 'accountManager', 'accountService', 'userDao', 'loginController' 등이 된다.(홑따옴표는 생략한다)
빈의 이름을 일관성 있게 지으면 설정을 읽기 쉽고 이해하기 쉽다. 스프링 AOP를 사용한다면 이름과 관련된 빈의 세트에 어드바이스(advice)를 적용할 때 큰 도움이 된다.
빈에 이름을 지을 때는 인스턴스의 필드 이름에 대한 표준 자바 관례를 사용한다. 즉, 빈 이름은 소문자로 시작하고 카멜케이스를 사용한다. 예를 들어 'accountManager', 'accountService', 'userDao', 'loginController' 등이 된다.(홑따옴표는 생략한다)
빈의 이름을 일관성 있게 지으면 설정을 읽기 쉽고 이해하기 쉽다. 스프링 AOP를 사용한다면 이름과 관련된 빈의 세트에 어드바이스(advice)를 적용할 때 큰 도움이 된다.
<alias name="fromName" alias="toName"/>
이 예제의 설정을 사용하면 같은 컨테이너에서 fromName라는 이름의 빈을 toName로도 참조할 수 있다.
예를 들어 서브시스템 A의 설정 메타데이터는 'subsystemA-dataSource'라는 이름으로 DataSource를 참조한다. 서브시스템 B의 설정 메타데이터는 'subsystemB-dataSource' 라는 이름으로 DataSource를 참조한다. 이 두 서브시스템을 사용해서 메인 어플리케이션을 구성했을 때 메인 어플리케이션은 'myApp-dataSource'라는 이름으로 DataSource를 참조한다. 이 3가지 이름이 같은 객체를 참조하게 하려면 MyAPP 설정 메타데이터에 다음의 별칭 정의를 추가해야 한다.
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
이제 각 컴포넌트와 메인 어플리케이션은 유일하면서 다른 어떤 정의와도 충돌하지 않는다는 보장을 받은 이름을 통해 데이터소스를 참조할 수 있다. 물론 같은 빈을 참조한다.
4.3.2 빈의 인스턴스화
본질적으로 빈 정의는 하나 이상의 객체를 생성하는 방법이다. 컨테이너는 요청이 발생했을 때 이름있는 빈에 대한 방법을 검색하고 실제 객체를 생성하는(또는 획득하는) 빈 정의로 은닉화된 설정 메타데이터를 사용한다.
XML기반의 설정 메타데이터를 사용하면 <bean/> 요소의 class 속성에서 인스턴스화 된 객체의 타입(또는 클래스)를 명시한다. 이 class 속성은 내부적으로는 BeanDefinition 인스턴스의 Class 프로퍼티다. class 속성은 강제적이다. (예외적인 상황은 Section 4.3.2.3, “인스턴스 팩토리 메서드를 이용한 인스턴스화”와 Section 4.7, “Bean definition inheritance”를 참고해라.) 두 가지 방법의 하나로 Class 프로퍼티를 사용한다.
- 보통은 컨테이너 스스로 빈의 생성자를 리플렉트하게 호출함으로써 직접 빈을 생성하는 경우에 생성되는 빈의 클래스를 명시한다. 약간은 new 오퍼레이터를 사용하는 자바 코드와 비슷하다.
- 객체를 생성하기 위해 호출될 static 팩토리 메서드가 있는 실제 클래스를 명시한다. 컨테이너가 빈을 생성하려고 클래스에서 static, factory 메서드를 호출하는 경우는 일반적이지는 않다. static 팩토리 메서드의 호출에서 리턴받은 객체의 타입은 같은 클래스나 다른 클래스와 완전히 같을 것이다.
4.3.2.1 생성자를 이용한 인스턴스화
생성자로 빈을 생성하면 일반적인 모든 클래스는 스프링과 함께 사용할 수 있다. 어떤 특정한 인터페이스를 구현하거나 특정한 스타일로 코딩할 필요가 없다. 그냥 빈 클래스를 명시하는 것만으로도 충분하다. 하지만 빈을 명시하려고 사용하는 IoC의 종류에 따라 기본 생성자(비어있는 생성자)가 필요할 수도 있다.
스프링 IoC 컨테이너는 관리하고자 하는 거의 모든 클래스를 관리할 수 있다. 진짜 JavaBean만 관리할 수 있다는 제약 같은 건 없다. 많은 스프링 사용자들은 기본 생성자(아규먼트가 없는)만 있는 실제 JavaBean에 프로퍼티에 대한 적절한 setter와 getter를 사용하는 방법을 선호한다. 물론 컨테이너에서 예외적으로 빈 스타일이 아닌 클래스도 사용할 수 있다. 예를 들어 JavaBean 스펙을 전혀 따르지 않는 레거시 커넥션풀을 사용하더라도 스프링이 잘 관리할 수 있다.
내부 클래스명
static 중첩클래스에 대한 빈 정의를 설정하고 싶다면 내부 클래스의 binary 이름을 사용해야 한다.
예를 들어 com.example 패키지에 Foo라는 클래스가 있고 이 com.example 클래스에는 Bar라는 static 내부 클래스가 있다면 빈 정의의 'class' 속성의 값은 다음과 같을 것이다.
com.example.Foo$Bar
이름에서 $ 문자는 바깥쪽 클래스 이름과 내부 클래스 이름을 구분하기 위해 사용한다.
static 중첩클래스에 대한 빈 정의를 설정하고 싶다면 내부 클래스의 binary 이름을 사용해야 한다.
예를 들어 com.example 패키지에 Foo라는 클래스가 있고 이 com.example 클래스에는 Bar라는 static 내부 클래스가 있다면 빈 정의의 'class' 속성의 값은 다음과 같을 것이다.
com.example.Foo$Bar
이름에서 $ 문자는 바깥쪽 클래스 이름과 내부 클래스 이름을 구분하기 위해 사용한다.
XML기반의 설정 메타데이터를 사용하면 다음과 같이 빈 클래스를 명시할 수 있다.
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
생성자에 아규먼트를 전달하거나(필요하다면) 객체가 생성된 후에 인스턴스에 프로퍼티를 설정하는 방법에 대해 자세히 알고 싶다면 의존성 주입을 참고해라.
4.3.2.2 정적 팩토리 메서드를 이용한 인스턴스화
정적 팩토리 메서드로 생성하는 빈을 정의할 때 static 팩토리 메서드가 있는 클래스를 지정하는 class 속성과 팩토리 메서드의 이름을 지정하는 factory-method 속성을 사용한다. 이 팩토리 메서드를 호출하면(뒤에서 설명할 선택적인 아규먼트로) 생성자로 생성한 빈과 동일하게 취급하는 살아 있는 객체를 리턴받는다. 레거시 코드에서 static 팩토리를 호출하는 것이 이러한 빈 정의의 한가지 사용법이다.
다음 빈 정의는 팩토리 메서드를 호출해서 빈이 생성될 것이라는 지정한다. 이 빈 정의는 리턴되는 객체의 타입(클래스)은 지정하지 않고 클래스의 팩토리 메서드만 지정했다. 이 예제에서 createInstance() 메서드는 반드시 정적 메서드여야 한다.
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
팩토리 메서드에 (선택적인) 아규먼트를 전달하고 팩토리에서 리턴된 객체의 인스턴스에 프로퍼티를 설정하는 메커니즘에 대한 자세한 내용은 의존성과 세부 설정 을 참고해라.
4.3.2.3 인스턴스 팩토리 메서드를 이용한 인스턴스화
정적 팩토리 메서드를 통한 인스턴스화와 비슷한 인스턴스 팩토리 메서드를 이용한 인스턴스화는 새로운 빈을 생성하기 위해 컨테이너에 존재하는 빈의 정적이 아닌 메서드를 호출한다. 이 메커니즘을 사용하려면 class 속성을 비워두고 factory-bean 속성에 현재 (또는 부모나 조상) 컨테이너에서 객체를 생성하기 위해 호출하는 인스턴스 메서드를 가지고 있는 빈의 이름을 지정한다. factory-method 속성에 팩토리 메서드의 이름을 설정한다.
<!-- createInstance()
를 호출하는 메서드를 담고 있는 팩토리 빈 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 이 로케이터 빈으로 필요한 의존성을 주입한다 -->
</bean>
<!-- 팩토리 빈을 통해 생성될 빈 -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
}
하나의 팩토리 클래스는 다음과 같이 하나 이상의 팩토리 메서드를 가질 수도 있다.
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 이 로케이터 빈으로 필요한 의존성을 주입한다 -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
이 접근은 팩토리 빈 스스로 의존성 주입 (DI)를 통해 관리되고 설정될 수 있다는 것을 보여준다. 의존성과 세부 설정 을 참고해라.
Note
스프링 문서에서 팩토리 빈은 스프링 컨테이너에 설정된 빈을 참조한다. 이 빈은 인스턴스나 정적 팩토리 메서드를 통해 객체를 생성하는 빈이다. 반면에 FactoryBean (대문자에 주의해라)은 스프링 고유의 FactoryBean 을 참조한다.
스프링 문서에서 팩토리 빈은 스프링 컨테이너에 설정된 빈을 참조한다. 이 빈은 인스턴스나 정적 팩토리 메서드를 통해 객체를 생성하는 빈이다. 반면에 FactoryBean (대문자에 주의해라)은 스프링 고유의 FactoryBean 을 참조한다.
'1장 스프링 프레임워크 소개'의 pom.xml를 보고 3.0.0.RELEASE 레퍼런스 문서를 번역했는 줄 알았는데,
'5.3.1 빈 이름짓기'를 보니 3.1에 대한 내용이 들어가 있네요. 번역한 원본 문서가 중간에 바뀌었나요?
1장 소개에 첫부분 설명에 나와 있기는 한데 정확히는 3.1 버전 상태에서 시작했습니다. 처음에 태깅이나 그런 부분을 잘 따서 했어야 했는데 그렇지는 못했고 당시 확인할 때도 코드랑 문서가 정확히 같은 버전으로 움직이지 않아서 스프링은 3.1이었지만 문서는 다 갱신되지 않은 느낌이었습니다.
https://github.com/spring-projects/spring-framework/commit/7636913710d49fa331f2a29d40d404640a111155
로그 보니 정확히는 이 커밋을 기준으로 시작했네요
감사합니다.