Bean Overview

2024. 6. 11. 15:21Spring Framework/Spring IoC

Bean Overview

Spring IoC 컨테이너는 하나 이상의 빈을 관리합니다. 이러한 빈은 컨테이너에 제공하는 구성 메타데이터(예: XML <bean/> 정의의 형태)로 생성됩니다.
컨테이너 내부에서 이러한 빈 정의는 BeanDefinition 객체로 표현되며, 이 객체는 다음 메타데이터(기타 정보 포함)를 포함합니다:

  • A package-qualified class name: 일반적으로 정의된 빈의 실제 구현 클래스.
  • 빈의 동작 구성을 나타내는 엘리먼트로, 빈이 컨테이너에서 어떻게 동작해야 하는지를 명시합니다(scope, lifecycle callbacks 등).
  • 빈이 작업을 수행하는 데 필요한 다른 빈에 대한 참조입니다. 이러한 참조는 협력자 또는 dependencies라고도 합니다.
  • 새로 생성된 객체에 설정할 기타 구성 설정. 예를 들어, 커넥션 풀을 관리하는 빈에서 풀의 크기 제한 또는 사용할 커넥션 개수.

이 메타데이터는 각 빈 정의를 구성하는 속성[properties] 세트로 변환됩니다. 다음 테이블는 이러한 속성을 설명합니다.

Table 1. The bean definition

Property Explained in…​
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Contructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
Lazy initialization mode Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

 

특정 빈을 생성하는 방법에 대한 정보를 포함하는 빈 정의 외에도, ApplicationContext 구현은 컨테이너 외부(사용자에 의해)에서 생성된 기존 객체의 등록도 허용합니다. 이는 getBeanFactory() 메서드를 통해 ApplicationContext의 BeanFactory에 접근하여 수행되며, 이 메서드는 DefaultListableBeanFactory 구현을 반환합니다. DefaultListableBeanFactory는 registerSingleton(..) 및 registerBeanDefinition(..) 메서드를 통해 이러한 등록을 지원합니다. 그러나 일반적인 애플리케이션은 정규 빈 정의 메타데이터를 통해 정의된 빈과만 작업합니다.

위 설명에 대한 예제 코드입니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

 

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;

public class Main {
    public static void main(String[] args) {
        // ApplicationContext를 초기화하여 AppConfig 클래스를 로드합니다.
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        
        // 빈 정의로부터 빈을 가져옵니다.
        MyBean myBean = context.getBean("myBean", MyBean.class);
        myBean.doSomething();

        // ApplicationContext의 BeanFactory를 가져옵니다.
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ((AnnotationConfigApplicationContext) context).getBeanFactory();

        // 컨테이너 외부에서 생성된 객체를 등록합니다.
        MyBean externalBean = new MyBean();
        beanFactory.registerSingleton("externalBean", externalBean);

        // 등록된 빈을 가져와 사용합니다.
        MyBean retrievedBean = context.getBean("externalBean", MyBean.class);
        retrievedBean.doSomething();

        // 새로운 빈 정의를 등록합니다.
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(MyBean.class.getName());
        beanFactory.registerBeanDefinition("newBean", beanDefinition);

        // 등록된 빈 정의로부터 빈을 가져와 사용합니다.
        MyBean newBean = context.getBean("newBean", MyBean.class);
        newBean.doSomething();
    }
}

 

이 예제에서는 다음을 수행합니다:

  1. ApplicationContext를 초기화하여 AppConfig 클래스를 로드합니다.
  2. 빈 정의로부터 빈을 가져옵니다.
  3. ApplicationContext의 BeanFactory를 가져옵니다.
  4. 컨테이너 외부에서 생성된 객체를 registerSingleton 메서드를 사용하여 등록합니다.
  5. 등록된 빈을 가져와 사용합니다.
  6. 새로운 빈 정의를 registerBeanDefinition 메서드를 사용하여 등록합니다.
  7. 등록된 빈 정의로부터 빈을 가져와 사용합니다.

이렇게 하면 자바 기반 구성 메타데이터를 사용하여 스프링 컨테이너를 설정하고 관리할 수 있습니다.

