ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SSH2整合实战:Struts2+Spring+Hibernate配置链路与避坑指南

2026/9/7 7:21:16 拓冰建站 浏览量
SSH2整合实战:Struts2+Spring+Hibernate配置链路与避坑指南 简介一份面向Java Web开发者、详解Struts2SpringHibernate三大框架整合的实操资源。内容涵盖SSH2分层整合思路、MyEclipse环境下的配置与调试并附有可直接运行的完整示例代码。压缩包共54个文件以jar依赖包、xml配置文件为主同时包含java源码、class编译文件、jsp页面、properties配置及doc说明文档整体约14.71MB结构清晰便于对照学习。已有622人学习下载。源码中Action、Service、DAO各层实现齐全配合applicationContext.xml、struts.xml、hibernate.cfg.xml等核心配置可直观理解HTTP请求如何流转、业务逻辑如何调用、数据库映射如何完成doc文档则对整合过程做了进一步梳理。学习者既能借此完成环境搭建也可深入分析框架间的协作细节适合正在学习SSH2整合或准备相关项目开发的人群。 读到 SSH2 这个组合名Java Web 老开发应该都不陌生。它就是 Struts2 Spring Hibernate 三大框架的整合方案在 Spring Boot 还没普及的年代这套组合几乎就是国内企业级 Web 项目的标准答案银行、教育、医疗、政企类系统的存量代码里到今天仍有大量业务跑在它上面。我最近接手一个遗留系统改造重新把三者串了一遍发现很多细节看着简单实际排序错了、类路径错了、作用域忘了配都会直接起不来服务。这篇博文从整合思路、配置链路、分层源码、避坑心得四个角度展开源码部分给了可以直接落地的骨架工程适合正在维护老系统的开发、刚学完三个框架想串起来的学生以及需要在本地快速搭一套可运行 Java Web 环境的新人。如果你想快速跑通直接按第三步的配置抄即可。1. 项目概述与整合思路1.1 三大框架各管哪一段SSH2 之所以能把三个独立的框架拼成一个完整应用核心原因是职责划分得很清楚。Struts2 是前台调度员负责接收 HTTP 请求、解析参数、调用业务逻辑、跳转页面。它靠 struts.xml 里的 action 映射来决定一个 URL 该进哪个类哪个方法。Spring 是容器管家所有业务对象都放进它的 IoC 容器里管理对象与对象之间通过依赖注入关联起来。事务也主要由 Spring 的声明式事务来控制一个 Transactional 就能让 Service 下的多个 DAO 操作要么全成功、要么全回滚。Hibernate 是对象和数据库之间的翻译官负责把 Java 对象映射成数据库表记录也就是 ORM。它自带缓存、懒加载、级联策略写数据层时可以少写大量 JDBC 代码。把三者拆开看每个框架单独用都不难难的是整合后谁该创建谁、谁该管理谁、谁该在什么时机销毁。很多人把三个框架的 jar 包塞进工程后Action 里直接new Service()Service 里又new DAO()表面上能用实际上 Spring 完全接管不到对象生命周期事务失效、Session 关闭异常、内存泄漏都是这么来的。1.2 版本与工程的合理搭配SSH2 最讲究版本匹配。我这次使用的组合是Spring 4.3.30.RELEASE、Hibernate 5.2.18.Final、Struts 2.5.30配合 MySQL 5.7 和 mysql-connector-java 5.1.49JDK 1.8。这套搭配很成熟网上资料也最多遇到兼容性问题容易查到答案。不建议用太新的 Spring 5.x Hibernate 5.4 来搭 SSH2。Spring 5 的升级导致部分旧 API 被移除Struts2 的 spring 插件需要同步升级Hibernate 5.4 对 Session 生命周期和事务策略的改动也更激进实际调试成本会高出不少。如果只是学习和维护老项目没必要自我折腾。工程骨架建议用 Maven 管理依赖把 struts2-core、struts2-spring-plugin、spring-context、spring-orm、hibernate-core、mysql-connector-java 这几个核心依赖加上剩下的交给 Maven 传递依赖解析。注意 struts2-spring-plugin 必须引入否则 Struts2 无法识别 Spring 容器里创建的 Action。2. 整合的核心配置逐段拆解2.1 web.xml 是整条启动链路的入口web.xml 决定整个 Web 应用启动时先做什么、后做什么。SSH2 整合后web.xml 需要承担三件事加载 Spring 的 applicationContext.xml、注册 Struts2 的前端控制器 Filter、加上请求编码过滤器。配置顺序有讲究。ContextLoaderListener 要最先让 Tomcat 在启动阶段完成 Spring 容器的初始化后续 Struts2 在处理请求时才能从容器里拿到业务 Bean。Struts2 在 2.5 版本的过滤器类路径是org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter旧版本则是在dispatcher.ng.filter包下写错就是 ClassNotFoundException这一条最容易踩。context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping filter filter-namestruts2/filter-name filter-classorg.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter/filter-class /filter filter-mapping filter-namestruts2/filter-name url-pattern/*/url-pattern /filter-mapping编码过滤器我习惯放在 Struts2 之前。如果反了Struts2 先拿到未解码的参数后面的 CharacterEncodingFilter 已经处理不了已经进入框架的参数内容中文乱码问题会非常顽固。2.2 applicationContext.xml 中如何注册 SessionFactorySpring 接管 Hibernate 的关键是在 applicationContext.xml 里注册一个LocalSessionFactoryBean。这个 Bean 会把数据源、实体扫描路径、Hibernate 的各种属性统一收编之后真正使用 Session 的地方只需要注入 SessionFactory 就行。我习惯把数据源、SessionFactory、事务管理器都写在一个配置文件里分包 Scan 实体类。packagesToScan指的是实体类所在的包路径Spring 会自动去扫描带 Entity 注解的类省掉老式 hibernate.cfg.xml 里一条条 mapping 的麻烦。bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/ssh2_demo?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /bean bean idsessionFactory classorg.springframework.orm.hibernate5.LocalSessionFactoryBean property namedataSource refdataSource/ property namepackagesToScan valuecom.demo.po/ property namehibernateProperties props prop keyhibernate.dialectorg.hibernate.dialect.MySQL5Dialect/prop prop keyhibernate.show_sqltrue/prop prop keyhibernate.format_sqltrue/prop prop keyhibernate.hbm2ddl.autoupdate/prop prop keyhibernate.current_session_context_classorg.springframework.orm.hibernate5.SpringSessionContext/prop /props /property /bean bean idtransactionManager classorg.springframework.orm.hibernate5.HibernateTransactionManager property namesessionFactory refsessionFactory/ /bean注意hibernate.current_session_context_class这一项指的是当前线程上下文里的 Session 由谁管理。在集成 Spring 时要指定为SpringSessionContextSpring 才能把当前线程的 Session 与事务绑定在一起。如果不配运行时会经常出现getCurrentSession抛异常的问题。hbm2ddl.auto在学习和测试阶段可以配成 update让 Hibernate 自动建表改表但生产环境建议换成 validate 或直接关闭避免框架在启动时对正式表结构做不可控调整。2.3 struts.xml 与 Struts2 的 Spring 插件Struts2 整合 Spring 后struts.xml 里 action 的 class 不再写类的全限定名而是写 Spring 容器中对应 Bean 的名字。为什么因为 Spring 接管了 action 对象的创建权Struts2 一旦收到请求会通过 struts2-spring-plugin 去容器里按 id 查找 Bean。struts constant namestruts.objectFactory valuespring/ package namedefault extendsstruts-default action nameuser_list classuserAction methodlist result namesuccess/list.jsp/result /action /package /struts这里的 class 是userAction对应 Spring 容器中 id 为 userAction 的 Bean而不是com.demo.action.UserAction这个类。如果写的是类名请求进来时 Struts2 直接 new 一个 Action 对象完全绕过 SpringAction 里注入的 Service 字段就全是 null报错极诡异。顺便提一句struts.objectFactory其实会被 struts2-spring-plugin 自动改成 spring但我会显式写出来让别人看配置时一眼能看出整合点。3. 源码结构与各层实现要点3.1 实体与 DAO 层的两种写法先看实体类最简单的做法是加 JPA 注解一行 Entity 加一行 Table 就能让 Hibernate 识别。字段直接用 Column 映射即可没必要每个字段都加复杂约束。写 DAO 层时有两条路线继承HibernateDaoSupport配合 HibernateTemplate或者直接注入 SessionFactory。早期 SSH 项目大量使用前者但 Spring 3.0 之后官方逐渐不再推荐 HibernateTemplateHibernate 5.x 后更是明显边缘化。我建议直接注入 SessionFactory代码直观debug 时也容易定位问题。Repository public class UserDao { Autowired private SessionFactory sessionFactory; SuppressWarnings(unchecked) public ListUser list() { return sessionFactory.getCurrentSession() .createCriteria(User.class) .list(); } public void save(User user) { sessionFactory.getCurrentSession().saveOrUpdate(user); } }Repository能让 Spring 自动扫描并创建 DAO 实例同时把持久层异常转换成 Spring 的DataAccessException。为什么用getCurrentSession而不是openSessiongetCurrentSession 拿到的 Session 会被绑定到当前事务上由 Spring 统一管理生命周期openSession 得自己手动关闭一旦在某些分支忘记 close连接就泄漏了。3.2 Service 层事务边界的设定Service 层是事务大事记里最该放 Transactional 的地方。DAO 层面通常只处理单表读写如果事务放在 DAO 上一次业务操作要调用多个 DAO 时每条 DAO 方法会各自开一个事务中间出了错根本回滚不了。正确做法是把事务边界放在 Service 方法上让一个业务操作整体成为一个事务。Service Transactional public class UserService { Autowired private UserDao userDao; public ListUser list() { return userDao.list(); } public void save(User user) { userDao.save(user); } }如果类上标了 Transactional类里所有方法都会进入事务默认不读操作也是只读事务。需要精确控制时可以移到方法上比如查询方法配置成Transactional(readOnly true)写入方法再用默认配置。这样做的目的不只是性能readOnly 提示对底层数据库优化器友好也能防止误操作把查询日志变成写入日志。事务管理器的注册方式在 2.2 节已经写了那句tx:annotation-driven是开关没有它 Transactional 就是普通注释不会产生任何效果。3.3 Action 类的 Spring 化与作用域选择Action 类接入 Spring 时有一个极易踩的坑作用域。Struts2 默认每次请求都会创建一个新的 Action 实例保证并发安全。Spring 管理的 Bean 默认是单例如果你不特意设置 Action 的 scope所有用户请求共用同一个 Action 对象Action 里的成员变量会被并发污染。最典型的症状就是A 用户查列表页面刷新后看到的是 B 用户的数据。解决办法是给 Action 标上Scope(prototype)每次请求都从 Spring 容器取一个新实例。Controller Scope(prototype) public class UserAction extends ActionSupport { Autowired private UserService userService; private ListUser userList; public String list() { userList userService.list(); return SUCCESS; } public ListUser getUserList() { return userList; } public void setUserList(ListUser userList) { this.userList userList; } }Service 和 DAO 保持单例没问题它们内部没有请求级别的状态信息SessionFactory、DataSource 这类资源线程安全。Action 必须 prototype这也是 SSH2 老项目里“Action 由 Spring 管理”看上去简单、实际上坑最深的地方。struts.xml 里 action 的 class 和 Spring 的 bean 名要保持一致。我习惯统一用小写驼峰比如 UserAction 类的 bean 名 userActionstruts.xml 里就写 userAction避免花时间排查名称对不上的问题。4. 常见报错与实战避坑4.1 启动期典型报错速查我把实际整合过程中最容易出现的启动期报错整理成一张速查表排查时直接对号入座。报错信息大概率原因解决方式ClassNotFoundException: dispatcher.ng.filter.StrutsPrepareAndExecuteFilterStruts2 版本与过滤器类路径不匹配2.5 以后用 dispatcher.filter.StrutsPrepareAndExecuteFilterBeanCreationException: sessionFactory 创建失败数据库连不上或实体包路径扫描不到检查 jdbc 连接参数、packagesToScan 路径NoSuchBeanDefinitionException: userActionstruts.xml 中 action 的 class 名没有对应 Spring Bean确认 Controller、Scope 注解、bean 名称plugin not found 或 Unable to load configurationstruts2-spring-plugin 缺失pom.xml 中补依赖jar 包冲突导致 ClassCastExceptionjavassist、antlr、slf4j 多版本互相打架统一版本并用 Maven 排除传递依赖启动问题大多数集中在“配置顺序”和“类路径”上。看到类加载相关错误时先检查是不是 jar 包被重复引入特别是 slf4j 和 javassist不同框架可能依赖不同版本Maven 依赖树里常能看到两份。4.2 运行期容易踩的坑启动没问题不代表整合就成功了运行期最经典的坑是 LazyInitializationException。这个报错发生在页面渲染时Hibernate 默认懒加载关联对象Service 层事务结束后 Session 关闭JSP 在页面上访问关联属性时 Session 已经不在了框架直接抛异常。解决思路有三条一是把关联对象的查询改成join fetch一次性把需要的数据拉回来二是在 web.xml 里配 OpenSessionInViewFilter把 Session 的生命周期延长到视图渲染结束三是干脆不用懒加载在一对一、多对一这种关联上用 EAGER 加载。我个人的习惯是优先用 join fetch毕竟 OpenSessionInViewFilter 会拉长数据库连接占用时间并发高时会明显拖垮数据库连接池。中文乱码又是一个高频坑。Struts2 默认只按application/x-www-form-urlencoded解析 POST 参数如果你的页面本身就是 UTF-8一定要保证前端页面编码、web.xml 的 CharacterEncodingFilter、数据库连接 URL 里的 characterEncoding 三处完全一致。数据库连接 URL 少写了useUnicodetruecharacterEncodingutf8即使前端和过滤器都对存进库里的中文照样乱。还有一个容易被忽略的是 Hibernate 缓存导致的“改库没生效”。启动时配了 hbm2ddl.autoupdate字段类型变了但缓存没刷新部分 SQL 会用旧元数据执行。这个在测试环境不常遇到但如果改过实体类后复现出一些莫名其妙的 SQL 错误先清理数据库里的表和 Hibernate 的二级缓存。4.3 老框架使用中的取舍我不建议在接手老系统后立刻把 SSH2 整体替换成 Spring Boot MyBatis改动面太大业务验证和回归测试的成本比想象中高得多。比较稳妥的做法是先把 SSH2 的整合链路理清楚让项目能稳定编译、正常启动、事务生效再以增量方式逐步替换。如果真要改造优先级我建议是先换连接池把原来的 DriverManagerDataSource 换成 Druid 或 HikariCP连接管理更规范排查 SQL 也能拿到监控数据再换 DAO 层从 Hibernate 迁到 MyBatis因为 DAO 层通常是最容易单元测试的边界最后才是把 Struts2 换成 Spring MVCAction 层涉及大量页面跳转和参数封装影响面最大放最后动。整个替换过程要保持各个框架的接口边界清晰才能做到改一层不牵动另外两层。如果没想清楚这一步很容易改到一半连二次开发都做不了。5. 源码说明与扩展建议5.1 源码包里有什么这次配套提供的源码是一个 Maven 工程目录结构如下ssh2-demo ├── pom.xml ├── src/main/java │ ├── com/demo/action/UserAction.java │ ├── com/demo/dao/UserDao.java │ ├── com/demo/service/UserService.java │ └── com/demo/po/User.java ├── src/main/resources │ ├── applicationContext.xml │ ├── struts.xml │ └── jdbc.properties └── src/main/webapp ├── WEB-INF/web.xml └── list.jsp跑起来的前提是你机器上有 JDK 8、Maven 3.5 以上、MySQL 5.7。把 jdbc.properties 里的用户名密码改成自己本机的启动 Tomcat 7 或 8访问http://localhost:8080/ssh2-demo/user_list.action就能看到一个简单的用户列表页面。数据表不用手动建Hibernate 的 hbm2ddl.auto 会自动帮你创建。由于博客不方便直接挂附件源码我打包放在网盘链接里拿到后直接mvn clean tomcat7:run即可启动。如果本地环境实在起不来优先检查 pom.xml 里 spring、struts、hibernate 三个版本是否与代码一致。5.2 后续还能怎么改这个骨架工程虽然小但已经覆盖了 SSH2 整合的完整链路。如果想把骨架变成真正的业务系统有几个值得动手的方向把单表 User 的业务扩成多表关联测试 Hibernate 的懒加载和缓存策略把用户列表改为分页查询在 DAO 层封装一个通用的分页工具方法给 Service 层加一层接口把事务边界和实现分离方便后续替换实现类。过去我接手完 SSH2 项目后的习惯是先在这个小骨架里把各种异常场景都试一遍比如故意把事务回滚、故意访问不存在的 action 映射看看整体框架的响应行为和日志输出。这样真正进老系统排查问题时心里对每一步链路都很有底。最后再分享一个我实测下来很管用的技巧给 SSH2 项目配置一个统一的启动日志级别把 log4j 的 rootLogger 调成 DEBUG然后逐步降到 INFO。调试整合期问题时要看 Spring 容器加载了哪些 Bean、Hibernate 执行的每一条 SQL等系统稳定后再把日志调回正常级别少走很多弯路。本文还有配套的精品资源点击获取