大多数程序员在进入一个规模稍大点的公司时,都会花一定时间熟悉公司的框架(也不是所有的公司都能开发出自己的框架,比如我们公司,就是把大多数主流的框做了一下优化和整合而已),当然我也不例外。公司是做移动支付开发的,使用的是自己的一套(整合)框架:struts+spring+abitis,log4j做调试,Ant做编译,CVS做版本控制。使用的IDE是 eclipse 3.2+MyEcilpse 5.1。整个框架struts做cs(表现层),使用spring的IOC功能和声明式事务,abitis做持久化。摒弃hibernate而选择 abatis的原因,我认为是看中了abatis的简洁、灵活且功能强大的原因吧。
public class BaseAction extends DispatchAction {
/** Log4j 日志 */
protected static Logger logger = Logger.getLogger(BaseAction.class);
/** Spring的上下文对象 */
ApplicationContext context;
protected synchronized ApplicationContext getContext(){
return SpringUtils.getContext();
}
}
这样一来,我们所有的action都要继承BaseAction:
public class LoginAction extends BaseAction {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
UserForm userForm = (UserForm) form;
UserVo userVo = new UserVo();
userVo.setUserName(userForm.getUserName());
userVo.setUserPass(userForm.getUserPass());
UserService userService = (UserService) getContext().getBean ("userService");
logger.debug(">> UserService " + userService);
...
不使用Action而改用 DispatchAction,这里谈一下我自己的看法。Action与DispatchAction的不同就是DispatchAction可以被请求多次(前提不继承execute方法)。以前继承Action时,对于每一个小功能,都得写一个action,项目一大,action多的满地爬。举个例子,对于一张用户表:tbl_user的增、删、改、查,以前我做项目的时候,就会做四个action:AddUserAction, DeleteUserAction,UpdateUserAction,QueryUserAction,相应的请求也是:addUser.do, deleteUser.do... ...
而使用了DispatchAction后,可以在一个action中声明四个方法,分别为:add,delete,update,query,当然参数都得和execute方法的参数一致,且去掉execute方法,这样我只需要写一个action:UserAction,在配置action的时候,多加一个parameter="action",这样相应的请求变成了:user.do?action=add,user.do?action= delete,...
有人说使用DispatchAction增加了程序的耦合性,而我认为这样的耦合性无所谓,因为它不是层与层之间的耦合,且action的扩展性也不大。
Spring 做的是IOC和声明式事务,spring管理了dataSource,管理了DataSourceTransactionManager,当然还要告诉 spring如果取得abatis的配置文件,所以要管理SqlMapClientFactoryBean类,通过其configLocation属性注入abatis配置文件的路径,通过dataSource属性注入dataSource。要使用spring的事务,还得管理 TransactionProxyFactoryBean,当然,DAO和Service都得管理。为了使用接口编程,我们每一个DAO和Service 都写一个接口和至少一个实现类,如:对于用户表tbl_user,接口为:UserDao,实现为了UserDaoAbatisImpl。在程序中声明的时候,使用接口声明,而在spring中管理的时候,管理实现类。我们再改动业务逻辑实现时,只需再写一个类实现接口,然后在配置文件中改动一小下就 OK。这样就符合了程序设计中的“开-闭”原则。
有一点不同的是,之前我使用spring的声明式事务时,每一个Service都管理两个,举个我以前写的例子,还是对于用户表:
<bean id="UserServiceTarget" class="com.jak.hospital.service.UserServiceImpl">
<property name="userDAO" ref="UserDAOLogProxy"/>
</bean>
然后再使用事务:
<bean id="UserService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target">
<ref bean="UserServiceTarget"/>
</property>
<property name="transactionManager">
<ref bean="TransactionManager"/>
</property>
<property name="proxyInterfaces">
<list>
<value>com.jak.hospital.service.interfaces.UserService</value>
</list>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
这样来说显然有点麻烦,在这个例子中,我们用到了spring中的一个类似继承的新属性,即先写一个管理事务的模板出来,显然这个模板只需要写一次
<bean id="txProxyTemplate"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>
还是对于用户表来说,我们可以这样管理Service:
<bean id="userService" parent="txProxyTemplate">
<property name="target">
<bean class="com.jak.service.UserServiceImpl">
<property name="userDao" ref="userDao" />
</bean>
</property>
</bean>
parent属性了我们要继承哪个模板,然后在属性target下再管理我们自己的Service,这样一来显然减少了代码量。
前面我们已经做了大量的工作,剩下的abatis就简单了,我们自己的DAO类只需继承SqlMapClientDaoSupport,即可使用abatis提供的强大功能。
public class UserDaoImpl extends SqlMapClientDaoSupport implements UserDao {
public UserVo validate(UserVo userVo) throws DaoException {
List list = null;
Map map = new HashMap();
map.put("userName", userVo.getUserName());
map.put("userPass", userVo.getUserPass());
try {
list = this.getSqlMapClientTemplate().queryForList ("User.findUsers", map);
logger.debug(">> size of return = " + list.size());
...
上面的代码非常简洁,queryForList方法要求传入至少一个参数,即sql-mql的映射字符串,如果说Hibernate是PO对象与数据库表的映射的话,那么aBatis就是PO对象与SQL语句的映射,每一个sql-mql文件中都是SQL语句的映射。来看一个最简单的例子:
<select id="findScores" resultMap="result">
select * from tbl_user
</select>
sql-mql配置文件中就是一堆这样的标签:select,delete,update,insert ...
id就是相当于一个key,我们就在程序中,通过这个key来调用不同的SQL语句的映射,resultMap当然就是PO对象是属性与数据库表字段的映射,当然如果它们的命名相同的话,它可以省略。记住,如果PO对象与对应的数据表的字段命名不同的话,aBatis不会报错,而是得到null对象。
那为什么我们还要传入一个Map呢,这也是aBatis的一个特性,叫做“动态SQL”,即提供一个标签,它会检查如果你传入对象是否为空来动态拼写SQL语句,如果没有传入的话,就按声明的去执行。
写到这儿,我突然想到当时在做his项目时,寇勇问过我这样一个问题:如果我对一张表进行多条件查询时有没有什么简单的办法,就是既按姓名,又按年龄查。当然我一时没回答上来,如果这个问题让到此例中的话,显然是那么的容易,我们只需使用dynamic 标签,在程序中,想按什么查询,都把它们封装到Map中,abatis在后台会把它们一一解开动态拼写SQL语句。
<select id="findUsers" parameterClass="java.util.Map" resultMap="result">
select * from tbl_user where 1=1
<dynamic>
<isNotNull prepend="and" property="userId">
user_id=#userId:VARCHAR#
</isNotNull>
</dynamic>
<dynamic>
<isNotNull prepend="and" property="userName">
user_name=#userName:VARCHAR#
</isNotNull>
</dynamic>
<dynamic>
<isNotNull prepend="and" property="userPass">
user_pass=#userPass:VARCHAR#
</isNotNull>
</dynamic>
<dynamic>
<isNotNull prepend="and" property="userAge">
user_age=#userAge:NUMERIC#
</isNotNull>
</dynamic>
</select>
最后,log4j.properties,spring.xml, SqlMapConfig.xml都要放在src目录下,这样eclipse发布的时候才会把它们发布到类路径($WebRoot/WEB-INF/ classes)下。当然,公司的框架远比这复杂得多,这只不过是一个精简得不能再精简的例子