在对spring和ibatis进行整合时,大家都会用到如下的配置以在ibatis中使用spring提供的事务处理功能。
<beans>
......
<!-- 配置相关数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<! -- 此处省略数据库属性配置-->
</bean>
<!-- Transaction manager for a single JDBC DataSource 事务管理的定义-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- Spring提供的iBatis的SqlMap配置-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="WEB-INF/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- DAO定义-->
<bean id="productDao"class="com.specl.api.dao.release.ShopDaoRelease">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
...
</beans>
在上面的配置片段中可以,类ShopDaoRelease是我们具体的一个dao实现,其继承了SqlMapClientDaoSupport,后者暴露出一个sqlMapClient属性,用于接受Spring的注射。SqlMapClientDaoSupport会对其中封装的SqlMapClientTemplate做相应的设置,所以DAO子类便可在取用SqlMapClientTemplate时正常地工作了。
但是我们可以通过查看源码发现在SqlMapClientDaoSupport类中,关于setSqlMapClient方法的定义:
/**
* Set the iBATIS Database Layer SqlMapClient to work with.
* Either this or a "sqlMapClientTemplate" is required.
* @see #setSqlMapClientTemplate
*/
public final void setSqlMapClient(SqlMapClient sqlMapClient) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
}
}
而我们的配置文件中名字为“sqlMapClient”的bean的类型却为org.springframework.orm.ibatis.SqlMapClientFactoryBean,而非为com.ibatis.sqlmap.client.SqlMapClient。这不免让人产生疑问,怎么能把org.springframework.orm.ibatis.SqlMapClientFactoryBean类型的实例注入给com.ibatis.sqlmap.client.SqlMapClient类型的属性呢?
这是因为Spring的机制的缘故。简单的说,如果一个bean实现了 FactoryBean接口,那么Spring就不会把该bean本身实例化并返回,而是返回该bean的getObject()返回的对象。这是Sprign的游戏规则。我们来看一眼 SqlMapClientFactoryBean的源码片段:
public class SqlMapClientFactoryBean implements FactoryBean,InitializingBean { private SqlMapClient sqlMapClient; protected SqlMapClient buildSqlMapClient(Resource configLocation,Properties properties) throws IOException { InputStream is = configLocation.getInputStream(); if (properties != null) { if (buildSqlMapClientWithInputStreamAndPropertiesMethodAvailable) { return SqlMapClientBuilder.buildSqlMapClient(is,properties); } else { return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is), properties); } } else { if (buildSqlMapClientWithInputStreamMethodAvailable) { return SqlMapClientBuilder.buildSqlMapClient(is); } else { return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is)); } } } //这里就是返回的、并会被注入到其它类里的对象 public Object getObject() { return this.sqlMapClient; } }
Spring这种机制的官方说明:
public interface FactoryBean Interface to be implemented by objects used within a BeanFactory which are themselves factories. If a bean implements this interface, it is used as a factory for an object to expose, not directly as a bean instance that will be exposed itself. NB: A bean that implements this interface cannot be used as a normal bean. A FactoryBean is defined in a bean style, but the object exposed for bean references (getObject() is always the object that it creates. FactoryBeans can support singletons and prototypes, and can either create objects lazily on demand or eagerly on startup. The SmartFactoryBean interface allows for exposing more fine-grained behavioral metadata. This interface is heavily used within the framework itself, for example for the AOP ProxyFactoryBean or the JndiObjectFactoryBean. It can be used for application components as well; however, this is not common outside of infrastructure code. NOTE: FactoryBean objects participate in the containing BeanFactory's synchronization of bean creation. There is usually no need for internal synchronization other than for purposes of lazy initialization within the FactoryBean itself (or the like). Since: 08.03.2003 Author: Rod Johnson, Juergen Hoeller See Also: BeanFactory, ProxyFactoryBean, JndiObjectFactoryBean