2023. 3. 15. 22:34ㆍSpring/Boot
접하게 된 계기
강의를 듣던 중 강사님께서 DB에서 값을 받아오기 위한 빈을 FactoryBean을 구현해서 만드는 것을 보게 되었다. 평소에 @Component나 @Configuration, @Bean을 통해서 빈을 생성했는데 이렇게 만드는 방법도 있다는 것을 보게 되었다.
스프링의 빈 등록
스프링은 지정된 클래스 이름을 가지고 Reflection API를 이용해서 빈을 생성한다. 내부적으로 빈 정의에 나오는 클래스 이름을 가지고 빈을 생성한다.
문제
스프링에서 DI할 수 없는 클래스의 빈이 있을 수 있다. 예를 들어 클래스 정보를 미리 알아낼 수 없는 경우이다. Java에서는 Dynamic Proxy로 만드는 객체의 경우 클래스가 무엇인지 미리 알 수 없다. 클래스 자체도 내부적으로 새로 정의해서 사용하기 때문이다. 다른 예시로는 정적 팩토리 클래스 로접근해야 하는 객체처럼 new 연산자로 생성할 수 없는 객체, Third-Party에서 제공하는 클래스, 생성과 기본 세팅이 복잡한 객체들 등이 있으며 이런 경우 스프링에서 직접 생성 및 관리하는 것이 불가능하다. 이를 위해 스프링에서는 FactoryBean이라는 어댑터 클래스를 제공한다.
FactoryBean
FactoryBean은 인터페이스이다. 스프링에서는 FactoryBean을 구현한 클래스들을 아래 메서드를 통해 빈으로 등록한다.
public interface FactoryBean {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
- getObject() - 팩토리에 의해 만들어진 객체를 반환한다. 이 객체는 스프링 컨테이너에 의해 사용된다. 즉 반환되는 객체는 빈으로 등록되며 싱글톤으로 관리된다.
- getObjectType() - 이 FactoryBean이 만든 객체의 타입을 반환한다.
- isSingleton() - 이 FactoryBean이 싱글톤인지를 의미한다. true를 반환하면 FactoryBean이 싱글톤임을 설정하는 것이며 FactoryBean이 반환하는 객체가 싱글턴이라는 뜻이 아니므로 주의하자.
Test
이를 테스트로 확인해보자.
먼저 빈으로 사용하고 싶은 클래스를 정의한다.
public class Message {
String text;
private Message(String text) {
this.text = text;
}
public static Message newMessage(String text) {
return new Message(text);
}
public String getText() {
return text;
}
}
그 다음 이 클래스를 생성하는 역할을 하는 MessageFactoryBean을 정의한다. 이 클래스가 FactoryBean을 구현한다.
@Component
public class MessageFactoryBean implements FactoryBean<Message> {
@Override
public Message getObject() throws Exception {
return Message.newMessage("hello");
}
@Override
public Class<?> getObjectType() {
return Message.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
코드를 보면 알 수 있듯이 MessageFactoryBean은 빈으로 등록되며 이 빈을 조회하게 되면 Message 객체가 반환된다.
@Autowired
ApplicationContext applicationContext;
@Test
void factoryBean() {
Message m = applicationContext.getBean("messageFactoryBean", Message.class);
for (String s : applicationContext.getBeanDefinitionNames()) {
System.out.println(s);
}
Assertions.assertThat(m.getText()).isEqualTo("hello");
}
실행 결과는 다음과 같다.
@Component의 name 값에 message를 줘서 FactoryBean의 이름까지 message로 변경할 수 있다.
@Component("message")
public class MessageFactoryBean implements FactoryBean<Message> {
// ...
}
@Autowired
ApplicationContext applicationContext;
@Test
void factoryBean() {
Message m = applicationContext.getBean("message", Message.class);
for (String s : applicationContext.getBeanDefinitionNames()) {
System.out.println(s);
}
Assertions.assertThat(m.getText()).isEqualTo("hello");
}
만약 FactoryBean 자체를 조회하고 싶다면 빈 이름 앞에 '&'를 붙여주면 된다.
@Autowired
ApplicationContext applicationContext;
@Test
void getFactoryBean() {
MessageFactoryBean mfb = applicationContext.getBean("&message", MessageFactoryBean.class);
for (String s : applicationContext.getBeanDefinitionNames()) {
System.out.println(s);
}
Assertions.assertThat(Objects.requireNonNull(mfb.getObject()).getText()).isEqualTo("hello");
}
'Spring > Boot' 카테고리의 다른 글
[Spring Boot] 자동 구성에 의한 H2 Default URL 변경 (0) | 2023.03.19 |
---|---|
[Spring Boot] @RequestParam vs @RequestPart (2) | 2022.03.08 |
[Spring Boot] Spring OAuth 없이 Spring Boot로 Github OAuth 사용하기[2/2] (0) | 2021.11.22 |
[Spring Boot] Spring OAuth 없이 Spring Boot로 Github OAuth 사용하기[1/2] (0) | 2021.11.22 |
2. IOC와 DI (0) | 2021.11.22 |