http://blog.csdn.net/daryl715/archive/2007/08/27/1760793.aspx
2.4. 代码剖析
下面就让我们开始进一步分析JpetStore4.0的源代码,为下面的改造铺路。
BeanAction.java是唯一一个Struts action类,位于com.ibatis.struts包下。正如上文所言,它是一个通用的控制类,利用反射机制,把控制转移到form bean的某个方法来处理。具体处理过程参考其源代码,简单明晰。
Form bean类位于com.ibatis.jpetstore.presentation包下,命名规则为***Bean。Form bean类全部继续于BaseBean类,而BaseBean类实际继续于ActionForm,因此,Form bean类就是Struts的 ActionForm,Form bean类的属性数据就由struts框架自动填充。而实际上,JpetStore4.0扩展了struts中ActionForm的应用: Form bean类还具有行为,更像一个BO,其行为(方法)由BeanAction根据配置(struts-config.xml)的URL来调用。虽然如此,我们还是把Form bean类定位于表现层。
Struts-config.xml的配置里有3种映射方式,来告诉BeanAction把控制转到哪个form bean对象的哪个方法来处理。
以这个请求连接为例http://localhost/jpetstore4/shop/vieWorder.do
1. URL Pattern
<action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
name="orderBean" scope="session"
validate="false">
<forward name="success" path="/order/ViewOrder.jsp"/>
</action>
此种方式表示,控制将被转发到"orderBean"这个form bean对象 的"viewOrder"方法(行为)来处理。方法名取"path"参数的以"/"分隔的最后一部分。
2. Method Parameter
<action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
name="orderBean" parameter="viewOrder" scope="session"
validate="false">
<forward name="success" path="/order/ViewOrder.jsp"/>
</action>
此种方式表示,控制将被转发到"orderBean"这个form bean对象的"viewOrder"方法(行为)来处理。配置中的"parameter"参数表示form bean类上的方法。"parameter"参数优先于"path"参数。
3. No Method call
<action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
name="orderBean" parameter="*" scope="session"
validate="false">
<forward name="success" path="/order/ViewOrder.jsp"/>
</action>
此种方式表示,form bean上没有任何方法被调用。假如存在"name"属性,则struts把表单参数等数据填充到form bean对象后,把控制转发到"success".否则,假如name为空,则直接转发控制到"success".
这就相当于struts内置的org.apache.struts.actions.ForwardAction的功能
<action path="/shop/viewOrder" type="org.apache.struts.actions.ForwardAction"
parameter="/order/ViewOrder.jsp " scope="session" validate="false">
</action>
Service类位于com.ibatis.jpetstore.service包下,属于业务层。这些类封装了业务以及相应的事务控制。Service类由form bean类来调用。 com.ibatis.jpetstore.persistence.iface包下的类是DAO接口,属于业务层,其屏蔽了底层的数据库操作,供具体的Service类来调用。DaoConfig类是工具类(DAO工厂类),Service类通过DaoConfig类来获得相应的DAO接口,而不用关心底层的具体数据库操作,实现了如图2中{耦合2}的解耦。 com.ibatis.jpetstore.persistence.sqlmapdao包下的类是对应DAO接口的具体实现,在JpetStore4.0中采用了ibatis来实现ORM。这些实现类继续BaseSqlMapDao类,而BaseSqlMapDao类则继续ibatis DAO 框架中的SqlMapDaoTemplate类。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目录下。这些类和配置文件位于数据层 Domain类位于com.ibatis.jpetstore.domain包下,是普通的javabean。在这里用作数据传输对象(DTO),贯穿视图层、业务层和数据层,用于在不同层之间传输数据。
剩下的部分就比较简单了,请看具体的源代码,非常清楚。
2.5. 需要改造的地方
JpetStore4.0的要害就在struts Action类和form bean类上,这也是其精华之一(虽然该实现方式是试验性,待扩充和验证),在此次改造中我们要保留下来,即控制层一点不变,表现层获取相应业务类的方式变了(要加载spring环境),其它保持不变。要非凡关注的改动是业务层和持久层,幸运的是JpetStore4.0设计非常好,需要改动的地方非常少,而且由模式可循,如下:
1. 业务层和数据层用Spring BeanFactory机制治理。
2. 业务层的事务由spring 的aop通过声明来完成。
3. 表现层(form bean)获取业务类的方法改由自定义工厂类来实现(加载spring环境)。
3. JPetStore的改造
3.1. 改造后的架构
其中红色部分是要增加的部分,蓝色部分是要修改的部分。下面就让我们逐一剖析。
3.2. Spring Context的加载
为了在Struts中加载Spring Context,一般会在struts-config.xml的最后添加如下部分:
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml" />
</plug-in>
Spring在设计时就充分考虑到了与Struts的协同工作,通过内置的Struts Plug-in在两者之间提供了良好的结合点。但是,因为在这里我们一点也不改动JPetStore的控制层(这是JpetStore4.0的精华之一),所以本文不预备采用此方式来加载ApplicationContext.我们利用的是spring framework 的BeanFactory机制,采用自定义的工具类(bean工厂类)来加载spring的配置文件,从中可以看出Spring有多灵活,它提供了各种不同的方式来使用其不同的部分/层次,您只需要用你想用的,不需要的部分可以不用。
具体的来说,就是在com.ibatis.spring包下创建CustomBeanFactory类,spring的配置文件applicationContext.xml也放在这个目录下。以下就是该类的全部代码,很简单:
public final class CustomBeanFactory {
static XmlBeanFactory factory = null;
static {
Resource is = new
InputStreamResource( CustomBeanFactory.class.getResourceAsStream("applicationContext.xml"));
factory = new XmlBeanFactory(is);
}
public static Object getBean(String beanName){
return factory.getBean(beanName);
}
}
实际上就是封装了Spring 的XMLBeanFactory而已,并且Spring的配置文件只需要加载一次,以后就可以直接用CustomBeanFactory.getBean("someBean")来获得需要的对象了(例如someBean),而不需要知道具体的类。CustomBeanFactory类用于{耦合1}的解耦。
CustomBeanFactory类在本文中只用于表现层的form bean对象获得service类的对象,因为我们没有把form bean对象配置在applicationContext.xml中。但是,为什么不把表现层的form bean类也配置起来呢,这样就用不着这CustomBeanFactory个类了,Spring会帮助我们创建需要的一切?问题的答案就在于form bean类是struts的ActionForm类!假如大家熟悉struts,就会知道ActionForm类是struts自动创建的:在一次请求中,struts判定,假如ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中。因此formbean类的对象就不能由spring来创建,但是service类以及数据层的DAO类可以,所以只有他们在spring中配置。
所以,很自然的,我们就创建了CustomBeanFactory类,在表现层来衔接struts和spring.就这么简单,实现了另一种方式的{耦合一}的解耦。
3.3. 表现层
上面分析到,struts和spring是在表现层衔接起来的,那么表现层就要做稍微的更改,即所需要的service类的对象创建上。以表现层的AccountBean类为例:
原来的源代码如下
private static final AccountService accountService = AccountService.getInstance();
private static final CatalogService catalogService = CatalogService.getInstance();
改造后的源代码如下
private static final AccountService accountService = (AccountService)CustomBeanFactory.getBean("AccountService");
private static final CatalogService catalogService = (CatalogService)CustomBeanFactory.getBean("CatalogService");
其他的几个presentation类以同样方式改造。这样,表现层就完成了。关于表现层的其它部分如JSP等一概不动。也许您会说,没有看出什么非凡之处的好处啊?你还是额外实现了一个工厂类。别着急,帷幕刚刚开启,spring是在表现层引入,但您发没发现:
presentation类仅仅面向service类的接口编程,具体"AccountService"是哪个实现类,presentation类不知道,是在spring的配置文件里配置。(本例中,为了最大限度的保持原来的代码不作变化,没有抽象出接口)。Spring鼓励面向接口编程,因为是如此的方便和自然,当然您也可以不这么做。 CustomBeanFactory这个工厂类为什么会如此简单,因为其直接使用了Spring的BeanFactory。Spring从其核心而言,是一个DI容器,其设计哲学是提供一种无侵入式的高扩展性的框架。为了实现这个目标,Spring 大量引入了Java 的Reflection机制,通过动态调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件BeanFactory,以此作为其依靠注入机制的实现基础。org.springframework.beans包中包括了这些核心组件的实现类,核心中的核心为BeanWrapper和BeanFactory类。
3.4. 持久层
在讨论业务层之前,我们先看一下持久层,如下图所示:
在上文中,我们把iface包下的DAO接口归为业务层,在这里不需要做修改。ibatis的sql配置文件也不需要改。要改的是DAO实现类,并在spring的配置文件中配置起来。
1、修改基类
所有的DAO实现类都继续于BaseSqlMapDao类。修改BaseSqlMapDao类如下:
public class BaseSqlMapDao extends SqlMapClientDaoSupport {
protected static final int PAGE_SIZE = 4;
protected SqlMapClientTemplate smcTemplate = this.getSqlMapClientTemplate();
public BaseSqlMapDao() {
}
}
使BaseSqlMapDao类改为继续于Spring提供的SqlMapClientDaoSupport类,并定义了一个保护属性smcTemplate,其类型为SqlMapClientTemplate。关于SqlMapClientTemplate类的具体说明请参照附录中的"Spring中文参考手册"
2、修改DAO实现类
所有的DAO实现类还是继续于BaseSqlMapDao类,实现相应的DAO接口,但其相应的DAO操作委托SqlMapClientTemplate来执行,以AccountSqlMapDao类为例,部分代码如下:
public List getUsernameList() {
return smcTemplate.queryForList("getUsernameList", null);
}
public Account getAccount(String username, String password) {
Account account = new Account();
account.setUsername(username);
account.setPassword(password);
return (Account) smcTemplate.queryForObject("getAccountByUsernameAndPassword", account);
}
public void insertAccount(Account account) {
smcTemplate.update("insertAccount", account);
smcTemplate.update("insertProfile", account);
smcTemplate.update("insertSignon", account);
}
就这么简单,所有函数的签名都是一样的,只需要查找替换就可以了!
3、除去工厂类以及相应的配置文件
除去DaoConfig.java这个DAO工厂类和相应的配置文件dao.xml,因为DAO的获取现在要用spring来治理。
4、DAO在Spring中的配置(applicationContext.xml)
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:hsql://localhost/xdb</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
<!-- ibatis sqlMapClient config -->
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>
classpath:comibatisjpetstorepersistencesqlmapdaosqlsql-map-config.xml
</value>
</property>
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
<!-- Transactions -->
<bean id="TransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
<!-- persistence layer -->
<bean id="AccountDao"
class="com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao">
<property name="sqlMapClient">
<ref local="sqlMapClient"/>
</property>
</bean>
具体的语法请参照附录中的"Spring中文参考手册".在这里只简单解释一下:
1. 我们首先创建一个数据源dataSource,在这里配置的是hsqldb数据库。假如是ORACLE数据库,driverClassName的值是"oracle.jdbc.driver.OracleDriver",URL的值类似于"jdbc:oracle:thin:@wugfMobile:1521:cdcf".数据源现在由spring来治理,那么现在我们就可以去掉properties目录下database.properties这个配置文件了;还有不要忘记修改sql-map-config.xml,去掉
对它的引用。
2. sqlMapClient节点。这个是针对ibatis SqlMap的SqlMapClientFactoryBean配置。实际上配置了一个sqlMapClient的创建工厂类。configLocation属性配置了ibatis映射文件的名称。dataSource属性指向了使用的数据源,这样所有使用sqlMapClient的DAO都默认使用了该数据源,除非在DAO的配置中另外显式指定。
3. TransactionManager节点。定义了事务,使用的是DataSourceTransactionManager.
4. 下面就可以定义DAO节点了,如AccountDao,它的实现类是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao,使用的SQL配置从sqlMapClient中读取,数据库连接没有非凡列出,那么就是默认使用sqlMapClient配置的数据源datasource.
这样,我们就把持久层改造完了,其他的DAO配置类似于AccountDao.怎么样?简单吧。这次有接口了:) AccountDao接口->AccountSqlMapDao实现。
3.5. 业务层
业务层的位置以及相关类,如下图所示:
在这个例子中只有3个业务类,我们以OrderService类为例来改造,这个类是最复杂的,其中涉及了事务。
1、在ApplicationContext配置文件中增加bean的配置:
<bean id="OrderService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="TransactionManager"></ref>
</property>
<property name="target">
<bean class="com.ibatis.jpetstore.service.OrderService">
<property name="itemDao">
<ref bean="ItemDao"/>
</property>
<property name="orderDao">
<ref bean="OrderDao"/>
</property>
<property name="sequenceDao">
<ref bean="SequenceDao"/>
</property>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
定义了一个OrderService,还是很轻易懂的。为了简单起见,使用了嵌套bean,其实现类是com.ibatis.jpetstore.service.OrderService,分别引用了ItemDao,OrderDao,SequenceDao。该bean的insert*实现了事务治理(AOP方式)。TransactionProxyFactoryBean自动创建一个事务advisor, 该advisor包括一个基于事务属性的pointcut,因此只有事务性的方法被拦截。
2、业务类的修改
以OrderService为例:
public class OrderService {
/* Private Fields */
private ItemDao itemDao;
private OrderDao orderDao;
private SequenceDao sequenceDao;
/* Constructors */
public OrderService() {
}
/**
* @param itemDao 要设置的 itemDao。
*/
public final void setItemDao(ItemDao itemDao) {
this.itemDao = itemDao;
}
/**
* @param orderDao 要设置的 orderDao。
*/
public final void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
/**
* @param sequenceDao 要设置的 sequenceDao。
*/
public final void setSequenceDao(SequenceDao sequenceDao) {
this.sequenceDao = sequenceDao;
}
//剩下的部分
…….
}
红色部分为修改部分。Spring采用的是Type2的设置依靠注入,所以我们只需要定义属性和相应的设值函数就可以了,ItemDao,OrderDao,SequenceDao的值由spring在运行期间注入。构造函数就可以为空了,另外也不需要自己编写代码处理事务了(事务在配置中声明),daoManager.startTransaction();等与事务相关的语句也可以去掉了。和原来的代码比较一下,是不是处理精简了很多!可以更关注业务的实现。
本篇文章来自<A href='http://www.soidc.net'>IDC专家网</a> 原文链接:http://www.soidc.net/articles/1213781061058/20080105/1215945430293_1.html