참고:
빈 메타데이터와 수동으로 제공된 싱글톤 인스턴스는 컨테이너가 자동 연결(autowiring) 및 기타 introspection 단계 동안 이를 적절히 처리할 수 있도록 가능한 한 빨리 등록되어야 합니다. 기존 메타데이터와 기존 싱글톤 인스턴스를 어느 정도 오버라이딩하는 것은 지원되지만, 런타임에 새로운 빈을 등록하는 것(팩토리에 대한 실시간 액세스와 동시에)은 공식적으로 지원되지 않으며, 이는 동시 액세스 예외, 빈 컨테이너의 일관되지 않은 상태 또는 두 가지 모두를 초래할 수 있습니다.

 

Naming Beans

모든 빈은 하나 이상의 식별자를 가지고 있습니다. 이러한 식별자는 빈을 호스팅하는 컨테이너 내에서 고유해야 합니다. 빈은 보통 하나의 식별자만 가지지만, 필요에 따라 추가 식별자를 가질 수 있으며, 이러한 추가 식별자는 별칭[aliases]으로 간주될 수 있습니다.

XML 기반 구성 메타데이터에서는 id 속성, name 속성 또는 둘 다를 사용하여 빈 식별자를 지정합니다. id 속성은 정확히 하나의 id를 지정할 수 있게 합니다. 관례적으로 이러한 이름은 영숫자(myBean, someService 등)이지만, 특수 문자를 포함할 수도 있습니다. 빈에 대한 다른 별칭을 도입하려면 name 속성에 쉼표(,), 세미콜론(;) 또는 공백으로 구분하여 지정할 수 있습니다. id 속성은 xsd:string 타입으로 정의되어 있지만, Bean id의 고유성은 XML 파서가 아닌 컨테이너에 의해 적용됩니다.

빈에 대해 이름이나 id를 제공할 필요는 없습니다. 이름이나 id를 명시적으로 제공하지 않으면, 컨테이너가 해당 빈에 대해 고유한 이름을 생성합니다. 그러나 ref 요소를 사용하거나 서비스 로케이터 스타일 조회를 통해 해당 빈을 이름으로 참조하려면, 이름을 제공해야 합니다. 이름을 제공하지 않는 이유는 주로 내부 빈 사용과 협력 객체 자동 연결(Autowiring)과 관련이 있습니다.

※ ref : XML 기반 구성 메타데이터의 <ref> 엘리먼트 

※ 내부 빈 : 내부 빈은 다른 빈의 속성으로만 사용되는 빈을 의미합니다. 이러한 빈은 외부에서 직접 참조할 필요가 없으므로 이름이나 ID를 명시적으로 지정할 필요가 없습니다.

Bean Naming Conventions
관례적으로 빈을 이름 짓는 경우 표준 Java 인스턴스 필드 이름 명명 규칙을 따릅니다. 즉, 빈 이름은 소문자로 시작하며 이후에 카멜 케이스 방식으로 작성됩니다. 이러한 이름의 예로는 accountManager, accountService, userDao, loginController 등이 있습니다.
Bean의 이름을 일관되게 지정하면 구성을 더 쉽게 읽고 이해할 수 있습니다. 또한 Spring AOP를 사용하면 이름별로 관련된 Bean 집합에 Advice를 적용할 때 많은 도움이 된다.

 

 

참고:
classpath에서 컴포넌트 스캐닝을 통해 Spring은 앞서 설명한 규칙에 따라 명명되지 않은 컴포넌트에 대한 빈 이름을 생성합니다. 기본적으로 간단한 클래스 이름을 취하고 첫 문자를 소문자로 바꿉니다. 그러나 두 개 이상의 문자가 있고 첫 번째 문자와 두 번째 문자가 모두 대문자인 (비정상적인) 특수한 경우에는 원래 대소문자가 유지됩니다. 이는 java.beans.Introspector.decapitalize(Spring이 여기서 사용하는)에 정의된 것과 동일한 규칙입니다.

 

 

Aliasing a Bean outside the Bean Definition

Bean Definition 자체에서 id 속성으로 지정된 하나의 이름과 name 속성에 지정된 여러 이름의 조합을 사용하여 빈에 여러 이름을 제공할 수 있습니다. 이러한 이름은 동일한 빈에 대한 동등한 별칭이 될 수 있으며, 애플리케이션의 각 컴포넌트가 해당 컴포넌트에 특정한 빈 이름을 사용하여 공통 의존성을 참조할 수 있게 하는 등 일부 상황에서 유용합니다.

