Struts1SH整合

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&amp;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的整合就至此为止了,如果本文存在什么问题,请及时提出来,感激不尽..

上一篇:docker的安装


下一篇:C#进阶系列——MEF实现设计上的“松耦合”(终结篇:面向接口编程)