Spring2.5+Hibernate3.5+Struts1.3整合开发
为了避免出现jar包不兼容,或者重复加载的情况,先把jar包整理出来:
hibernate核心安装包下的:
hibernate3.jar
lib/required/*
lib/optional/ehcache-1.2.3.jar
hibernate 注解安装包下的:
lib/test/slf4j-log4j12.jar
Spring安装包下的
dist/spring.jar
dist/modules/spring-webmvc-struts.jar
lib/jakarta-commons/commons-logging.jar、commons-dbcp.jar、commons-pool.jar
lib/aspectj/aspectjweaver.jar、aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
lib/j2ee/common-annotations.jar
lib/log4j/log4j-1.2.15.jar
Struts
struts-1.3.8-lib.zip,需要使用到解压目录下的所有jar包,建议把jstl-1.0.2.jar和standard-1.0.2.jar更换为1.1版本。Spring中已经存在一个antlr-2.7.6.jar,所以把
struts中的antlr-2.7.2.jar删除,避免jar冲突.
数据库驱动
替换旧的jar包,删除重复的jar包,jar包就准备好了,在此选用Mysql作为数据库,在临时数据库中创建一个person表,表的结构如下:
use test; |
create table person |
( |
name varchar(12), |
id int |
) |
不要一开始就进行三大框架的整合,我们首先进行Spring与Hibernate的整合,然后再进行Spring与Struts的整合.
把要做的事情整理好,能够便于我们开发:
搭建Hibernate
- 创建表
- 创建对应的javaBean---Person类,放置在domain包下
- 创建PersonDao,提供一个save方法,接收一个Person对象,放置在dao下
- 创建BusinessService,维护一个PersonDao对象,对外界提供save功能,放置在service下
- 配置person.hbm.xml文件,放置在domain包下
- 配置hibernate.cfg.xml,放置在类路径下
表和实体创建好后,就开始配置person.hbm.xml文件:
<hibernate-mapping> |
<!-- |
name:javaBean |
table:对应的表名 |
--> |
<class name="..domain.Person" table="person"> |
<!-- 配置主键 --> |
<id name="id" type="integer"> |
<column name="id"></column> |
<!-- 自增 --> |
<generator class="increment"></generator> |
</id> |
<!-- 配置其他列 --> |
<property name="name" type="string"> |
<column name="name" sql-type="varchar(12)"></column> |
</property> |
</class> |
</hibernate-mapping> |
接下来配置hibernate.cfg.xml文件,相信大家对session-factory的配置很熟悉,就不再赘述了:
<hibernate-configuration> |
<session-factory> |
<property name="hibernate.connection.username">root</property> |
<property name="hibernate.connection.password">root</property> |
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property> |
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> |
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> |
<property name="hibernate.show_sql">true</property> |
<property name="hibernate.hbm2ddl.auto">update</property> |
<mapping resource="../domain/Person.hbm.xml"/> |
</session-factory> |
</hibernate-configuration> |
为了简化对数据库的操作,我们在PersonDao里面维护一个HibernateTemplate,标注注解,用Spring注入进来:
@Resource |
private HibernateTemplate hibernateTemplate; |
同样,BusinessService维护的PersonDao也使用注解注入:
@Resource |
private PersonDao personDao; |
Hibernate的搭建就完成了.
由于目前为止没有使用到Struts,我们不用在WEB上对Spring+Hibernate进行测试,用junit或者main方法都可以,在此用main方法测试.
搭建Spring
- 创建RegisterAction类(为后续的Struts作准备),在类加入main方法,放入web.action包中
- 配置beans.xml文件,放在类路径下
我们将在后面使用Spring的事务管理,所以要引入tx命名空间,事务管理器还需要被管理的切入点,不然就只是没有灵魂的空壳而已,aop命名空间也是必须的,还有对标注@Resource注解的字段进行注入,因而要引入context命名空间.
由于数据源在hibernate.cfg.xml文件中配置,hibernate中的session,就相当于Spring中的datasource,我们需要Spring获取hibernate.cfg.xml文件中的数据,Spring的org.springframework.orm.hibernate3包中,有着一系列整合hibernate框架的类,我们只需要使用LocalSessionFactoryBean类即可:
<!-- 这是Spring整合Hibernate的入口 --> |
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> |
<!-- |
Spring生成sessionFactory需要Hibernate的配置文件Hibernate.cfg.xml |
classpath:hibernate.cfg.xml: 表示在类路径下查找hibernate.cfg.xml文件,语法格式classpath:配置文件 |
--> |
<property name="configLocation" value="classpath:hibernate.cfg.xml" /> |
</bean> |
Spring标榜自己的声明式事务管理是多么多么的强大,我们不对事务管理器进行配置,岂不是白费了Spring的一番苦心?
<!-- Spring支持5种事务管理器,分别对应不同的委托机制,我们现在使用Hibernate,就要使用Hibernate的事务管理器实现. |
Spring支持的5种事务管理器: |
org.springframework.jdbc.datasource.DataSourceTransactionManager |
org.springframework.orm.hibernate3.HibernateTransactionManager |
org.springframework.jdo.JdoTransactionManager |
org.springframework.transaction.jta.JtaTransactionManager |
org.springframework.orm.ojb.PersistenceBrokerTransactionManager --> |
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> |
<property name="sessionFactory" ref="sessionFactory"></property> |
</bean> |
接下来该配置事务管理器的通知(其实事务管理器就是一个切面):
<!-- 配置通知 |
transaction-manager: 表示通知织入到的切面 |
--> |
<tx:advice id="advice" transaction-manager="txManager"> |
<tx:attributes> |
<tx:method name="save*" read-only="false" isolation="DEFAULT" propagation="REQUIRED"/> |
<!-- 如果是其它方法 --> |
<tx:method name="*" read-only="true"/> |
</tx:attributes> |
</tx:advice> |
切入点:
<!-- 配置切入点 --> |
<aop:config> |
<aop:pointcut id="perform" expression="execution(* ..service..*.*(..))"/> <!-- 本文所有包名都用..代替,此表达式包名有误 --> |
<aop:advisor pointcut-ref="perform" advice-ref="advice"/> |
</aop:config> |
由于PersonDao中维护了一个HibernateTemplate,BusinessService中维护了一个PersonDao,我们需要在配置文件中对它们进行注入:
<!-- HibernateTemplate --> |
<bean id="hibernateTemplat" class="org.springframework.orm.hibernate3.HibernateTemplate"> |
<property name="sessionFactory" ref="sessionFactory"></property> |
</bean> |
<!-- Dao层对象 --> |
<bean id="personDao" class=".t.dao.PersonDao"></bean> |
<!-- 配置业务层对象 --> |
<bean id="service" class="..service.BusinessService"></bean> |
<!-- 注册注解解析器 --> |
<context:annotation-config></context:annotation-config> |
最后再在RegisterAction中进行测试,RegisterAction:
public class RegisterAction { //由于只是测试,不需要继承Action |
public static void main(String[] args) throws Exception { |
ApplicationContext act = new ClassPathXmlApplicationContext("beans.xml"); |
BusinessService service = (BusinessService) act.getBean("service"); |
Person person = new Person(); |
//id设置了自增,不用管 |
person.setName("zhang"); |
service.save(person); |
} |
} |
注:由于PersonDao和BusinessService都没有实现接口,故代理对象使用cglib库生成目标对象的子类.如果对事务管理器工作产生怀疑,可以在dao中分别抛出运行时异常和编译时异常进行测试.
搭建Struts
- 创建register.jsp和success.jsp文件,直接放在根目录下
- RegisterAction继承自DispatchAction
- 维护一个BusinessService,由Spring运行时注入
- 配置struts-config.xml
- 配置web.xml
register.jsp文件中只需要简单的一个表单即可,一个文本框,一个提交按钮,用不用Stusts的标签库都行,不过,我为了方便,在表单里面增加了一个隐藏域:
<input type="hidden" name="method" value="save" /> |
待会在Struts的配置文件中对方法进行转发就行了.
success.jsp文件中也随便输出什么就行了,只要是看到就代表Action的跳转成功了.
把RegisterAction稍微修改一下:
public class RegisterAction extends DispatchAction{ |
/** |
* 加了点形参,接收Struts传递的对象 |
*/ |
public ActionForward save(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) { |
ApplicationContext act = new ClassPathXmlApplicationContext("beans.xml"); |
BusinessService service = (BusinessService) act.getBean("service"); |
Person person = new Person(); |
person.setName(request.getParameter("name")); |
service.save(person); |
return mapping.findForward("success"); |
} |
} |
在WEB-INF下配置Struts的Action映射信息:
<struts-config> |
<action-mappings> |
<action path="/register" |
type="..web.action.RegisterAction" |
scope="request" |
parameter="method"> //跟register.jsp中隐藏域的值对应 |
<forward name="success" path="/success.jsp"></forward> |
</action> |
</action-mappings> |
</struts-config> |
再将ActionServlet在web.xml文件中映射进来就行了.
测试,如果跳转到success.jsp页面就代表成功了.
好了,程序逻辑就是这样,但是有三点需要注意,首先,Hibernate的配置文件中配置了一个连接池,这是Hibernate的开发者为了让人们学习Hibernate而开发的一个简易连接池,这个连接池在实际应用中是有BUG的,不能使用.那么我们就要在Spring里面配置连接池,并且要交由Spring管理:
Hibernate配置文件修改后如下:
<hibernate-configuration> |
<session-factory> |
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> |
<property name="hibernate.show_sql">true</property> |
<property name="hibernate.hbm2ddl.auto">update</property> |
<mapping resource="../../domain/Person.hbm.xml"/> |
</session-factory> |
</hibernate-configuration> |
删除了关于连接的信息,然后在Spring的配置文件中加上连接池,以c3p0为例:
<!-- 配置数据源 --> |
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> |
<property name="driverClass" value="com.mysql.jdbc.Driver" /> |
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=GBK"/> |
<property name="user" value="root" /> |
<property name="password" value="root" /> |
<!--连接池中保留的最小连接数。--> |
<property name="minPoolSize" value="5" /> |
<!--连接池中保留的最大连接数。Default: 15 --> |
<property name="maxPoolSize" value="30" /> |
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> |
<property name="initialPoolSize" value="10"/> |
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> |
<property name="maxIdleTime" value="60"/> |
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> |
<property name="acquireIncrement" value="5" /> |
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements |
属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 |
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0--> |
<property name="maxStatements" value="0" /> |
<!--每60秒检查所有连接池中的空闲连接。Default: 0 --> |
<property name="idleConnectionTestPeriod" value="60" /> |
<!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> |
<property name="acquireRetryAttempts" value="30" /> |
<!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 |
保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 |
获取连接失败后该数据源将申明已断开并永久关闭。Default: false--> |
<property name="breakAfterAcquireFailure" value="true" /> |
</bean> |
在Spring的配置文件中加入这个还不够,它虽然知道有这么个连接池,但是没有管理起来,我们需要增加Spring在创建SessionFactory时的参数:
<!-- 创建本地化工厂Bean,这是Spring整合Hibernate的入口 --> |
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> |
<!-- |
不得不说Spring的这玩意设计的很好,有dataSource这个属性, |
dataSource就对应Hibernate中的sessionFactory |
--> |
<property name="dataSource" ref="dataSource"></property> |
<!-- |
Spring生成sessionFactory需要Hibernate的配置文件Hibernate.cfg.xml |
classpath:hibernate.cfg.xml: 表示在类路径下查找hibernate.cfg.xml文件,语法格式classpath:配置文件 |
--> |
<property name="configLocation" value="classpath:hibernate.cfg.xml" /> |
</bean> |
这样一来,Spring就会管理这个连接池,池也能发挥作用了.第一点需要注意的就是Hibernate的池并不好用,学习娱乐一下没有问题,不要放在真正的项目上面去.
第二点就是Action中的BusinessService需要由Spring注入进来,而不是在同一个Action中都写上相同的获取BusinessService的代码.好吧,也许这个很简单,就不用说了...
第三点.
就是当你以为第二点很简单的时候你就错了!你要知道Action是什么时候创建的,它是由Spring创建的吗?Spring会在Struts创建这个Action的时候给它注入对象吗?Struts1中的Action只有一个,且都是在服务器初始化的时候一并初始化的,想要让Spring注入这个对象,至少也要让Spring在服务器初始化的时候加载进来
ApplicationContext act = new ClassPathXmlApplicationContext("beans.xml");
这段代码只能在程序运行时加载beans.xml文件,我们要让beans.xml文件随服务器的启动而启动,就需要配置上下文参数:
<context-param> |
<param-name>contextConfigLocation</param-name> |
<!-- 加载beans.xml文件的方法二:classpath:beans.xml --> |
<param-value>/WEB-INF/classes/beans.xml</param-value> |
</context-param> |
这段代码放在web.xml文件中,启动web应用的时候,这些参数就被读取到了ServletContext中,,整个web应用程序都共享这个上下文参数,注意和<init-param>标签不同,<init-param>是servlet范围内的参数,只能在servlet的init()方法中取得.
好了,光有上下文参数还不够,它只是以contextConfigLocation为Key,存储在ServletContext中而已,我们要让Spring容器启动,还是得用老办法:
<!-- 通过servlet配置Spring容器随Web应用的启动而初始化 --> |
<servlet> |
<servlet-name>contextLoader</servlet-name> |
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> |
<load-on-startup>2</load-on-startup> |
</servlet> |
让ContextLoaderServlet 这个类加载,然后读取contextConfigLocation的数据,从而启动Spring,但是我并没有在这里指定读取哪个文件,也没有告诉它读取ServletContext中的contextConfigLocation,那么它是如何知道的呢?
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; |
ContextLoaderServlet里面维护了一个ContextLoader,上面这个字符串常量是ContextLoader中的一人属性,那么就不用再说什么了吧?Spring会自动去找这个Key.
启动Spring就没有问题了,但是这样还不够,事实上我们需要在Struts创建Action的时候把BusinessService注入进来,这就需要我们对Struts的工作机制有相当的了解.Struts在工作的时候,使用了很多Processor(处理程序),每个处理程序都只完成一个功能,我们要利用Struts的这个特点,在适当的位置加入一个处理程序,然后注入对象.不过,Spring已经帮我们完成了这个功能,毕竟它号称支持全部主流框架,我们需要用到一个处理程序,但是不用考虑实现细节,修改后的Struts-config.xml文件如下:
<struts-config> |
<action-mappings> |
<action path="/register" |
scope="request" |
parameter="method"> |
<forward name="success" path="/success.jsp"></forward> |
</action> |
</action-mappings> |
<!-- |
ActionServlet中所有的工作委托给RequestProcessor负责 |
controller:struts中ActionServlet把请求委托给processorClass属性所指定的类来完成 |
* org.springframework.web.struts.DelegatingRequestProcessor该类完成 |
spring负责创建struts的Action对象,并注入业务层对象,剩下的工作仍由Struts完成 |
--> |
<controller processorClass="org.springframework.web.struts.DelegatingRequestProcessor"/> |
</struts-config> |
关键就是最后一句.注意:上面action中没有type属性,因为已经不需要了,写了也没有用,这是因为只要有DelegatingRequestProcessor 这个处理程序在,Struts读取这个配置文件的时候,它就会拿path指定的值去找Spring中的bean,意味着你其他不想要这样处理的action也要这样处理,不过Spring里面也很简单:
<!-- 配置RegisterAction --> |
<bean name="/register" class="..web.action.RegisterAction"> |
<property name="service" ref="service"></property> |
</bean> |
就像这样,在Spring中加上这个一个bean就可以了,注意它和action的path属性值是一样的.最后修改的RegisterAction如下:
public class RegisterAction extends DispatchAction{ |
private BusinessService service; |
public ActionForward save(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) { |
Person person = new Person(); |
person.setName(request.getParameter("name")); |
this.service.save(person); |
return mapping.findForward("success"); |
} |
public void setService(BusinessService service) { |
this.service = service; |
} |
} |
已经不需要多余的代码了,对象由Spring注入进来,然后剩下工作由Struts完成.没有解决中文乱码的问题.不过整合已经完成了.
另外,再提供另外一种解决方案,称做"分离",可以不必让Struts和Spring耦合的这么紧.
由于web.xml文件中已经将beans.xml读取到ServletContext中了,我们可以使用工具类读取ServletContext里面beans.xml的配置信息.先将beans.xml文件中关于action的配置删掉,再将Struts-config.xml文件中的处理程序删掉,给action配上type,就样就是一个传统的action配置.现在,Struts和Spring中已经没有各自的配置项了,是不是已经解耦了呢?不过光这样还不行,我们要修改RegisterAction中的代码,只需要加入两行而已:
WebApplicationContext wct = WebApplicationContextUtils.getWebApplicationContext(this.getServlet().getServletContext()); |
service = (BusinessService) wct.getBean("service"); |
第一句是利用WebApplicationContextUtils工具类获取ServletContext中beans.xml文件的配置信息,然后就可以得到bean了.这样就完成了分离.
Spring2.5+Hibernate3.5+Struts1.3的整合就至此为止了,如果本文存在什么问题,请及时提出来,感激不尽..