그러나 빈이 실제로 정의된 곳에서 모든 별칭을 지정하는 것이 항상 적절한 것은 아닙니다. 때로는 다른 곳에 정의된 빈에 대해 별칭을 도입하는 것이 바람직할 때가 있습니다. 이는 보통 대규모 시스템에서 구성 설정이 각 하위 시스템별로 분리되어 각 하위 시스템이 자체 객체 정의 세트를 가지는 경우에 해당합니다. XML 기반 구성 메타데이터에서 <alias/> 엘리먼를 사용하여 이를 수행할 수 있습니다. 다음 예는 그 방법을 보여줍니다:

<alias name="fromName" alias="toName"/>

 

이 경우, 동일한 컨테이너내에서 fromName이라는 이름의 빈은 이 별칭 정의를 사용한 후 toName으로도 참조될 수 있습니다.

예를 들어, 하위 시스템 A의 구성 메타데이터는 subsystemA-dataSource라는 이름으로 DataSource를 참조할 수 있습니다. 하위 시스템 B의 구성 메타데이터는 subsystemB-dataSource라는 이름으로 DataSource를 참조할 수 있습니다. 이 두 하위 시스템을 모두 사용하는 기본 애플리케이션을 구성할 때 기본 애플리케이션은 myApp-dataSource라는 이름으로 DataSource를 참조합니다. 세 가지 이름이 모두 동일한 개체를 참조하도록 하려면 구성 메타데이터에 다음 별칭 정의를 추가하면 됩니다.

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

이제 각 컴포넌트와 메인 애플리케이션은 고유하고 다른 정의와 충돌하지 않는 이름(사실상 네임스페이스를 생성)을 통해 dataSource를 참조할 수 있으며, 이들은 동일한 빈을 참조합니다.

Java-configuration
Java 구성을 사용하는 경우 @Bean 어노테이션을 사용하여 별칭을 제공할 수 있습니다. 자세한 내용은 @Bean 어노테이 사용을 참조하세요.

 

 

 

Instantiation Beans

Bean Definition은 본질적으로 하나 이상의 객체를 생성하기 위한 레시피입니다. 컨테이너는 빈 객체 생성 요청 시에 명명된 빈의 레시피를 확인하고, 해당 빈 정의에 캡슐화된 구성 메타데이터(BeanDefinition의 속성에 저장됨)를 사용하여 실제 객체를 생성(또는 획득)합니다.

해당 빈 정의에 캡슐화된 구성 메타데이터란?

"해당 빈 정의에 캡슐화된 구성 메타데이터"는 빈을 생성하는 데 필요한 모든 정보를 담고 있으며, 이 정보는 스프링에서 BeanDefinition 객체의 속성들에 저장됩니다.

BeanDefinition 객체의 주요 속성들
BeanDefinition 객체는 스프링 컨테이너가 빈을 생성하고 관리하기 위해 사용하는 메타데이터를 포함합니다. 여기에는 다음과 같은 주요 속성들이 포함됩니다:
    1. 빈 클래스 이름 (Bean Class Name):
         빈이 어떤 클래스의 인스턴스인지를 정의합니다. 스프링 컨테이너는 이 클래스 정보를 사용하여 빈의
           인스턴스를 생성합니다.


    2. 스코프 (Scope):
         빈의 범위를 정의합니다. 예를 들어, singleton, prototype, request, session 등의 스코프가 있습니다.
           singleton 스코프는 빈의 단일 인스턴스가 애플리케이션 컨텍스트에 존재하도록 하고,
           prototype 스코프는 요청할 때마다 새로운 인스턴스를 생성합니다.


    3. 생성자 인자 값 (Constructor Arguments):
         빈이 생성될 때 생성자에 전달되는 인자들을 정의합니다. 이를 통해 스프링은 올바른 생성자를 호출하여
           객체를 생성할 수 있습니다.


    4. 프로퍼티 값 (Property Values):
         생성된 빈의 속성(property) 값을 설정하는데 사용됩니다.
           이는 setter 메서드나 필드에 값을 주입하는 데 사용됩니다.


    5. 의존성 (Dependencies):
         빈이 생성될 때 필요한 다른 빈에 대한 의존성을 정의합니다. 이를 통해 스프링은 필요한 다른 빈들을
           먼저 생성하고, 이 빈에 주입할 수 있습니다.


    6. 초기화 메서드 (Init Method):
         빈이 생성된 후 호출될 초기화 메서드를 정의합니다.

    7. 소멸 메서드 (Destroy Method):
         빈이 컨테이너에서 제거될 때 호출될 메서드를 정의합니다.


