[spring]從頭開始-FactoryBeans

開發工具:IntelliJ IDEA 13

開發環境:jdk1.6.0_45

Framework:spring 3

FactoryBean 一般都是運用在無法使用 spring 的 inject 來產生 instance 的狀況(無法使用 new 來產生實體的對象)。直接來看一下範例:

MessageDigestFactoryBean.java

package foo.bar;

import org.springframework.beans.factory.FactoryBean;

import org.springframework.beans.factory.InitializingBean;

import java.security.MessageDigest;

/**

 * Created by Hsu on 2014/7/15.

 */

public class MessageDigestFactoryBean implements FactoryBean<MessageDigest>,InitializingBean {

    private String algorithmName = "MD5";

    // 於 java.security 關於安全性的編碼類別

    private MessageDigest messageDigest = null;

    @Override

    public MessageDigest getObject() throws Exception {

        return this.messageDigest;

    }

    @Override

    public Class<MessageDigest> getObjectType() {

        return MessageDigest.class;

    }

    @Override

    public boolean isSingleton() {

        return true;

    }

    @Override

    public void afterPropertiesSet() throws Exception {

        messageDigest = MessageDigest.getInstance(algorithmName);

    }

    public void setAlgorithmName(String algorithmName) {

        this.algorithmName = algorithmName;

    }

}

可以看到上述的類別使用 InitializingBean來做初始化bean的動作,並且implement FactoryBean<T>這個類別,FactoryBean 簡單說就是一個用來幫我們產生實體用的工廠,其中需要複寫三個方法,getObject 返回<T>的實體(由FactoryBean產生,即為範例中的afterPropertiesSet的 MessageDigest.getInstance(algorithmName) 產生)getObjectType 返回 <T>的型別,isSingleton 告知spring 該物件是否為 Singleton 實例。

接著我們新建一個bean

MessageDigester.java

package foo.bar;

import java.security.MessageDigest;

/**

 * Created by Hsu on 2014/7/15.

 */

public class MessageDigester {

    private MessageDigest digest1 = null;

    private MessageDigest digest2 = null;

    public void setDigest1(MessageDigest digest1) {

        this.digest1 = digest1;

    }

    public void setDigest2(MessageDigest digest2) {

        this.digest2 = digest2;

    }

    public void digest(String msg) {

        System.out.println("Using digest1");

        digest(msg, digest1);

        System.out.println("Using digest2");

        digest(msg, digest2);

    }

    private void digest(String msg, MessageDigest digest) {

        //取得編碼規則

        System.out.println("Using alogrithm: " + digest.getAlgorithm());

        //重置

        digest.reset();

        byte[] bytes = msg.getBytes();

        //編碼

        byte[] out = digest.digest(bytes);

        System.out.println(out);

    }

}

單純在專案中加入這個類別,應該還看不出個所以然,我們再把 spring configuration file 加上去:

spring-config.xml

..

..標頭的部分省略

    <bean id="shaDigest" class="foo.bar.MessageDigestFactoryBean">

        <property name="algorithmName">

            <value>SHA1</value>

        </property>

    </bean>

    <bean id="defaultDigest" class="foo.bar.MessageDigestFactoryBean"/>

    <bean id="digester" class="foo.bar.MessageDigester">

        <property name="digest1">

            <ref local="shaDigest"/>

        </property>

        <property name="digest2">

            <ref local="defaultDigest"/>

        </property>

    </bean>

..結尾省略

這邊先將 bean shaDigest Inject 編碼方式為 SHA1,另一個 digester 則使用系統預設 MD5,接下來我們建立一個演繹的 MessageDigestExample類別;

MessageDigestExample.java

package foo.bar;

import org.springframework.context.support.GenericXmlApplicationContext;

/**

 * Created by Hsu on 2014/7/15.

 */

public class MessageDigestExample {

    public static void main(String[] args) {

        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();

        ctx.load("classpath:spring-config.xml");

        ctx.refresh();

        MessageDigester digester = (MessageDigester) ctx.getBean("digester");

        digester.digest("Hello World!");

    }

}

結合起上面的程式,我們可以看到我們藉由 MessageDigestFactoryBean 來產生我們所要的編碼實體,運行之後所得到的結果為:

Using digest1

Using alogrithm: SHA1

