SSH框架整合笔记

这一篇说的是纯手动的整合,目前主流的应该是采用 Maven 或者 Gradle 进行整合,这个以后再说吧…
关于集成顺序,不是固定的,但是一般是先添加 Spring 支持,再添加 Hibernate 支持,这样 hibernate.cfg.xml 就被集成到 applicationContext.xml(beans.xml) 中,就不用手动配置了。至于 Struts 在他们俩前后都可以了….
每添加一个框架建议做一次测试,以保证引入是没有问题的,用简单的单元测试就行

环境搭建

先用最基本的手动搭建大法,现在使用构建工具来搭建也很普遍了,也更方便一些,后面看看再说吧,这里采用的是:Struts2-2.3 + Spring4.3 + Hibernate5.2
手动导入的时候还要注意一个问题,那就是 jar 包的冲突问题

1.引入Spring&XML知识补充

作为第一个引入的来说,应该是最简单的,没什么可说的和平常加入 Spring 是一样的,导入相应的 jar 包,设置好相应的配置文件就行了
配置文件的名字和位置其实是随意的,前面都说过,通过用 applicationContext 或者 beans 还有种是 spring-config,一般放在 src 目录下
配置方面可以加入 aop 和事务的约束,对于 spring 来说 xsd 文件可以不写版本号,会使用自带的最新的版本,内部做了本地的映射,jar 包中就有 xsd 文件,避免离线不能用的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 自动扫描方式加载对象 -->
<context:component-scan base-package="com.bfchengnuo"/>
</beans>

这里就顺便补充下关于 xml 的一些知识吧,原来大多使用的是 dtd 约束文件,但是因为其的局限性已经逐步被 XML Schema 取代,对于 Spring ,为了避免冲突,有几种不同的命名空间,相当于 java 中的包了
需要那个加那个就可以了

  • xmlns=
    声明 xml 文件默认的命名空间,表示未使用其他命名空间的所有标签的默认命名空间。
    如果没有提供 schemaLocation,那么 Spring 的 XML 解析器会从 namespace 的 URI 里加载 XSD 文件
  • xmlns:xsi=
    声明 XML Schema 实例,声明后就可以使用 schemaLocation 属性了
    说明当前的 xml 是 schema 一个实例文档
  • xmlns:aop=
    声明前缀为 aop 的命名空间,后面的 URL 用于标示命名空间的地址,不会被解析器用于查找信息
    其惟一的作用是赋予命名空间一个惟一的名称。当命名空间被定义在元素的开始标签中时,所有带有相同前缀的子元素都会与同一个命名空间相关联。
  • xsi:schemaLocation=
    这个从命名可以看出个大概,指定 Schema 的位置这个属性必须结合命名空间使用。
    这个属性有两个值,第一个值表示需要使用的命名空间。第二个值表示供命名空间使用的 XML schema 的位置
    schemaLocation 提供了一个 xml namespace 到对应的 XSD 文件的一个映射,所以我们可以看到,在 xsi:schemaLocation 后面配置的字符串都是成对的,前面的是 namespace 的 URI,后面是 xsd 文件的 URI

为了防止离线获取不到的问题,Spring 很贴心的将这些文件打包到 jar 文件里了,并且进行了映射,所以说:不要在 Spring 的配置里,配置上 XSD 的版本号,因为如果没有配置版本号,取的就是当前 jar 里的 XSD 文件,减少了各种风险。而且这样约定大于配置的方式很优雅。


如果感觉下载 XSD 文件非常慢,导致IDE的卡顿,可以事先下载好然后本地引用,详细配置可以参考:
http://blog.csdn.net/jackphang/article/details/17021241

2.引入Hibernate

和一般的引入也是差不多的,倒不如说更简单了,因为 cfg 文件不需要单独配置了(映射的 hbm 文件也可以用 JPA 来代替了),直接在 Spring 的配置文件里用就行了
导入相关的 jar 包不要忘了数据库驱动的包和连接池相关的包(spring 也自带一些)