예시: BeanDefinition 메타데이터 예시
@Configuration
public class AppConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public MyService myService() {
        MyService myService = new MyService("Hello, Spring!");
        myService.setDependency(dependencyBean());
        return myService;
    }

    @Bean
    public DependencyBean dependencyBean() {
        return new DependencyBean();
    }
}​

 

위의 예시에서, myService 빈은 다음과 같은 메타데이터를 가지게 됩니다:
  빈 클래스 이름: MyService
  스코프: singleton (기본값)
  생성자 인자 값: 없음 (기본 생성자 사용)
  프로퍼티 값: setDependency 메서드를 통해 주입된 DependencyBean
  초기화 메서드: init
  소멸 메서드: destroy
  의존성: dependencyBean

이 모든 정보는 BeanDefinition 객체의 속성으로 캡슐화되며, 스프링 컨테이너는 이 정보를 사용하여 실제 빈 객체를 생성하고 관리합니다.

따라서, "해당 빈 정의에 캡슐화된 구성 메타데이터"는 바로 이 BeanDefinition 객체의 속성들에 저장된 정보들을 의미합니다. 이 정보들은 빈을 생성, 초기화, 주입, 관리하는 데 필요한 모든 것을 포함하고 있습니다.

 

XML 기반 구성 메타데이터를 사용하는 경우, 인스턴스화할 객체의 타입(또는 클래스)을 <bean/> 엘리먼트의 class 속성에 지정합니다. 이 class 속성(내부적으로는 BeanDefinition 인스턴스의 Class 속성)은 일반적으로 필수입니다. (예외 사항에 대해서는 Instantiation by Using an Instance Factory MethodBean Definition Inheritance 을 참조하십시오.) Class 속성은 다음 두 가지 방법 중 하나로 사용할 수 있습니다:

  • 일반적으로 컨테이너 자체가 생성자를 반사적으로 호출하여 Bean을 직접 생성하는 경우 생성될 Bean 클래스를 지정하는 것은 new 연산자를 사용하는 Java 코드와 다소 동일합니다.("반사적으로 호출한다"는 의미는 자바 리플렉션(Reflection) API를 사용하여 런타임에 클래스의 생성자를 호출하는 것을 말합니다. 즉, 컴파일 시점이 아닌 런타임 시점에 클래스의 메타데이터(예: 클래스, 생성자, 메서드, 필드 등)를 조사하고, 이를 통해 객체를 생성하거나 메서드를 호출하는 것을 의미합니다.)
  • 컨테이너가 빈을 생성하기 위해 특정 클래스의 static 팩토리 메서드를 호출하는 조금은 일반적이지 않는 경우, 객체를 생성하기 위해 호출되는 static 팩토리 메서드가 포함된 실제 클래스를 지정합니다. static 팩토리 메서드 호출에서 리턴된 객체 타입은 동일한 클래스이거나 완전히 다른 클래스일 수 있습니다.
// MyService.java
public class MyService {

    private String message;

    // private 생성자
    private MyService(String message) {
        this.message = message;
    }

    // static 팩토리 메서드
    public static MyService createInstance() {
        return new MyService("Hello from MyService!");
    }

    public String getMessage() {
        return message;
    }
}

// AppConfig.java (스프링 설정 클래스)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return MyService.createInstance();
    }
}
Nested class names
중첩 클래스에 대한 빈 정의를 구성하려면, 중첩 클래스의 바이너리 이름이나 소스 이름을 사용할 수 있습니다.

예를 들어, com.example 패키지에 SomeThing이라는 클래스가 있고, 이 SomeThing 클래스에 OtherThing이라는 정적 중첩 클래스가 있는 경우, 이들은 달러 기호($) 또는 점(.)으로 구분할 수 있습니다. 따라서 빈 정의의 class 속성 값은 com.example.SomeThing$OtherThing 또는 com.example.SomeThing.OtherThing이 됩니다.

 

Instantiation with a Constructor

1. 스프링 IoC 컨테이너의 범용성

  • 모든 클래스 호환: 스프링 IoC 컨테이너는 대부분의 일반적인 클래스를 관리할 수 있습니다. 클래스를 스프링 빈으로 등록하기 위해 특별한 인터페이스를 구현하거나 특정 코딩 패턴을 따를 필요가 없습니다. 즉, 개발자가 작성한 대부분의 클래스는 스프링에서 사용 가능하며 호환됩니다.

