Spring通過工廠建立例項的注意事項
阿新 • • 發佈:2019-01-31
如果第三方(or別的team)提供一個工廠類(此類是不可以修改的,往往以jar包形式提供的),需要供給我們專案來使用。
但是我們自己的專案使用了spring來配置,所以我們當然希望能夠通過spring的aop來配置這個工廠類來,來建立例項以進行引用。
但是這個工廠類的原始碼比較特殊。如下:
package x.y; import java.util.Properties; import x.y.client.CmdClient; import x.y.client.InternalCmdClient; public class ClientFactory { private static volatile CmdClient cmdclient; private static volatile InternalCmdClient cmdinternalclient; private static Properties props = new Properties(); public static void setEnv(String env){ props.put( "env", env ); } public static CmdClient getCmdClient() throws Exception{ if ( cmdclient == null ) { synchronized ( CmdClientImpl.class ) { if ( cmdclient == null ) { cmdclient = new CmdClientImpl( props ); } } } return cmdclient; } public static InternalCmdClient getIntenalCmdClient() throws Exception{ if ( cmdinternalclient == null ) { synchronized ( InternalCmdClientImpl.class ) { if ( cmdinternalclient == null ) { cmdinternalclient = new InternalCmdClientImpl( props ); } } } return cmdinternalclient; } public static void destoryClient(){ if ( cmdclient != null ) { cmdclient.destroy(); cmdclient = null; } if ( cmdinternalclient != null ) { cmdinternalclient.destroy(); cmdinternalclient = null; } } }
介紹如上工廠類的特殊性:
- 所有的方法屬性都是靜態的。
- setEnv() 是靜態方法:內部是往靜態屬性props設值。
- 兩個工廠方法,是靜態空參方法(這是符合工廠方法定義的)。內部的實現是根據已經設定好的props來例項化物件,如果引數(env)沒有設值,那麼例項化物件就會失敗。所以我們就需要先setEnv,在通過工廠方法例項化物件。
<bean id="internalClient" class="x.y.ClientFactory" factory-method="getIntenalCmdClient" destroy-method="destroy"> <property name="env" value="${env}" /> </bean>
如上配置,例項化internalClient會失敗!原因:
- spring雖然會呼叫setEnv()方法。但是並不是呼叫x.y.ClientFactory類裡面的此方法,而是呼叫internalClient例項裡面的setEnv()方法。所以此<property>的配置是無效的。
- 另外工廠方法getIntenalCmdClient會被首先呼叫(可以debug斷點),然後spring再呼叫setEnv方法。因此在呼叫getIntenalCmdClient時候沒有任何env引數被傳入。
- 以上兩條都會導致例項化失敗!
二:會有人說既然“靜態工廠化方法”不可取,那麼spring官方文件裡面還提供了“例項工廠方法
<bean id="clientFactory"
class="x.y.ClientFactory">
<property name="env" value="${env}" />
</bean>
<bean id="internalClient" factory-bean="clientFactory"
factory-method="getIntenalCmdClient" destroy-method="destroy">
</bean>
如上配置,例項化internalClient會失敗!原因:
- 例項化clientFactory,其目的是為了讓spring呼叫setEnv方法,往x.y.ClientFactory類裡面設值(靜態屬性env設值),因為設定了factory-bean屬性,所以clientFactory的例項化順序會先於internalClient。到目前沒有什麼問題,但是問題出現在第二點
- 工廠方法gentIntenalCmdClient是靜態方法,設定了factory-bean屬性的含義就是clientFactory.getIntenalCmdClient如此呼叫,在spring裡面不支援物件引用靜態方法!因此是報錯的。
- spring報錯log:“No matching factory method found: factory bean 'clientFactory'; factory method 'getIntenalCmdClient()'. Check that a method with the specified name exists and that it is non-static.”
- 例項化clientFactory bean還是需要的,因為必須要保證env可以最先set值(靜態方法setEnv()可以最先被呼叫)。
- 例項化internalClient bean使用"靜態工廠方法",如此就可以成功的呼叫靜態工廠方法(ClientFactory.getIntenalCmdClient())
- 為保證先setEnv,再呼叫工廠方法(ClientFactory.getIntenalCmdClient()),可以使用depends-on屬性
- destroy-method所設值的方法,是internalClient例項所能引用到的方法。
<bean id="internalClient" class="x.y.AhClientFactory"
factory-method="getIntenalCmdClient" destroy-method="destroy"
depends-on="clientFactory">
</bean>
<bean id="clientFactory" class="x.y.ClientFactory">
<property name="env" value="${env}" />
</bean>
如有不對的or更好的解決方案,望大哥賜教!
BTW:
例項工廠和靜態工廠一樣的相同限制:- 靜態工廠方法上不能有引數,也不能在Spring種定義靜態工廠方法的引數。
- 靜態工廠方法只能是public的,不能是private或protected的。
- 靜態工廠方法不能和建構函式注入一起使用。
- 例項工廠方法不能是靜態的,而靜態工廠方法必須是靜態的。