Spring FactoryBean Interface


FactoryBean Interface


Spring으로 DI 할 수 없는 Class의 bean을 Spring에서 관리가 가능하게 하도록 Wrapper해주는 기능이다. 예를들어 POJO형태가 아닌 singleton패턴이 구현된 Class 혹은 서드파티에서 제공하는 Class, JNDI lookup을 통해서 리턴 받아야 하는 객체, 혹은 생성과 기본 세팅과정이 복잡한 객체들은 Spring에서 직접 생성관리 하는게 불가능하다. 그래서 사용하는 것이 FactoryBean Interface이다.


FactoryBean Interface는 Spring을 대신하여 사용자가 getObject()에서 직접 인스턴스를 생성하거나 lookup 조회, 혹은 getInstance로 singleton객체를 전달받아 return 하여 준다. 이렇게 Interface를 구현하면 마치 getObject에서 리턴해주는 객체를 직접 Spring에서 정의하여 쓰는 것 처럼 사용하는 것이 가능해진다.



용도

  • Spring의 일반적인 DI패턴으로 생성하기 힘든 객체를 Spring에서 생성/관리하고자 할 때 Wrapping 기능 제공( 타 객체에 의존성 주입 등 )
  • Spring을 대신하여 사용자가 getObject를 구현하고 적절히 Instance를 생성하여 리턴하여 준다.

 

사용방법

  • FactoryBean Interface 구현체 작성하여 하위 Method 작성
    • getObject() : Instance를 생성하거나 획득하여 Spring에서 관리할 객체를 리턴하여 준다.
    • getType() : getObject()에서 리턴되는 객체의 타입을 명시하여 준다 (null이여도 상관없지만, 타입을 명시 했을 때 type기반 Autowired 가능)
    • isSingleton() : getObject()에서 리턴되는 객체는 singleton인지 true/false를 리턴한다.
  • bean definition 시 해당 factorybean Interface를 구현한 class를 참조하는 bean을 정의하고 일반 bean 처럼 사용하면 된다.
  • applicationContext.getBean("factoryBean"); 수행시 FactoryBean 인터페이스의 구현체가 아닌 FactoryBean.getObjec()에서 리턴되는 객체가 리턴된다.
  • applicationContext.getBean("&factoryBean");  수행시 FactoryBean 자체가 리턴된다. (id 앞에 &를 붙인다)

 

Spring 내부에서 사용하는 FactoryBean

  • JndiObjectFactoryBean : JNDI Lookup을 통하여 반환된 Object를 반환
  • ProxyFactoryBean : Proxy된 Object를 반환. AOP를 사용할 때 가장 많이 사용된다.
  • TransactionProxyFactoryBean : ProxyFactoryBean의 한 종류로 Transaction 처리만을 담당하는 Object를 반환
  • LocalStatelessSessionProxyFactoryBean, SimpleRemoteStatelessSessionProxyFactoryBean : Local EJB, Remote EJB를 생성하기 위하여 사용
  • JMS Resource를 반환하는 JMS 관련된 많은 FactoryBean

 


Example

 


[IMessageProvider Interface]

getMessage() 메소드만 정의하는 간단한 인터페이스

1
2
3
4
5
6
package com.mesage.provider;
 
public interface IMessageProvider {
    public String getMessage();
}
 
cs

 

[HiMessageProvider Class]

IMessageProvider를 구현하여 getMessage()에서 "Hi World"를 리턴한다.

나머지 코드는 singleton패턴 적용 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
package com.mesage.provider;
 
public class HiMessageProvider implements IMessageProvider{
 
    private static HiMessageProvider instance = new HiMessageProvider();
    
    private HiMessageProvider() {}
    
    @Override
    public String getMessage() {
        return "HI world";
    }
 
    public static IMessageProvider getInstance() {
        return instance;
    }
}
 
cs


[HelloMessageProvider Class]

IMessageProvider를 구현하여 getMessage()에서 "Hello World"를 리턴한다.

나머지 코드는 singleton패턴 적용 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
package com.mesage.provider;
 
public class HelloMessageProvider implements IMessageProvider{
 
    private static HelloMessageProvider instance = new HelloMessageProvider();
    
    private HelloMessageProvider(){}
    
    @Override
    public String getMessage() {
        return "Hello World";
    }
 
    public static IMessageProvider getInstance() {
        return instance;
    }
}
 
cs


[MessageFactoryBean Class]

FactoryBean Interface를 구현하여 getObject(), getObjectType(), isSingleton()을 구현

String type Property에 주입되는 값에 따라 getObject() 메소드에서 IMessageProvider 객체를 리턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
package com.spring.factory;
 
import org.springframework.beans.factory.FactoryBean;
 
import com.mesage.provider.HelloMessageProvider;
import com.mesage.provider.HiMessageProvider;
import com.mesage.provider.IMessageProvider;
 
public class MessageFactoryBean implements FactoryBean<IMessageProvider> {
 
    private String type;
    
    @Override
    public IMessageProvider getObject() throws Exception {
        
        if("Hi".equals(type))
            return HiMessageProvider.getInstance();
        
        else 
            return HelloMessageProvider.getInstance();
    }
 
    @Override
    public Class<IMessageProvider> getObjectType() {
        return IMessageProvider.class;
    }
 
    @Override
    public boolean isSingleton() {
        return true;
    }
 
    public void setType(String type) {
        this.type = type;
    }
}
cs


[MessageRenderrer Class]

FactoryBean Interface로 생성된 bean이 어떻게 다른 bean에 주입되는지 확인하기 위해 작성한 Renderrer Class (xml 설정파일에서 설명)

set method를 생성하여 property injection이 가능하도록 작성하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.message.render;
 
import com.mesage.provider.IMessageProvider;
 
