首页 > 代码库 > Spring通过工厂创建实例的注意事项

Spring通过工厂创建实例的注意事项

如果第三方(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;
		}
	}

}

介绍如上工厂类的特殊性:

  1. 所有的方法属性都是静态的。
  2. setEnv() 是静态方法:内部是往静态属性props设值。
  3. 两个工厂方法,是静态空参方法(这是符合工厂方法定义的)。内部的实现是根据已经设置好的props来实例化对象,如果参数(env)没有设值,那么实例化对象就会失败。所以我们就需要先setEnv,在通过工厂方法实例化对象。

一:有人会说,这个很简单,根据spring的官方文档,用“静态工厂方法”进行实例化。但是此方法不可取,如下所述:

	<bean id="internalClient" class="x.y.ClientFactory"
		factory-method="getIntenalCmdClient" destroy-method="destroy">
		<property name="env" value=http://www.mamicode.com/"${env}" />>
如上配置,实例化internalClient会失败!原因:
  1. spring虽然会调用setEnv()方法。但是并不是调用x.y.ClientFactory类里面的此方法,而是调用internalClient实例里面的setEnv()方法。所以此<property>的配置是无效的。
  2. 另外工厂方法getIntenalCmdClient会被首先调用(可以debug断点),然后spring再调用setEnv方法。因此在调用getIntenalCmdClient时候没有任何env参数被传入。
  3. 以上两条都会导致实例化失败!


二:会有人说既然“静态工厂化方法”不可取,那么spring官方文档里面还提供了“实例工厂方法”。但是此方法也是不可取的,如下所述:

	<bean id="clientFactory"
		class="x.y.ClientFactory">
		<property name="env" value=http://www.mamicode.com/"${env}" />>


如上配置,实例化internalClient会失败!原因:
  1. 实例化clientFactory,其目的是为了让spring调用setEnv方法,往x.y.ClientFactory类里面设值(静态属性env设值),因为设置了factory-bean属性,所以clientFactory的实例化顺序会先于internalClient。到目前没有什么问题,但是问题出现在第二点
  2. 工厂方法gentIntenalCmdClient是静态方法,设置了factory-bean属性的含义就是clientFactory.getIntenalCmdClient如此调用,在spring里面不支持对象引用静态方法!因此是报错的。
  3. 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.

综上两种情况,我想出了解决方法。
  1. 实例化clientFactory bean还是需要的,因为必须要保证env可以最先set值(静态方法setEnv()可以最先被调用)。
  2. 实例化internalClient bean使用"静态工厂方法",如此就可以成功的调用静态工厂方法(ClientFactory.getIntenalCmdClient())
  3. 为保证先setEnv,再调用工厂方法(ClientFactory.getIntenalCmdClient()),可以使用depends-on属性
  4. 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=http://www.mamicode.com/"${env}" />>
如有不对的or更好的解决方案,望大哥赐教!


BTW:


   实例工厂和静态工厂一样的相同限制:
  1. 静态工厂方法上不能有参数,也不能在Spring种定义静态工厂方法的参数。
  2. 静态工厂方法只能是public的,不能是private或protected的。
  3. 静态工厂方法不能和构造函数注入一起使用。

  

    实例工厂和静态工厂不同的点:
  •  实例工厂方法不能是静态的,而静态工厂方法必须是静态的。