总算将一个项目勉强上线,也是第一次我独立完成的这么多任务,框架设计、框架搭建、代码编写、测试、数据库创建、表结构设计……除了页面和详细设计我都做了个遍,虽然很仓促也犯了很多错误,但是整个过程走下来,发现自己真的学到不少东西。在这个空档期写个小总结,以便以后使用 :)
现在框架真是多的要死,也没有什么真正能一统天下的东西,每个人的着眼点和技术习惯都不同,在刚接到项目的时候,老大就要我自己去确定框架。当时我正沉迷于TapeStry+Spring+iBatis开发的畅快之中,当时真想直接将自己的东西移植过去,但是一想公司陆续会有其他人进来加入到项目中,那么TSS这个框架肯定很难找到合适的人选,而且自己在研究TSS的过程中也发现由于文档的缺乏,遇到问题的解决就是一个很严重的问题,最终只能弃用。虽然我很喜欢T4,但是它实在不让人放心,只好期待Howard能给我们不断带来惊喜,真正成为继Struts、WebWork之后的最优选择。有时间我还会继续学习T4,并做一下总结。
经过当时的考虑,我只得放弃TSS,最终选择自己相对熟悉的Struts+Spring+Hibernate,说是熟悉,那是因为以前的项目是用这个,有网友戏称SSH是庸俗组合,呵呵,庸俗就庸俗吧,我想自己能把这套东西搞好也会有相当的难度的(现在证明这个选择是对的,出了问题有人问,项目组的其他人也都是玩这套东西的,维护扩展都方便)。
具体细节就不再描绘了,做软件的都知道问题无法解决是多么的折磨人,这其中的酸甜苦辣只有经历过的人才知道,技术上的问题是遇到一个解决一个,实在觉得解决不了或者没有把握,利马换其他想法,这个很重要,实现的方法有很多,即便不是最优的解决方案,但是节省的大量的时间,在工期紧的时候一定要权衡的去考虑,否则后果不堪设想。
总体框架(SSH)需要4个重要的配置文件:
1.web.xml
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
< web-app >
< display-name > sample </ display-name >
< context-param >
< param-name > contextConfigLocation </ param-name >
< param-value > /WEB-INF/applicationContext.xml </ param-value >
</ context-param >
<!-- OpenSessionInViewFilter配置,其实是一个又繁琐性能又低的东西,以后再也不想用到 -->
< filter >
< filter-name > OpenSessionInViewFilter </ filter-name >
< filter-class > org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </ filter-class >
<!-- singleSession默认为true,若设为false则等于没用OpenSessionInView -->
< init-param >
< param-name > singleSession </ param-name >
< param-value > true </ param-value >
</ init-param >
</ filter >
< filter-mapping >
< filter-name > OpenSessionInViewFilter </ filter-name >
< url-pattern > /* </ url-pattern >
</ filter-mapping >
<!-- encoding -->
< filter >
< filter-name > Set Character Encoding </ filter-name >
< filter-class > com.shpcims.util.SetCharacterEncodingFilter </ filter-class >
< init-param >
< param-name > encoding </ param-name >
< param-value > utf-8 </ param-value >
</ init-param >
</ filter >
< filter-mapping >
< filter-name > Set Character Encoding </ filter-name >
< servlet-name > action </ servlet-name >
</ filter-mapping >
<!-- Spring Context listener -->
< listener >
< listener-class > org.springframework.web.context.ContextLoaderListener </ listener-class >
</ listener >
< servlet >
< servlet-name > action </ servlet-name >
< servlet-class > org.apache.struts.action.ActionServlet </ servlet-class >
< init-param >
< param-name > config </ param-name >
< param-value > /WEB-INF/struts-config.xml </ param-value >
</ init-param >
< init-param >
< param-name > debug </ param-name >
< param-value > 3 </ param-value >
</ init-param >
< init-param >
< param-name > detail </ param-name >
< param-value > 3 </ param-value >
</ init-param >
< load-on-startup > 1 </ load-on-startup >
</ servlet >
< servlet-mapping >
< servlet-name > action </ servlet-name >
< url-pattern > *.do </ url-pattern >
</ servlet-mapping >
< welcome-file-list >
< welcome-file > index.jsp </ welcome-file >
</ welcome-file-list >
< taglib >
< taglib-uri > /WEB-INF/struts-bean.tld </ taglib-uri >
< taglib-location > /WEB-INF/struts-bean.tld </ taglib-location >
</ taglib >
< taglib >
< taglib-uri > /WEB-INF/struts-html.tld </ taglib-uri >
< taglib-location > /WEB-INF/struts-html.tld </ taglib-location >
</ taglib >
< taglib >
< taglib-uri > /WEB-INF/struts-logic.tld </ taglib-uri >
< taglib-location > /WEB-INF/struts-logic.tld </ taglib-location >
</ taglib >
< taglib >
< taglib-uri > /WEB-INF/struts-tiles.tld </ taglib-uri >
< taglib-location > /WEB-INF/struts-tiles.tld </ taglib-location >
</ taglib >
< taglib >
< taglib-uri > /WEB-INF/CustomTag.tld </ taglib-uri >
< taglib-location > /WEB-INF/tags/CustomTag.tld </ taglib-location >
</ taglib >
</ web-app >
2.Struts-config.xml
<! DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
< struts-config >
<!-- forms -->
< form-beans >
< form-bean name ="loginForm" type ="com.shpcims.web.login.form.LoginForm" /> </ form-beans >
< global-forwards >
< forward name ="web_report" path ="/reporter" />
< forward name ="report_fail" path ="/WEB-INF/jsp/SystemError.jsp" />
</ global-forwards >
<!-- actions -->
< action-mappings >
< action path ="/LoginAction"
type ="org.springframework.web.struts.DelegatingActionProxy"
scope ="request"
name ="loginForm"
validate ="true"
input ="/index.jsp" >
< forward name ="success" path ="/CreditInfoPreAction.do" />
< forward name ="fail" path ="/index.jsp" />
</ action >
</ action-mappings >
<!-- Custmoer Controller for Authentication -->
< controller >
< set-property property ="processorClass" value ="com.shpcims.framework.CustomerRequestProcessor" />
</ controller >
<!-- Message-resources -->
< message-resources parameter ="ApplicationResources" />
<!-- plug-in -->
<!-- tiles template -->
< plug-in className ="org.apache.struts.tiles.TilesPlugin" >
< set-property property ="definitions-config" value ="/WEB-INF/tiles-defs.xml" />
< set-property property ="definitions-debug" value ="2" />
< set-property property ="definitions-parser-details" value ="2" />
< set-property property ="definitions-parser-validate" value ="true" />
</ plug-in >
<!-- spring plug-in -->
< plug-in className ="org.springframework.web.struts.ContextLoaderPlugIn" >
< set-property property ="contextConfigLocation" value ="/WEB-INF/beans.xml" />
</ plug-in >
<!-- Validator plugin -->
< plug-in className ="org.apache.struts.validator.ValidatorPlugIn" >
< set-property
property ="pathnames"
value ="/WEB-INF/validaterule/validator-rules.xml,
/WEB-INF/validaterule/validation.xml
" />
</ plug-in >
</ struts-config >
3.applicationContext.xml
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
< beans >
<!-- service -->
< bean id ="chartService" parent ="baseTransactionProxy" >
< property name ="target" >
< bean class ="com.shpcims.service.ChartServiceImpl" >
</ bean >
</ property >
</ bean >
<!-- dao -->
< bean id ="customDao" class ="com.shpcims.dao.CustomDaoImpl" >
< property name ="sessionFactory" ref ="sessionFactory" />
</ bean >
<!-- 配置sessionFactory, 注意这里引入的包的不同 -->
< bean id ="sessionFactory" class ="org.springframework.orm.hibernate3.LocalSessionFactoryBean" >
< property name ="dataSource" ref ="dataSource" />
<!-- mappingResources -->
< property name ="mappingResources" >
< list >
< value > com/shpcims/model/Area.hbm.xml </ value > </ list >
</ property >
< property name ="hibernateProperties" >
< props >
< prop key ="hibernate.dialect" > org.hibernate.dialect.SQLServerDialect </ prop >
<!-- 开发期间为true -->
< prop key ="hibernate.show_sql" > true </ prop >
</ props >
</ property >
</ bean >
< bean id ="baseTransactionProxy" class ="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract ="true" >
< property name ="proxyTargetClass" value ="true" />
< property name ="transactionManager" ref ="transactionManager" />
< property name ="transactionAttributes" >
< props >
< prop key ="update*" > PROPAGATION_REQUIRED </ prop >
< prop key ="insert*" > PROPAGATION_REQUIRED </ prop >
< prop key ="create*" > PROPAGATION_REQUIRED </ prop >
< prop key ="assign*" > PROPAGATION_REQUIRED </ prop >
< prop key ="reassign*" > PROPAGATION_REQUIRED </ prop >
< prop key ="delete*" > PROPAGATION_REQUIRED </ prop >
< prop key ="select*" > PROPAGATION_REQUIRED </ prop >
< prop key ="remove*" > PROPAGATION_REQUIRED </ prop >
< prop key ="find*" > PROPAGATION_REQUIRED,readOnly </ prop >
< prop key ="add*" > PROPAGATION_REQUIRED </ prop >
< prop key ="delete*" > PROPAGATION_REQUIRED </ prop >
</ props >
</ property >
</ bean >
< bean id ="calculateJob" class ="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" >
< property name ="targetObject" >
< ref bean ="gradeCalculateService" />
</ property >
< property name ="targetMethod" >
< value > calcuateCreditLevel </ value >
</ property >
</ bean >
< bean id ="cronCalculateTigger" class ="org.springframework.scheduling.quartz.CronTriggerBean" >
< property name ="jobDetail" >
< ref bean ="calculateJob" />
</ property >
< property name ="cronExpression" >
< value > 0 0 0 1,15 * ? </ value >
</ property >
</ bean >
< bean class ="org.springframework.scheduling.quartz.SchedulerFactoryBean" >
< property name ="triggers" >
< list >
< ref bean ="cronCalculateTigger" />
</ list >
</ property >
</ bean >
< bean id ="dataSource" class ="org.apache.commons.dbcp.BasicDataSource" destroy-method ="close" >
< property name ="driverClassName" >
< value > net.sourceforge.jtds.jdbc.Driver </ value >
</ property >
< property name ="url" >
< value > jdbc:jtds:sqlserver://192.168.0.16:1433/SHPCIMS </ value >
</ property >
< property name ="username" >
< value > sqlserver </ value >
</ property >
< property name ="password" >
< value > sqlserver </ value >
</ property >
</ bean >
< bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name ="dataSource" ref ="dataSource" />
</ bean >
<!-- init properties
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>init.properties</value>
</property>
</bean> -->
<!-- use to resolve hibernate lazy load -->
< bean id ="hibernateLazyResolver" class ="com.shpcims.util.HibernateLazyResolver" >
< property name ="sessionFactory" >< ref local ="sessionFactory" /></ property >
</ bean >
<!-- log advice -->
< bean name ="logger" class ="com.shpcims.framework.LogAdvice" >
< property name ="systemManagerService" ref ="systemManagerService" />
</ bean >
< bean name ="beforeLogAdvice" class ="com.shpcims.framework.BeforeLogAdvice" >
< property name ="systemManagerService" ref ="systemManagerService" />
</ bean >
</ beans >
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
< beans >
<!-- action -->
< bean name ="/LoginAction" class ="com.shpcims.web.login.action.LoginAction" singleton ="false" >
< property name ="systemManagerService" ref ="systemManagerService" />
</ bean >
< bean name ="autoProxy" class ="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
< property name ="beanNames" >
< list >
< value > /LoginAction </ value >
</ list >
</ property >
< property name ="interceptorNames" >
< list >
< value > logger </ value >
< value > beforeLogAdvice </ value >
</ list >
</ property >
< property name ="proxyTargetClass" value ="true" />
</ bean >
</ beans >
4个文件中的大多数配置都是很普通的,只在这记录一些比较容易出问题的地方,
web.xml中配置了OpenSessionInViewFilter,这个东西是我在整个框架构建中浪费最多时间也是出现问题最多的地方,后来和项目组同事讨论,最终在新的项目中放弃了对这烂东西的使用,如果使用延迟加载,那么所有需要加载的东西应该在Dao中使用Hibernate.initialize(object)强制加载,使得持久化操作保持在Dao层中完成,彻底抛弃OpenSesionInViewFilter。HOHO
Spring ContextListner 的加载仍然放在web.xml里面进行,尽管在Struts中使用了ContextLoaderPlugin,但仅仅是在加载StrutsAction的时候才使用到,而整个SpringContext的初始化仍然是放在web.xml里面进行。个人觉得这样做肯定有很多好处,但是目前仍不能说出真正的缘由。如此配置的初衷还是因为用到了OpenSessionInViewFilter,因为如果在Struts中去初始化Spring,OpenSessionInViewFilter根本跑不起来,因为它本身是在web.xml中初始化的,而且需要ContextListner 的支持。必须配置在web.xml中,此时仍使用ContextLoaderPlugin来加载就会出现Spring被加载两次的情况,结果造成OpenSessionInViewFilter失效。尽管现在不再使用那个Filter,配置却仍然这样保持下来,我觉得仍然可以避免一些问题,毕竟配置在Struts下面就被Struts绑死了,那么好像是 Struts在控制整体,违背了框架搭建的初衷,也无法使Spring发挥自己最大的功力。
Struts-config.xml中有3个需要注意的地方,第一个是使用DelegatingActionProxy,我曾经对我项目组的同事每个人都解释了一遍为什么要使用这个东东,真是痛苦的要死。但是这个东西一直都被认为是Struts+Spring最好的配置方式,其他的诸如使用WebLoadContext那种方式好像都无法带来这样的好处。我觉得这样用的好处是:就如它的名字一样,Proxy,Struts在使用它的时候根本不知道它的存在,几乎感觉不到影响,只是把配置文件写到beans.xml中去了。第二个好处是Struts的线程安全问题,如此配置新的线程不再共享同一个Action,更高深的东西我也说不清楚了 :(。第三个好处就是方便的使用AOP,这个是我最喜欢的了,有了这个Proxy,StrutsAction才真正的成为Spring辖区的一部分,方便的使用AOP,我不知道没有AOP我需要多写几千行代码,这对项目的及时完工真是不知道作出多少贡献,也使得框架更加优雅,极尽赞美之词也不为过。:P
第二个注意的地方是使用了Tiles框架,尽管后来美工给我Frame框架的时候造成了很多麻烦,可我仍然挺喜欢Tiles,心想如果我在用T4的时候能那么轻易的使用Tiles当时真的就用TSS也说不一定哦。Tiles里面基础配置没什么可说的,tiles-defs.xml、layout.jsp,页面上的使用写正确配置就可以了,但是在使用Tiles的时候遇到一个预加载JS的需求,要不是手头有以前项目的代码,估计就玩完了
< tiles:getAsString name ="javascript" ignore ="true" />
< SCRIPT language ="JavaScript" > ...
<!--
function init()...{
if(window.subinit)...{
subinit();
}
}
<tiles:insert attribute="fw_script_block" ignore="true" />
//-->
</ SCRIPT >
< link href ="css/portal.css" rel ="stylesheet" type ="text/css" >
</ HEAD >
< BODY onload ="init()" style ="overflow-x:hidden;overflow-y:hidden" >
看上去很普通的代码,但是其中使用了tiles的一个树型 ignore="true", 才真正使得预加载JS变得更加方便和可控制,这样我就不用在每个布局模板上都写上一个没有意义的空方法在里面咯。
第三个问题是使用Validation框架,尽管以前使用过,但是自己用的时候才知道配置起来没那么简单,最初我采用Struts-1.2.9版本,没想到Validation不能用,后来在网上发现Validation用的包不全是Struts自带的,版本不统一导致了Validation问题多多,折磨我好多天,最终不得不放弃Struts1.2.9而降级到1.2.8,才使得问题得以解决。
applicationContext.xml中的配置也相对要稳定一些,需要什么加什么就好了,当时我为了将JUnit分离出来还特地请教了一位高人,JUnit分离其实有很大的意义,它不但真正意义上脱离了Web环境还使得测试数据和真实数据的分离。利用好JUnit绝对是提高开发效率的一件法宝,难怪有很多大师级的人物都说编写测试用例要比编写代码挑战大得多。但是项目最终的遗留问题让我被迫放弃了这个JUnit分离:(,原因是我无法解决相对路径的查找,项目中大量用到了Socket、线程、任务计划这种东西,使得相对路径的问题多多,没办法我只能尽量缩减配置文件的数目,不知道有没有更好的解决方案。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1028413