public class MessageRederrer {
    
    private IMessageProvider hiProvider;
    private IMessageProvider helloProvider;
    
    public void printMessage() {
        System.out.println(hiProvider.getMessage());
        System.out.println(helloProvider.getMessage());
    }
 
    public IMessageProvider getHiProvider() {
        return hiProvider;
    }
 
    public void setHiProvider(IMessageProvider hiProvider) {
        this.hiProvider = hiProvider;
    }
 
    public IMessageProvider getHelloProvider() {
        return helloProvider;
    }
 
    public void setHelloProvider(IMessageProvider helloProvider) {
        this.helloProvider = helloProvider;
    }
    
}
cs





[application-context.xml]

MessageFactoryBean을 정의 type에 따라서 Hi, Hello 두 개를 정의한다. 이 때, Class를 보면 MessageFactoryBean 이다.


하지만 실제 어플리케이션 수행 시 

ApplicationContext.getBean("hiFactoryBean") / ApplicationContext.getBean("helloFactoryBean") 을 수행하면

MessageFactoryBean에서 구현한 getObject()의 리턴 값이 나오게 된다. 


즉, FactoryBean을 구현한 Class를 bean으로 정의시 FacrotyBean bean이 리턴되는 것이 아니라

FactoryBean.getObject() 의 리턴값이 리턴된다.


그렇기 때문에 hiFactoryBean, helloFactoryBean을 messageRenderrer의 property에 주입하는 것이 가능해 진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"    xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"    xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
    
    <!-- FacoryBean 정의 -->
    <bean id="hiFactoryBean" class="com.spring.factory.MessageFactoryBean">
        <property name="type">
            <value>Hi</value>
        </property>
    </bean>
    
    <!-- FacoryBean 정의 -->
    <bean id="helloFactoryBean" class="com.spring.factory.MessageFactoryBean">
        <property name="type">
            <value>Hello</value>
        </property>
    </bean>
    
    <!-- 위에 적의된 FactoryBean을 주입을 통해 사용 -->
    <bean id="messageRenderrer" class="com.message.render.MessageRederrer">
        <property name="hiProvider">
            <ref bean="hiFactoryBean"/>
        </property>
        
        <property name="helloProvider">
            <ref bean="helloFactoryBean"/>
        </property>
    </bean>
    
</beans>
 
cs




[MainApplication Class]

FactoryBean을 획득시 id를 그냥 요청하는 경우 FactoryBean.getObject()의 결과가 반환됨을 알 수 있다. (20~21 line)

FactoryBean을 획득시 id에  &붙여 요청하는 경우 FactoryBean 자체가 반환됨을 알 수 있다. (27~28 line)

MessageRenderrer의 property는 IMessageProvider 이지만, FactoryBean의 주입이 가능한 이유는 FactoryBean.getObject()의 결과가 반환되기 때문이다 (33 line)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.spring;
 
import org.springframework.context.support.GenericXmlApplicationContext;
 
import com.mesage.provider.IMessageProvider;
import com.message.render.MessageRederrer;
import com.spring.factory.MessageFactoryBean;
 
public class MainApplication {
 
    public static void main(String[] args) {
        
        GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext();
        appCtx.load("classpath:META-INF/spring/app-context.xml");
        appCtx.registerShutdownHook();
        appCtx.refresh();
        
        System.out.println("****************************************************");
        // FactoryBean.getObject에서리턴 된 객체가 넘어온다.
        IMessageProvider hiProvider = (IMessageProvider)appCtx.getBean("hiFactoryBean");
        IMessageProvider helloProvider = (IMessageProvider)appCtx.getBean("helloFactoryBean");
        
        System.out.println(hiProvider.getMessage());
        System.out.println(helloProvider.getMessage());
        
        /*//FacoryBean을 직접 가져오는 경우 id 앞에 & 를 붙여준다.
        MessageFactoryBean hiFactoryBean     = (MessageFactoryBean)appCtx.getBean("&hiFactoryBean");
        MessageFactoryBean helloFactoryBean = (MessageFactoryBean)appCtx.getBean("&helloFactoryBean");
        */
 
        System.out.println("****************************************************");
        // FacrotyBean을 직접 별도 클래스에 주입하여 사용하는 경우.
        MessageRederrer render = (MessageRederrer )appCtx.getBean("messageRenderrer");
        render.printMessage();
    }
}
cs



[MainApplication Class 와 Application Context 관계 리뷰]

사진이 잘 보려나 모르겠지만, 정리하면 대략 이런 관계 입니다.



수행결과

****************************************************

HI world

Hello World

****************************************************

HI world

Hello World


다운로드

SpringFactoryBean.zip



만약 FactoryBean을 구현해야 하는 bean 자체가 서드파티에 존재하여 내부 로직이 캡술화 되어있다면 어떻게 해야할까? (즉, Factory Pattern의 클래스는 제공되고 있는 상황이지만, Factory Pattern의 클래스를 내가 조작할 수 없어 interface를 구현할 수 없는 상태)


이런 경우는 factory-bean과 factory-method를 지정하는 방법이 있다.  보러가기


'프로그래밍 > Spring FWK' 카테고리의 다른 글

Spring MessageSource 국제화  (0) 2016.01.08
Spring CustomPropertyEditor  (2) 2016.01.05
Spring PropertyEditor  (0) 2016.01.05
Spring facoty-bean, factory-method  (0) 2015.12.30
Spring ApplicationContextAware Interface  (0) 2015.12.28
Spring BeanNameAware Interface  (1) 2015.12.28
Spring Bean Life Cycle (빈 생명주기 관리)  (0) 2015.12.28
Spring bean 상속  (0) 2015.12.27

이 글을 공유하기

댓글

Email by JB FACTORY