首先要配置数据源,这里使用的 C3P0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="org.gjt.mm.mysql.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/itcast?useUnicode=true&amp;characterEncoding=UTF-8"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="1"/>
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="1"/>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="300"/>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="5"/>
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60"/>
</bean>

至于为什么要配置数据源,现在基本都是用线程池的吧,要不然效率也太低了并且官方也推荐这样配置,关于连接池的好处前面其实已经说过了
然后就是 Hibernate 最重要的对象 sessionFactory 了(单例),并且我们知道它是需要一个数据源的,我们配置的 cfg 配置文件其实就是为了生成它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 使用的注解,或者 packagesToScan -->
<property name="annotatedClasses">
<list>
<value>com.bfchengnuo.entity.UsersEntity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="dialect">org.hibernate.dialect.MySQLDialect</prop>
<!-- 设置 session 的上下文,解决 getCurrentSession 获取不到的情况 -->
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate5.SpringSessionContext</prop>
<prop key="hibernate.connection.url">jdbc:mysql://localhost:3306/ssh</prop>
<prop key="hibernate.connection.driver_class">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>

<!-- 上面用的是注解形式,如果使用的是 hbm 映射文件就是这样配 -->
<property name="mappingResources">
<list>
<value>com/bfchengnuo/domain/Employee.hbm.xml</value>
</list>
</property>

至于里面的配置是什么意思,我想都看出来了,这也更说明了 Spring 其实就是个快速生成对象(bean)的框架(从这里来看)
其他的,可以配置下事务等配置,使用事务管理器最大的好处就是能少些代码,由它来管理 sessionFactory 的事务,其实是使用的类似 AOP 技术的(织入)通知实现的,这样在 Service 层的类中写的 CRUD 方法的时候就不需要写事务相关的代码了(记得在类上加注解)

1
2
3
4
5
6
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 使用注解配置事务 -->
<tx:annotation-driven transaction-manager="txManager"/>

然后可以创建个实体 bean,编写好对应的映射文件,在上面的配置中写好 hbm 的文件路径(如果用注解就是类名了),然后就可以进行测试了,不出意外的话 hibernate 就集成完毕了

3.引入Struts

Struts 作为 web 层的框架引入也是很简单的,首先当然还是要导入相关的 jar ,还是要注意 jar 包的重复,以及因为要和 spring 整合,不要忘了导入 struts2-spring-plugin 包和 spring-web-x.x.x.RELEASE.jar 包
然后顺便配置下相关的配置文件,有 struts 的 Struts.xml

1
2
3
4
5
6
7
8
<!-- 指定Action对象由谁创建 -->  
<constant name="struts.objectFactory" value="spring"></constant>
<!-- 配置Action 不用写全名,因为已经交给Spring管理 -->
<package name="employee" namespace="/employee" extends="struts-default">
<action name="list" class="employeeAction">
<result name="list">/WEB-INF/page/employee.jsp</result>
</action>
</package>

注意:Action 的管理托管给 Spring 后,在 action 标签配置的 class 就是 spring 配置文件中 bean 的 id
web.xml 中配置 struts:

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

最后还是要在 Web.xml 中配置实例化 Spring 容器,避免每一次请求就初始化一次,浪费资源;实际上就是把 spring 容器存到 ServletContext 中去了:

1
2
3
4
5
6
7
8
9
10
<!-- 指定spring的配置文件,默认从web根目录寻找配置文件,我们可以通过spring提供的classpath:前缀指定从类路径下寻找 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>

<!-- 对Spring容器进行实例化 Spring容器放在application范围 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

到这里基本就整合完了,下面要做的就是测试一下然后进行开发了,测试也非常的简单

4.测试

创建个 Action 访问下测试就可以了,比如可以这样写一个测试类

1
2
3
4
5
6
7
8
9
10
@Service
public class EmployeeAction {
// 依赖注入
@Resource EmployeeService employeeService;

public String execute(){
ActionContext.getContext().put("empList", employeeService.list());
return "list";
}
}

OK,到这里基本就真的完成了,如果不出问题的话。。。。

所谓整合