2. 생성자 접근 방식

  • 생성자를 통한 빈 생성: 스프링은 클래스를 빈으로 만들 때 생성자를 통해 객체를 생성할 수 있습니다. 이 경우, 특별한 요구사항이 없으면 기본 생성자가 필요하지 않지만, 특정 IoC 방식에서는 기본 생성자가 필요할 수 있습니다.

3. JavaBean 스타일 클래스

  • JavaBean 선호: 많은 스프링 사용자는 디폴트 생성자(기본 생성자)와 설정자(setter), 접근자(getter) 메서드를 가진 JavaBean 스타일의 클래스를 선호합니다. 이는 클래스의 속성들을 설정하고 가져올 수 있는 메서드가 있다는 뜻입니다.
  • JavaBean이 아니어도 관리 가능: 스프링은 JavaBean 사양을 따르지 않는 클래스도 관리할 수 있습니다. 예를 들어, 초기화 메서드나 설정자 메서드 없이 생성자만을 사용하는 클래스도 관리 가능합니다.

4. 특이한 클래스 관리

  • non-bean 스타일 클래스: 스프링 컨테이너는 일반적인 JavaBean 스타일이 아닌 특이한(non-bean) 스타일의 클래스도 관리할 수 있습니다. 예를 들어, 레거시 시스템에서 사용되는 특정 클래스를 스프링 컨테이너가 관리할 수 있습니다.

5. Java 기반 구성 메타데이터 예시

  • 스프링에서는 XML 대신 Java 코드로 빈 설정을 할 수 있습니다. 이때 @Configuration과 @Bean 어노테이션을 사용하여 구성 메타데이터를 정의합니다.
  • 이 설정은 스프링이 관리할 객체들을 정의하고, 어떻게 생성하고 초기화할지를 지정하는 방법입니다.

위 내용을 통해 스프링이 얼마나 유연하게 다양한 유형의 클래스와 객체를 관리할 수 있는지 이해할 수 있습니다.

 

1. JavaBean 스타일 클래스 관리 예시

// JavaBean 스타일의 클래스
public class MyBean {
    private String name;
    private int age;

    // 디폴트 생성자
    public MyBean() {
    }

    // 접근자와 설정자 (Getter와 Setter)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
// AppConfig.java (스프링 설정 클래스)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        MyBean myBean = new MyBean();
        myBean.setName("John Doe");
        myBean.setAge(30);
        return myBean;
    }
}


위 예시에서 MyBean은 JavaBean 스타일로 작성된 클래스이며, 스프링 IoC 컨테이너에 의해 관리됩니다. AppConfig 클래스는 @Bean 메서드를 통해 MyBean의 인스턴스를 생성하고 속성을 설정합니다.

2. JavaBean 사양을 따르지 않는 클래스 관리 예시

// JavaBean 사양을 따르지 않는 클래스
public class LegacyConnectionPool {
    private String connectionString;

    // 디폴트 생성자가 없음
    public LegacyConnectionPool(String connectionString) {
        this.connectionString = connectionString;
    }

    // JavaBean 스타일의 접근자와 설정자가 없음
    public void connect() {
        System.out.println("Connecting to: " + connectionString);
    }
}
// AppConfig.java (스프링 설정 클래스)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public LegacyConnectionPool legacyConnectionPool() {
        return new LegacyConnectionPool("jdbc:legacy-db://localhost:3306/mydb");
    }
}


이 두 번째 예시에서 LegacyConnectionPool은 JavaBean 스타일이 아닌 클래스로, 디폴트 생성자가 없고, 설정자(setter)와 접근자(getter) 메서드도 없습니다. 그럼에도 불구하고 스프링은 이 클래스를 관리할 수 있으며, AppConfig 클래스에서 @Bean 메서드를 사용해 이를 스프링 컨텍스트에 등록할 수 있습니다.

요약

  • JavaBean 스타일 클래스는 스프링에서 가장 일반적으로 사용되며, 디폴트 생성자와 설정자/접근자를 가집니다.
  • JavaBean 스타일이 아닌 클래스도 스프링에서 관리할 수 있으며, 이 경우 스프링은 클래스의 특성에 맞는 방법으로 빈을 생성하고 관리합니다.
  • Java 기반 구성 메타데이터는 @Configuration과 @Bean 어노테이션을 사용하여 스프링 컨테이너에서 사용할 빈을 정의합니다.

 