[B@33682598

Using digest2

Using alogrithm: MD5

[B@7a7c3885

整個程式的流程可以解釋為,腦中先構想什麼類別(MessageDigest)要由 FactoryBean 來產生,依照什麼樣的條件(MessageDigester)產生什麼樣的實例,後續要怎麼樣使用(MessageDigestExample),比如說有哪些票卷(實例),如何產生(factory),票卷要怎麼用(商業邏輯)...這樣的思考流程。

ps.from Pro Spring 3

FactoryBeans are the perfect solution when you are working with classes that cannot be created

using the newoperator. If you work with objects that are created using a factory method and you want to

use these classes in a Spring application, then create a FactoryBeanto act as an adaptor, allowing your

classes to take full advantage of Spring’s IoC capabilities.

直接使用 FactoryBean?當然可以,就像買家具一樣,你當然也可以選擇直接去工廠買啊~

直接來看看範例:

AccessingFactoryBeans.java

package foo.bar;

import org.springframework.context.support.GenericXmlApplicationContext;

import java.security.MessageDigest;

/**

 * Created by Hsu on 2014/7/15.

 */

public class AccessingFactoryBeans {

    public static void main(String[] args) {

        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();

        ctx.load("classpath:spring-config.xml");

        ctx.refresh();

        // 對照組

        //MessageDigest digest = (MessageDigest) ctx.getBean("shaDigest");

        // 要取得 factoryBean 實體,需在 beanName錢加上 & 運算子

        // 否則將發生 Exception in thread "main" java.lang.ClassCastException:

        // java.security.MessageDigest$Delegate cannot be cast to foo.bar.MessageDigestFactoryBean

        MessageDigestFactoryBean factoryBean =

                (MessageDigestFactoryBean) ctx.getBean("&shaDigest");

        try {

            MessageDigest shaDigest = factoryBean.getObject();

            System.out.println(shaDigest.digest("Hello world".getBytes()));

        } catch (Exception ex) {

            ex.printStackTrace();

        }

    }

}

配置檔同上一個範例,這邊就不再贅述。

運行的結果為:

[B@7162e295

一樣成功得到我們們所想要的結果。

我們的第一個例子是我們知道如何產生bean實體的時候所用的方法,但如果是第三方的class,我們已經知道產生他的實體Method之後,我們有更快速的方式可以用spring來完成相同的效果,在<bean>中使用 factory-bean 及 factory-method,立馬來看看範例:

MessageDigestFactory.java

package foo.bar;

import java.security.MessageDigest;

/**

 * Created by Hsu on 2014/7/15.

 */

public class MessageDigestFactory {

    private String algorithmName = "MD5";

    public MessageDigest createInstance() throws Exception {

        return MessageDigest.getInstance(algorithmName);

    }

    public void setAlgorithmName(String algorithmName) {

        this.algorithmName = algorithmName;

    }

}

再加上spring configuration file

factoryMethod.xml

..

..標頭的部分省略

    <bean id="shaDigestFactory"

          class="foo.bar.MessageDigestFactory">

        <property name="algorithmName">

            <value>SHA1</value>

        </property>

    </bean>

    <bean id="defaultDigestFactory"

          class="foo.bar.MessageDigestFactory"/>

    <bean id="shaDigest"

          factory-bean="shaDigestFactory"

          factory-method="createInstance">

    </bean>

    <bean id="defaultDigest"

          factory-bean="defaultDigestFactory"

          factory-method="createInstance"/>

    <bean id="digester"

          class="foo.bar.MessageDigester">

        <property name="digest1">

            <ref local="shaDigest"/>

        </property>

        <property name="digest2">

            <ref local="defaultDigest"/>

        </property>

    </bean>

..結尾省略

可以看到之先前不同的是,多了個指定 factry-bean / factory-method,直接指定產生實例的bean及Method,不需要再像 MessageDigestFactoryBean 做些多餘的繁瑣過程,接下來在來產生一個演繹類別 MessageDigestFactoryExample.java

package foo.bar;

import org.springframework.context.support.GenericXmlApplicationContext;

/**

 * Created by Hsu on 2014/7/15.

 */

public class MessageDigestFactoryExample {

    public static void main(String[] args) {

        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();

        ctx.load("classpath:factoryMethod.xml");

        ctx.refresh();

        MessageDigester digester = (MessageDigester) ctx.getBean("digester");

        digester.digest("Hello World!");

    }

}

上面運行的結果基本上與第一個範例相同:

Using digest1

Using alogrithm: SHA1

[B@290fd7f6

Using digest2

Using alogrithm: MD5

[B@4f2b6c89

要使用哪一種,就視情況而定囉!!!

demo程式(右鍵另開視窗下載)