https://sundaland.tistory.com/284
Instantistion Beans
Bean Definition 본질적으로 하나 이상의 객체를 생성하기 위한 레시피이다. 컨테이너는 요청 시 명명된 빈의 레시피를 확인하고, 해당 빈 정의에 캡슐화된 구성 메타데이터를 사용하여 실제 객체를 생성(또는 획득)한다.
해당 빈 정의에 캡슐화된 구성 메타데이터
빈을 생성하는데 필요한 모든 정보를 담고 있으며, 이 정보는 스프링에서 BeanDefinition 객체의 속성들에 저장된다.
BeanDefinition 객체의 주요 속성들
스프링 컨테이너가 빈을 생성하고 관리하기 위해 사용하는 메타데이터를 포함한다.
- 빈 클래스 이름 : 빈이 어떤 클래스의 인스턴인지를 정의한다. 스프링 컨테이너는 이 클래스 정보를 사용하여 빈의 인스턴스를 생성한다.
- 스코프 : 빈의 범위를 정의한다. singleton, prototype, request, session 등이 있다. singleton 스코프는 빈의 단일 인스턴스가 애플리케이션 컨텍스트에 존재하도록하고, prototype 스코프는 요청할 때마다 새로운 인스턴스를 생성한다.
- 생성자 인자 값 (아규먼트) : 빈이 생성될때 생성자에 전달되는 인자들을 정의한다. 이를 통해 스프링은 올바른 생성자를 호출하여 객체를 생성할 수 있다.
- 속성 값 : 생성된 빈의 속성(Property)값을 설정하는데 사용된다. 이는 setter 메서드나 필드에 값을 주입하는데 사용된다.
- 의존성 (Dependencies) : 빈이 생성될떄 필요한 다른 빈에 대한 의존성을 정의한다. 이틑 통해 스프링은 필요한 다른 빈들은 먼저 생성하고, 이 빈에 주입할 수 있다.
- 초기화 (Init) 메서드 : 빈이 생성된 후 호출될 초기화 메서드를 정의한다.
- 소멸 (Destory) 메서드 : 빈이 컨테이너에서 제거될떄 호출될 메서드를 정의한다.
▼ 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();
}
}
- 빈 클래스 이름 : MyServie
- ,스코프 : singleton (기본값)
- 생성자 아규먼트 : 없음 (기본 생성자 사용)
- 속성 값 : setDependency 메서드를 통해 주입된 dependencyBean
- 초기화 메서드 : init
- 소멸 메서드 : destory
- 의존성 : dependencyBean
이 모든 정보는 BeanDefinition 객체의 속성으로 캡슐화되며, 스프링 컨테이너는 이 정보를 사용하여 실제 빈 객체를 생성하고 관리한다.
따라서 해당 빈 정의에 캡슐화된 구성 메타데이터는 바로 이 BeanDefinition 객체의 속성들에 저장된 정보들을 의미한다. 이 정보들은 빈을 생성, 초기화, 주입, 관리하는데 필요한 모든 것을 포함하고 있다.
XML 기반 구성 메타데이터를 사용하는 경우, 인스턴스화할 객체의 타입(또는 클래스)을 <bean/> 엘리먼트의 class 속성에 지정한다. 이 class 속성 (내부적으로는 BeanDefinition 인스턴스의 class 속성)은 일반적으로 필수이다.
class 속성은 아래의 두 가지 방법 중 하나로 사용할 수 있다.
- 일반적으로 컨테이너 자체가 생성자를 반사적으로 호출하여 Bean을 직접 생성하는 경우, 생성될 Bean 클래스를 지정하는 것은 new 연산자를 사용하는 자바 코드와 다소 동일하다.
- 객체를 생성하기 위해 호출되는 정적 팩토리 메서드를 포함하는 실제 클래스를 지정하려면 컨테이너가 Bean을 생성하기 위해 클래스의 정적 팩토리 메서드를 호출하는 흔하지 않은 경우이다. 정적 팩토리 메서드 호출에서 리턴된 객체는 동일한 클래스일 수도 있고 완전히 다른 객체일 수도 있다.
반사적으로 호출이라는 의미는 자바 리플랙션(Reflection) API를 사용하여 런타임에 클래스의 생성자를 호출하는 것을 말한다. 즉 컴파일 시점이 아닌 런타임 시점에 클래스의 메타데이터를 조사하고, 이를 통해 객체를 생성하거나 메서드를 호출하는 것을 말한다.
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
- 모든 클래스 호환 : 스프링 IoC 컨테이너는 대부분의 일반적인 클래스를 관리할 수 있다. 클래스를 스프링 빈으로 등록하기 위해 특별한 인터페이스를 구현하거나 특정 코딩 패턴을 따를 필요가 없다. 즉 개발자가 작성한 대부분의 클래스는 스프링에서 사용가능하며 호환된다.
- 생성자를 통한 빈 생성 : 스프링은 클래스를 빈으로 만들때 생성자를 통해 객체를 생성할 수 있다. 이 경우, 특별한 요구사항이 없으면 기본 생성자가 필요하지 않지만, 특정 IoC 방식에서는 기본 생성자가 필요할 수 있다.
- JavaBean 선호 : 많은 스프링 사용자는 디폴트(기본) 생성자와 설정자(Setter), 접근자(Getter) 메서드를 가진 JavaBean 스타일의 클래스를 선호한다. 이는 클래스의 속성을 설정하고 가져올 수 있는 메서드가 있다는 뜻이다.
- JavaBean이 아니어도 관리 가능 : 스프링은 JavaBean 사양을 따르지 않는 클래스도 관리할 수 있다. 예를 들어 초기화 메서드나 설정자 메서드 없이 생성자만 사용하는 클래스도 관리가 가능하다.
- non-bean 스타일 클래스 : 스프링 컨테이너는 일반적인 JavaBean 스타일이 아닌 특이한 스타일의 클래스도 관리할 수 있다. 예를 들어 레거시 시스템에서 사용되는 특정 클래스를 스프링 컨테이너가 관리할 수 있다.
스프링에서는 XML 대신 Java 코드로 빈 설정을 할 수 있다. 이떄 @Configuration과 @Bean 어노테이션을 사용하여 메타 데이터를 정의한다.
이 설정은 스프링이 관리할 객체들을 정의하고, 어떻게 생성하고 초기화할지를 지정하는 방법이다.
위 내용을 통해 스프링이 얼마나 유연하게 다양한 유형의 클래스와 객체를 관리할 수 있는지 이해할 수 있다.
▼ 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 인스턴스를 생성하고 속성을 설정한다.
▼ 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 스타일이 아닌 클래스도 스프링에서 관리할 수 있으며, 이 경우 스프링은 클래스의 특성에 맞는 방법으로 빈을 생성하고 관리한다.
- 자바 기반 구성 메타데이터는 @Configuration과 @Bean 어노테이션을 사용하여 스프링 컨테이너에서 사용할 빈을 정의한다.
▼ 자바 기반 구성 메타데이터를 사용하여 위와 동일한 빈을 생성하는 예제
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 witn a Static Factory Method
XML 기반 구성 메타데이터에서 static 팩토리 메서드를 사용하여 생성하는 Bean을 정의할때 class 속성을 사용하여 static 팩토리메서드가 포함된 클래스를 지정하고, 팩토리 메서드 자체의 이름을 지정하려면 factory-method라는 속성을 사용한다. 나중에 설명하는 선택적 아규먼트를 사용하여 이 메서드를 호출하고 라이브 객체를 반환할 수 잇어야 하며, 이후 이 객체는 생성자를 통해 생성된 것처럼 처리된다. 이러한 Bean 정의의 용도 중 하나는 레거시 코드에서 정적 팩토리를 호출하는 것이다.
아래의 코드는 팩토리 메서드를 호출하여 빈을 생성할 것임을 지정한다. 정의는 리턴된 객체의 타임(클래스)를 지정하지 않고, 대신 팩토리 메서드를 포함하는 클래스를 지정한다. 이 예제에서 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;
}
}
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(); // 예시 메서드 호출
}
}
팩토리 메서드 아규먼트의 경우, 컨테이너는 동일한 이름의 여러 오버로드된 메서드 중에서 해당 메서드를 선택할 수 있다.
그러나 애매함을 피하기 위해 팩토리 메서드 시그니처는 가능한 단순하게 유지하는 것이 좋다.
팩토리 메서드 오버로딩과 관련된 일반적인 문제 사례는 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 팩토리 메서드를 통한 인스턴스화와 유사하게, 인스턴스 팩토리 메서드롤 사용한 인스턴스화는 컨터이네에서 기존 Bean의 non-static 매서드를 호출하여 새 Bean을 생성한다. 이 메커니즘을 사용하려면 클래스 속성을 비워두고 Factory-bean 속성에서 객체를 생성하기 위해 호출될 인스턴스 메서드가 포함된 현재(또는 상위/조상) 컨터이너의 Bean 이름을 지정한다. Factory-method 속성을 사용하여 팩토리 메서드 자체의 이름을 설정한다.
▼ Bean을 구성하는 방법
<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 ClientService {
void doSomething();
}
public class ClientServiceImpl implements ClientService {
@Override
public void doSomething() {
System.out.println("ClientService is doing something");
}
}
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();
}
}
▼ ApplicationContext를 초기화하고 빈을 가져오는 코드
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)를 통해 관리되고 구성될 수 있음을 보여준다.
Spring 문서에서 factory bean은 Spring 컨테이너에 구성된 빈으로, 인스턴스나 정적 팩토리 메서드를 통해 객체를 생성하는 빈을 의미한다. 대조적으로, FactoryBean은 Spring의 특정 FactoryBean 구현 클래스를 참조한다.
Determining a Bean’s Runtime Type
특정 Bean의 런타임 타입을 결정하는 것은 간단하지 않다. Bean 메타데이터 정의에서 지정된 클래스는 초기 클래스 참조일 뿐이며, 잠재적으로 선언된 팩토리 메서드와 결합되거나 FactoryBean 클래스일 경우 빈의 다른 런타임 타입으로 이어질 수 있다. 또는 인스턴스 레벨 팩토리 메서드의 경우에는, 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와 ClientServic 빈을 생성한다. clientService 빈은 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());
}
}
'스프링 프레임워크' 카테고리의 다른 글
The IoC Container [4] (0) | 2024.08.14 |
---|---|
The IoC Container (3) (0) | 2024.08.09 |
메이븐 (Maven) Build System (0) | 2024.08.08 |
The IoC Container (1) (0) | 2024.08.08 |
POJO (Plain Old Java Object) (0) | 2024.08.05 |