생성자에 아규먼를 제공하는 메커니즘(필요한 경우)과 객체가 생성된 후 객체 인스턴스 속성을 설정하는 방법에 대한 자세한 내용은 Injecting Dependencies을 참조하십시오.

 

다음은 자바 기반 구성 메타데이터를 사용하여 빈을 생성하는 일반적인 예제는 다음과 같습니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ExampleBean exampleBean() {
        return new ExampleBean();
    }

    @Bean(name = "anotherExample")
    public ExampleBeanTwo exampleBeanTwo() {
        return new ExampleBeanTwo();
    }
}

 

위 구성 클래스를 사용하여 ApplicationContext를 다음과 같이 초기화할 수 있습니다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        ExampleBean exampleBean = context.getBean(ExampleBean.class);
        ExampleBeanTwo exampleBeanTwo = (ExampleBeanTwo) context.getBean("anotherExample");

        // ExampleBean 및 ExampleBeanTwo 사용
        exampleBean.doSomething();
        exampleBeanTwo.doSomethingElse();
    }
}

 

위 예제에서는 AppConfig라는 @Configuration 클래스를 정의하고, @Bean 어노테이션을 사용하여 빈을 생성합니다. 그런 다음 AnnotationConfigApplicationContext를 사용하여 Spring 컨테이너를 초기화하고, 빈을 가져와 사용할 수 있습니다.

참고:
생성자 아규먼트의 경우, 컨테이너는 여러 오버로드된 생성자 중에서 해당 생성자를 선택할 수 있습니다. 그러나 애매함을 피하기 위해 생성자 시그니처는 가능한 한 단순하게 유지하는 것이 좋습니다.

 

 

Instantiation with a Static Factory Method

xml 기반 구성 메타데이터에서 static 팩토리 메소드를 사용하여 생성하는 Bean을 정의할 때 class 속성을 사용하여 static 팩토리 메소드가 포함된 클래스를 지정하고, 팩토리 메소드 자체의 이름을 지정하려면 factory-method라는 속성을 사용하십시오. 나중에 설명하는 선택적 아규먼트를 사용하여 이 메서드를 호출하고 라이브 객체를 반환할 수 있어야 하며, 이후 이 객체는 생성자를 통해 생성된 것처럼 처리됩니다. 이러한 Bean 정의의 용도 중 하나는 레거시 코드에서 static 팩토리 메소드를 호출하는 것입니다.

다음 빈 정의는 팩토리 메서드를 호출하여 빈을 생성할 것임을 지정합니다. 이 빈 정의는 리턴된 객체의 타입(클래스)을 지정하지 않고, 대신 팩토리 메서드를 포함하는 클래스를 지정합니다. 이 예제에서 createInstance() 메서드는 정적 메서드여야 합니다. 다음 예는 팩토리 메서드를 지정하는 방법을 보여줍니다:

<bean id="clientService"
	class="examples.ClientService"
	factory-method="createInstance"/>

 

다음 코드는 위 xml 기반 구성 메타데이터와 동일한 구성 정보를 가지는 자바 기반 구성 메타데이터입니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService() {
        return ClientService.createInstance();
    }
}

 

다음 예제는 앞서 설명한 빈 정의와 함께 사용할 수 있는 클래스를 보여줍니다:

public class ClientService {
	private static ClientService clientService = new ClientService();
	private ClientService() {}

	public static ClientService createInstance() {
		return clientService;
	}
}

 

팩토리 메서드에 (선택적) 아규먼트를 제공하고, 객체가 팩토리에서 리턴된 후 객체 인스턴스 속성을 설정하는 메커니즘에 대한 자세한 내용은 " Dependencies and Configuration in Detail "을 참조하십시오.

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        ClientService clientService = context.getBean(ClientService.class);
        clientService.doSomething(); // 예시 메서드 호출
    }
}

 

Note
팩토리 메서드 아규먼트의 경우, 컨테이너는 동일한 이름의 여러 오버로드된 메서드 중에서 해당 메서드를 선택할 수 있습니다. 그러나 애매함을 피하기 위해 팩토리 메서드 시그니처는 가능한 한 단순하게 유지하는 것이 좋습니다.

 