我们所说的整合一般就是:
Spring 整合 Hibernate :IOC 容器(Spring 框架的核心)管理 Hibernagte 的 SessionFactory ;以及让 Hibernate 使用上 Spring 的声明式事务
Spring 整合 Struts :让 spring 来接管 Struts 的 Action

在服务器启动的时候自动创建相应的 Action,如果想要解决高并发问题可以利用 spring 的 scope 属性(scope="prototype"),让其每一个请求产生独立的 Action,避免数据的混乱,当然它的生命周期是很短的

补充

来说一下为什么要在 web.xml 中配置实例化 Spring,如果不实例化那么在 Action 被访问的时候是必须要实例化的(因为 Action 要用啊),如果有很多人访问就会多次执行生成 ApplicationContext 的代码,这个是非常耗费资源的
所以不如索性在服务器启动的时候就把 ApplicationContext 给实例化了,存到 ServletContext 中,需要的时候可以使用下面的代码获取:

1
2
3
ApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
// 还有一种形式是
ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);

更好的,可以通过继承 ApplicationContextAware 来构造一个 spring 的工具类,别忘了让 spring 把这个工具类顺便给实例化了,这样直接调用就可以了

或者可以直接使用 spring 提供的:
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
进而使用 wac 来获取 bean

WebApplicationContext:
其实这个接口不过是 applicationContext 接口的一个子接口罢了,只不过说它的应用形式是 web 罢了. 它在ApplicationContext 的基础上,添加了对 ServletContext 的引用(后面还加入了很多其他作用域的引用),即 getServletContext 方法.
可以说:它是一个是支持 web 特性的 BeanFactory。

相关源码:

1
2
3
public interface WebApplicationContext extends ApplicationContext {
ServletContext getServletContext();
}

在配置 Hibernate 数据库信息的时候,就是 datasource 的时候,可以从 prop 文件中装载的,需要配一个:
<context:property-placeholder location="classpath:db.properties"/> 属性而已
这样可以在 datasource 中使用:value="${user}" 进行引用;
同理,配置 SessionFactory 的时候也可以引用外部文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<property name="configLocations" value="classpath:hinernate.cfg.xml" />
<!-- 上下的作用、内容都是一样的 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="dialect">org.hibernate.dialect.MySQLDialect</prop>
<!-- 一定不要忘记,要不然 getCurrentSession 是获取不到的 -->
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate5.SpringSessionContext</prop>
<prop key="hibernate.connection.url">jdbc:mysql://localhost:3306/ssh</prop>
<prop key="hibernate.connection.driver_class">com.mysql.jdbc.Driver</prop>
</props>
</property>

这样 cfg 文件中就没必要写数据库连接的相关东西了,因为会注入一个连接池,只需要写 hibernate 特有的一些配置就 OK 了
至于是使用外部文件好还是直接写在一起好,我也不造….


再补充 classpath 是指 WEB-INF 文件夹下的 classes 目录;
classpath 和 classpath* 区别:

  • classpath:只会到你的 class 路径中查找找文件;
  • classpath*:不仅包含 class 路径,还包括 jar 文件中( class 路径)进行查找.

也就是说前者只会从第一个 classpath 中加载,而后者会从所有的 classpath 中加载,用 classpath* 需要遍历所有的 classpath,所以加载速度是很慢的,因此,在规划的时候,应该尽可能规划好资源文件所在的路径,尽量避免使用 classpath*
另外,"**/" 表示的是任意目录;**/applicationContext-*.xml" 表示任意目录下的以 “applicationContext-“ 开头的 XML 文件。


千万注意 jar 包的重复和未部署 jar 包的巨坑,详细的解决笔记在我的 Github

参考&拓展

http://www.cnblogs.com/doit8791/p/5757798.html
http://blog.csdn.net/hzy38324/article/details/44139609
spring获取bean的几种方式
https://essviv.github.io/2016/07/02/spring/spring-differerce-between-application-context-and-servlet-context/
http://www.cnblogs.com/panxuejun/p/5898540.html

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~