Tip
팩토리 메서드 오버로딩과 관련된 일반적인 문제 사례는 mock 메서드의 많은 오버로드가 있는 Mockito입니다. 가능한 가장 구체적인 mock 변형을 선택하십시오:
<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg type="java.lang.Class" value="examples.ClientService"/>
         <constructor-arg type="java.lang.String" value="clientService"/>
</bean>

 

Instantiation by Using an Instance Factory Method

static 팩토리 메소드를 통한 인스턴스화와 유사하게, instance 팩토리 메소드를 사용한 인스턴스화는 컨테이너에서 특정(아래 예에서는 serviceLocator 빈)Bean의 non-static 메소드를 호출하여 특정 Bean(아래 예에서는 clientSevice 빈)을 생성합니다. 이 메커니즘을 사용하려면 class 속성을 비워두고 factory-bean 속성에서 객체를 생성하기 위해 호출될 인스턴스 메서드가 포함된 현재 컨테이너에 등록된 Bean(serviceLocator) 이름을 지정합니다. factory-method 속성을 사용하여 팩토리 메소드 자체의 이름을 설정합니다. 다음 예에서는 이러한 Bean을 구성하는 방법을 보여줍니다.

<!-- the factory bean, which contains a method called createClientServiceInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
	<!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
	factory-bean="serviceLocator"
	factory-method="createClientServiceInstance"/>

다음 예에서는 해당 팩토리 빈 클래스를 보여줍니다.

public class DefaultServiceLocator {

	private static ClientService clientService = new ClientServiceImpl();

	public ClientService createClientServiceInstance() {
		return clientService;
	}
}
package examples;

public interface ClientService {
    void doSomething();
}

public class ClientServiceImpl implements ClientService {
    @Override
    public void doSomething() {
        System.out.println("ClientService is doing something");
    }
}

 

위 예제를 자바 기반 구성 메타데이터로 변경하면 다음과 같습니다.

 

자바 기반 구성 메타데이터를 만듭니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public DefaultServiceLocator serviceLocator() {
        return new DefaultServiceLocator();
    }

    @Bean
    public ClientService clientService() {
        return serviceLocator().createClientServiceInstance();
    }
}

 

마지막으로, 이 구 클래스를 사용하여 ApplicationContext를 초기화하고 빈을 가져오는 코드를 작성합니다:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import examples.ClientService;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        ClientService clientService = context.getBean(ClientService.class);
        // ClientService 사용
        clientService.doSomething();
    }
}

 

하나의 팩토리 빈 클래스가 여러 팩토리 메서드를 가질 수도 있습니다. 다음 예제에서 보여줍니다:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
	<!-- inject any dependencies required by this locator bean -->
</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();

	public ClientService createClientServiceInstance() {
		return clientService;
	}

	public AccountService createAccountServiceInstance() {
		return accountService;
	}
}

 

위 예제를 자바 기반 구성 메타데이터를 사용하는 코드들은 다음과 같습니다.

public interface AccountService {
    void performAction();
}

public class AccountServiceImpl implements AccountService {
    @Override
    public void performAction() {
        System.out.println("AccountService is performing an action");
    }
}

 

자바 기반 구성 메타데이터 클래스를 다음과 같이 작성합니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public DefaultServiceLocator serviceLocator() {
        return new DefaultServiceLocator();
    }

    @Bean
    public ClientService clientService() {
        return serviceLocator().createClientServiceInstance();
    }

    @Bean
    public AccountService accountService() {
        return serviceLocator().createAccountServiceInstance();
    }
}

 

main 메소드는 다음과 같습니다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import examples.ClientService;
import examples.AccountService;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        ClientService clientService = context.getBean(ClientService.class);
        AccountService accountService = context.getBean(AccountService.class);

        // ClientService 및 AccountService 사용
        clientService.doSomething();
        accountService.performAction();
    }
}

 

이 접근 방식은 팩토리 빈 자체가 의존성 주입(DI)을 통해 관리되고 구성될 수 있음을 보여줍니다. 자세한 내용은 "  Dependencies and Configuration in Detail "을 참조하십시오.

 

Note:
Spring 문서에서 "factory bean"은 Spring 컨테이너에 구성된 빈으로, 인스턴스나 정적 팩토리 메소드를 통해 객체를 생성하는 빈을 의미합니다. 대조적으로, FactoryBean(대문자 사용에 주의)은 Spring 특정 FactoryBean 구현 클래스를 참조합니다.

 

 

Determining a Bean’s Runtime Type

특정 bean의 런타임 타입을 결정하는 것은 간단하지 않습니다. bean 메타데이터 정의에서 지정된 클래스는 초기 클래스 참조일 뿐이며, 잠재적으로 선언된 팩토리 메서드와 결합되거나 FactoryBean 클래스일 경우 빈의 다른 런타임 타입으로 이어질 수 있습니다. 또는 인스턴스 레벨 팩토리 메서드의 경우에는 (지정된 factory-bean 이름을 통해 해결되므로) 전혀 설정되지 않을 수도 있습니다. 추가적으로, AOP 프록시가 bean 인스턴스를 인터페이스 기반 프록시로 래핑할 수 있으며, 이 경우 타겟 빈의 실제 타입이 제한된 인터페이스로만 노출됩니다.

 

빈 메타데이터 정의에서 지정된 클래스가 팩토리 메서드와 결합된 예제를 명확히 보여주는 코드를 작성해보겠습니다.

다음은 빈 메타데이터 정의에서 지정된 클래스가 팩토리 메서드와 결합된 예제를 보여주는 코드입니다.

먼저, DefaultServiceLocator 클래스를 정의합니다:

package examples;

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    // 팩토리 메서드
    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

 

ClientService 인터페이스를 정의합니다.

// ClientService 인터페이스
package examples;

public interface ClientService {
    void doSomething();
}

 

ClientService 인터페이스를 구현한 ClientServiceImpl 클래스를 정의합니다.

// ClientService 구현 클래스
package examples;

public class ClientServiceImpl implements ClientService {
    @Override
    public void doSomething() {
        System.out.println("ClientServiceImpl is doing something");
    }
}


이제 Spring 구성 클래스를 작성합니다:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public DefaultServiceLocator serviceLocator() {
        return new DefaultServiceLocator();
    }

    @Bean
    public ClientService clientService() {
        // serviceLocator 빈의 createClientServiceInstance 팩토리 메서드를 호출하여 빈 생성
        return serviceLocator().createClientServiceInstance();
    }
}


이 설정 클래스는 DefaultServiceLocator 클래스의 팩토리 메서드 createClientServiceInstance를 사용하여 ClientService 빈을 생성합니다. 이제, 이 구성 클래스를 사용하여 ApplicationContext를 초기화하고 빈을 가져오는 코드를 작성합니다:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import examples.ClientService;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // ClientService 빈을 가져옴
        ClientService clientService = context.getBean(ClientService.class);
        clientService.doSomething(); // ClientServiceImpl is doing something 출력
    }
}


위 코드에서는 AppConfig라는 @Configuration 클래스를 정의하고, @Bean 어노테이션을 사용하여 serviceLocator와 clientService 빈을 생성합니다. clientService 빈은 serviceLocator 빈의 createClientServiceInstance 팩토리 메서드를 호출하여 생성됩니다. 이는 빈 메타데이터 정의에서 지정된 클래스가 팩토리 메서드와 결합된 경우를 보여줍니다.

 

특정 빈의 실제 런타임 타입을 알아내는 권장 방법은 지정된 빈 이름에 대해 BeanFactory.getType 호출을 사용하는 것입니다. 이는 위의 모든 경우를 고려하여 동일한 빈 이름에 대해 BeanFactory.getBean 호출이 반환할 객체의 타입을 반환합니다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.beans.factory.BeanFactory;
import examples.ClientService;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // ClientService 빈을 가져옴
        ClientService clientService = context.getBean(ClientService.class);
        clientService.doSomething(); // ClientServiceImpl is doing something 출력

        // BeanFactory를 사용하여 빈의 실제 런타임 타입을 알아냄
        BeanFactory beanFactory = context.getAutowireCapableBeanFactory();
        Class<?> clientServiceType = beanFactory.getType("clientService");
        System.out.println("Runtime type of 'clientService' bean: " + clientServiceType.getName());
    }
}

 

 

 

'Spring Framework > Spring IoC' 카테고리의 다른 글

Dependencies  (0) 2024.11.14
Dependency Injection  (0) 2024.06.11
Using @Autowired  (0) 2023.12.10
Dependencies and Configuration in Detail  (0) 2023.12.10
Fine-tuning Annotation-based Autowiring with @Primary  (0) 2023.07.08