Spring 应用开发完全手册

ka520

贡献于2015-11-19

字数:0 关键词: Spring JEE框架 手册

图书在版编目(CIP)数据 Spring 应用开发完全手册/明日科技编著.—北京:人民邮电出版社, 2007.9 (程序开发从技术到实践) ISBN 978-7-115-16380-6 Ⅰ.S… Ⅱ.明… Ⅲ.计算机网络-程序设计 Ⅳ.TP393 中国版本图书馆 CIP 数据核字(2007)第 085515 号 内 容 提 要 本书以使用 Spring 框架技术开发网络应用程序的实用技术为中心,全面、系统地介绍了使用 Spring 框架技术开发应用程序时必须掌握的基础知识、高级应用技术、方法和开发过程。本书分为四篇,共 20 章。主要内容包括 Spring 框架开发环境的搭建及使用 Spring 时必须掌握的基础知识,Spring 的数据持 久化、事务管理以及企业应用中的远程调用,JNDI 命名服务,JMail 发送电子邮件等企业级服务,小型 网站或应用程序的开发思路、方法和典型应用模块,运用 Spring+Hibernate 开发校园管理系统,运用 Spring+Struts+Hibernate 开发企业门户网站,运用 Spring+Java Swing 开发企业进销存管理系统等。 本书附有配套光盘。光盘提供了书中所有实例、范例和案例程序的全部源代码,所有源代码都经过精 心调试,在 Windows 2003 下测试通过,保证能够正常运行。 本书内容精练、重点突出、实例丰富并且配有实例录像,是各级程序开发人员必备的参考书,本书也 非常适合大中专院校师生学习参考。 程序开发从技术到实践 Spring 应用开发完全手册 ♦ 编 著 明日科技 李钟尉 冯东庆 责任编辑 张立科 ♦ 人民邮电出版社出版发行 北京市崇文区夕照寺街 14 号 邮编 100061 电子函件 315@ptpress.com.cn 网址 http://www.ptpress.com.cn 北京鸿佳印刷厂印刷 新华书店总店北京发行所经销 ♦ 开本:787×1092 1/16 印张:33 字数:822 千字 2007 年 9 月第 1 版 印数:1 – 5 000 册 2007 年 9 月北京第 1 次印刷 ISBN 978-7-115-16380-6/TP 定价:59.00 元(附光盘) 读者服务热线:(010)67132692 印装质量热线:(010)67129223 前前 言言 随着软件分层体系结构的不断成熟,越来越多的开发人员开始使用框架技术。目前, 在 Web 领域,已经出现了许多优秀的 MVC 框架,Spring 作为一个开放源代码的企业级开 发框架,集诸多开发框架的设计思想之大成,简化了 J2EE 系统开发过程,越来越受到众 多程序员的青睐。为此,我们撰写了《Spring 应用开发完全手册》一书。如果想全面掌握 Spring,本书会成为您学习和实践的最佳帮手。 本书是“技术+实践”的完美结合。本书寓知识技巧于实践当中,令读者以轻松、高效 的方式掌握 Spring 的强大功能。 本书特点  本书系统地介绍了 Spring,面向入门者和希望进一步提高的爱好者。  突出实际工作的特点,让读者不仅仅学会 Spring,而且能够了解它在实际中的应用。  充分考虑了自学者的学习特点,图书结构编排新颖合理,可以帮助读者快速、轻 松地自学。  本书所附光盘除了配备了实例源程序,还提供了介绍开发工具的安装、调试、管 理和使用的视频录像,并有语音讲解,使读者真正做到一书在手,无师自通。  语言简洁,通俗易懂,深入浅出,对于同一操作,给出了多种的实现方法,使读 者可以学习到更多的知识。 本本书书导导读读 主要内容 本书以网站开发的实用技术为中心,以开发者为对象,以提高开发者的开发技能为宗 旨,全面、系统地介绍了使用 Spring 框架技术开发网络应用程序必须掌握的技术、方法和 过程。本书共分为以下四篇。 第一篇为基础篇,主要介绍 Spring 的环境搭建及使用 Spring 框架时必须掌握的基本技 术,包括 Spring 的 IoC 依赖注入、AOP 面向切面编程两大核心概念。 第二篇为 Spring 高级篇,主要介绍 Spring 的高级应用,包括在 Spring 中使用 DAO、 Hibernate 等技术实现数据持久化、事务管理,Spring 提供的 Web 框架、企业服务、远程服 务以及与其他框架的整合技术。 第三篇为典型实例篇,本篇精选了当前比较流行的典型实例程序,介绍小型网站或应 用程序的开发思路、方法和过程。内容包括用户信息维护、生成 Excel 文件、留言本、文 件上传、分页处理、企业通信软件、在线投票。 第四篇为项目实践篇,精选了当前应用较广泛的网络应用程序和网站,详细介绍了基 2 于 Spring+Hibernate 开发网站的基本思路、方法和过程。内容包括运用 Spring+Hibernate 开 发校园管理系统、运用 Spring+Java Swing 开发企业进销存管理系统和运用 Spring+Struts+ Hibernate 开发企业门户网站。 本书的约定 使用本书实例源程序时,应安装 JDK 1.5 开发包;Java Web 服务器安装 Tomcat 5.5; 如果实例后台数据库为 SQL Server 数据库,读者应安装 SQL Server 2000 数据库; 如果实例后台数据库为 MySQL 数据库,读者应安装 MySQL 5.0 数据库。 因篇幅限制,本书项目实践篇中的典型案例只给出了关键代码,完整代码参见光 盘源程序。 购买本书以后,读者可以得到由本书开发团队提供的稳定的技术支持,彻底解决 读者在学习过程中的后顾之忧。 联联系系我我们们 本书由明日科技组织编写,参加编写的人员有李钟尉、霍春明、顾彦玲、刘欣、刘玲 玲、黄锐等。由于作者水平有限,错漏之处在所难免,请广大读者批评指正。 为便于读者和本书作者沟通,明日科技将通过明日科技网站全面为读者提供网上服务和 支持。读者使用本书遇到的错误和问题,我们承诺在 6 个工作日内给您提供答复。 服务网站:www.mingrisoft.com 服务信箱:mingrisoft@mingrisoft.com 客服电话:0431-84978981 84978982 QQ 群服务:228651051 228651052 目目 录录 第第 11 篇篇 基基 础础 篇篇 第 1 章 初识 S pring................................................................................................................... 3 1.1 Spring 简介............................................................................................................................... 3 1.2 依赖注入................................................................................................................................... 4 1.3 AOP 编程.................................................................................................................................. 5 1.4 Spring 的 7 大模块 ................................................................................................................... 6 1.4.1 核心模块........................................................................................................................ 6 1.4.2 Context 模块 .................................................................................................................. 6 1.4.3 AOP 模块....................................................................................................................... 6 1.4.4 DAO 模块 ...................................................................................................................... 6 1.4.5 ORM 映射模块.............................................................................................................. 7 1.4.6 Web 模块........................................................................................................................ 7 1.4.7 MVC 模块...................................................................................................................... 7 第 2 章 S pring 环境搭建........................................................................................................... 8 2.1 安装 Java 基础环境.................................................................................................................. 8 2.1.1 JDK 1.5 的安装与配置.................................................................................................. 8 2.1.2 Tomcat 5.5 的安装 ....................................................................................................... 12 2.2 Spring 环境安装..................................................................................................................... 15 2.2.1 获得 Spring 发布包...................................................................................................... 15 2.2.2 安装 Spring .................................................................................................................. 16 2.3 在 Jbuilder 2006 中应用 Spring 框架 .................................................................................... 16 2.3.1 在 JBuilder 2006 中添加 Spring 库 ............................................................................. 16 2.3.2 创建工程并加载 Spring 库.......................................................................................... 18 2.4 在 Eclipse 中应用 Spring 框架............................................................................................... 20 2.4.1 安装插件 MyEclipse.................................................................................................... 20 2.4.2 创建工程...................................................................................................................... 23 2.4.3 添加 Spring 功能 ......................................................................................................... 24 2.5 Spring IoC 实例...................................................................................................................... 25 第 3 章 S pring 基础与容器..................................................................................................... 29 2 3.1 Spring IoC............................................................................................................................... 29 3.1.1 BeanFactory 介绍......................................................................................................... 29 3.1.2 ApplicationContext 介绍.............................................................................................. 30 3.2 使用 XML 装配 JavaBean...................................................................................................... 30 3.2.1 使用 XML 定义 JavaBean........................................................................................... 30 3.2.2 标签 ................................................................................................................ 31 3.2.3 标签........................................................................................................ 31 3.2.4 标签............................................................................................................... 32 3.2.5 标签.................................................................................................................. 32 3.2.6 如何配置 JavaBean...................................................................................................... 33 3.2.7 JavaBean 的赋值标签.................................................................................................. 37 3.2.8 匿名内部 JavaBean 的创建......................................................................................... 40 3.2.9 自动装配...................................................................................................................... 40 3.3 BeanFactory ............................................................................................................................ 44 3.3.1 BeanFactory 中 JavaBean 的生命周期........................................................................ 44 3.3.2 JavaBean 的预处理和后处理...................................................................................... 46 3.4 ApplicationContext ................................................................................................................. 49 3.4.1 容器的后处理方法 ...................................................................................................... 50 3.4.2 定制属性编辑器.......................................................................................................... 51 3.4.3 容器与 JavaBean 的耦合............................................................................................. 54 3.4.4 获得 BeanFactory 容器的引用.................................................................................... 54 3.4.5 获得 ApplicationContext 容器的引用......................................................................... 55 3.4.6 获得 JavaBean 在容器中的 ID 名称........................................................................... 55 3.4.7 实现 Spring 的国际化.................................................................................................. 56 3.4.8 事件的监听与发布 ...................................................................................................... 58 第 4 章 AOP 编程 .....................................................................................................................61 4.1 AOP 简介................................................................................................................................ 61 4.1.1 Spring AOP 概述.......................................................................................................... 62 4.1.2 Spring AOP 术语.......................................................................................................... 63 4.2 Spring AOP 的实现 ................................................................................................................ 64 4.2.1 实现简单的 AOP......................................................................................................... 64 4.2.2 创建代理...................................................................................................................... 65 4.3 Spring 的切入点..................................................................................................................... 66 4.3.1 静态切入点.................................................................................................................. 66 4.3.2 动态切入点.................................................................................................................. 67 4.3.3 静态切入点的优缺点 .................................................................................................. 67 4.3.4 深入静态切入点.......................................................................................................... 67 4.3.5 深入 Spring 切入点底层.............................................................................................. 68 4.3.6 自定义切入点.............................................................................................................. 68 4.3.7 Spring 常见的切入点................................................................................................... 69 3 4.4 创建通知................................................................................................................................. 69 4.4.1 Before 通知 .................................................................................................................. 69 4.4.2 Throws 通知................................................................................................................. 71 4.4.3 After 通知..................................................................................................................... 73 4.4.4 Around 通知................................................................................................................. 75 4.4.5 Introduction 通知 ......................................................................................................... 77 4.5 Spring 的 Advisor ................................................................................................................... 79 4.5.1 DefaultPointcutAdvisor................................................................................................ 79 4.5.2 NameMatchMethodPointcutAdvisor............................................................................ 80 4.6 使用 ProxyFactoryBean 创建代理......................................................................................... 80 4.7 使用自动代理......................................................................................................................... 83 4.7.1 BeanNameAutoProxyCreator 类.................................................................................. 83 4.7.2 DefaultAdvisorAutoProxyCreator 类........................................................................... 85 4.8 AOP 范例................................................................................................................................ 87 4.8.1 范例概述...................................................................................................................... 87 4.8.2 范例的特点.................................................................................................................. 89 4.8.3 范例应用的知识点 ...................................................................................................... 89 4.8.4 实现过程...................................................................................................................... 89 第第 22 篇篇 高高 级级 篇篇 第 5 章 数据持久化服务.......................................................................................................... 99 5.1 Spring 中 DAO 框架............................................................................................................... 99 5.1.1 数据访问对象 DAO 简介............................................................................................ 99 5.1.2 Spring 的 DAO 支持.................................................................................................. 100 5.1.3 DAO 的简单应用 ...................................................................................................... 100 5.2 Spring 中操作 JDBC............................................................................................................ 104 5.2.1 使用 JDBC 存在的问题 ............................................................................................ 104 5.2.2 一个简单的实例........................................................................................................ 105 5.2.3 核心类 JdbcTemplate 实现 JDBC 操作..................................................................... 107 5.2.4 以对象方式操作 JDBC ............................................................................................. 112 5.3 Spring 整合 Hibernate .......................................................................................................... 115 5.3.1 Hibernate 入门应用 ................................................................................................... 115 5.3.2 Spring 对 Hibernate 的支持....................................................................................... 119 第 6 章 事务管理.....................................................................................................................122 6.1 Spring 中的事务................................................................................................................... 122 6.2 事务的 ACID 特性............................................................................................................... 122 6.2.1 原子性(Atomicity)................................................................................................ 123 6.2.2 一致性(Consistency)............................................................................................. 123 6.2.3 隔离性(Isolation) .................................................................................................. 123 4 6.2.4 持久性(Durability)................................................................................................ 123 6.3 事务之间的缺陷................................................................................................................... 123 6.4 事务的属性........................................................................................................................... 123 6.4.1 事务的传播行为........................................................................................................ 124 6.4.2 事务的隔离级别........................................................................................................ 124 6.4.3 事务的只读属性........................................................................................................ 125 6.4.4 事务的超时属性........................................................................................................ 125 6.5 Spring 的事务管理器 ........................................................................................................... 125 6.5.1 定义 DataSource ........................................................................................................ 125 6.5.2 JDBC 事务管理器 ..................................................................................................... 126 6.5.3 Hibernate 事务管理器 ............................................................................................... 126 6.5.4 JDO 事务管理器........................................................................................................ 127 6.5.5 OJB 事务管理器........................................................................................................ 128 6.5.6 JTA 事务管理器......................................................................................................... 128 6.6 编程式事务........................................................................................................................... 128 6.6.1 使用 PlatformTransactionManager 接口 ................................................................... 128 6.6.2 使用 TransactionTemplate 模板................................................................................. 132 6.7 声明式事务........................................................................................................................... 133 6.7.1 优化 DataSource ........................................................................................................ 134 6.7.2 使用事务代理工厂 .................................................................................................... 134 第 7 章 远程服务.....................................................................................................................138 7.1 Spring 中提供的远程服务 ................................................................................................... 138 7.2 使用 Spring 的 RMI.............................................................................................................. 140 7.3 Hessian 和 Burlap 服务的实现 ............................................................................................ 143 7.4 HTTP Invoker 服务的实现................................................................................................... 144 7.5 Spring 实现 Web Service...................................................................................................... 145 第 8 章 企业级服务.................................................................................................................149 8.1 Java 命名服务....................................................................................................................... 149 8.1.1 JNDI 简介 .................................................................................................................. 149 8.1.2 从 JNDI 获取 DataSource.......................................................................................... 150 8.2 Java 电子邮件....................................................................................................................... 155 8.2.1 JavaMail 简介 ............................................................................................................ 155 8.2.2 Spring 的邮件发送器................................................................................................. 156 8.2.3 发送简单的文本邮件 ................................................................................................ 157 8.3 任务调度............................................................................................................................... 164 8.3.1 Spring 的 Java Timer 调度器..................................................................................... 164 8.3.2 Spring 的 Quartz 调度器............................................................................................ 166 第 9 章 S pring 的 W eb 框架..................................................................................................170 5 9.1 Spring 的 MVC 框架............................................................................................................ 170 9.1.1 Spring MVC 概述 ...................................................................................................... 170 9.1.2 Spring Web 入门实例 ................................................................................................ 170 9.1.3 配置 DispatcherServlet(分发器)........................................................................... 174 9.1.4 处理器映射与拦截器 ................................................................................................ 175 9.1.5 视图解析器................................................................................................................ 177 9.1.6 异常解析器................................................................................................................ 178 9.1.7 控制器简介................................................................................................................ 178 9.1.8 模型与视图................................................................................................................ 180 9.1.9 命令控制器................................................................................................................ 181 9.1.10 表单控制器与验证器 .............................................................................................. 181 9.1.11 多动作控制器 .......................................................................................................... 187 9.1.12 向导控制器.............................................................................................................. 191 9.1.13 ParameterizableViewController 控制器................................................................... 198 9.1.14 UrlFilenameViewController 控制器 ........................................................................ 199 9.1.15 使用 Web 应用上下文............................................................................................. 200 9.1.16 国际化信息.............................................................................................................. 201 9.1.17 文件上传.................................................................................................................. 206 9.2 使用 Spring 标签.................................................................................................................. 207 9.2.1 标签 ..................................................................................................... 207 9.2.2 标签............................................................................................... 208 9.3 使用 Tile 布局 ...................................................................................................................... 208 9.3.1 定义视图.................................................................................................................... 208 9.3.2 配置 Tiles ................................................................................................................... 209 第 10 章 集成 W eb 框架.........................................................................................................211 10.1 Struts 集成 .......................................................................................................................... 211 10.1.1 Struts 简介................................................................................................................ 211 10.1.2 Struts 简单实例........................................................................................................ 214 10.1.3 Spring 集成 Struts .................................................................................................... 217 10.1.4 Spring 集成 Struts 实例 ........................................................................................... 218 10.2 JSF 集成.............................................................................................................................. 221 10.2.1 JSF 简介及入门实例 ............................................................................................... 221 10.2.2 Spring 如何集成 JSF................................................................................................ 224 第第 33 篇篇 典典 型型 实实 例例 第 11 章 用户信息维护...........................................................................................................229 11.1 实例运行结果..................................................................................................................... 229 11.2 设计与分析......................................................................................................................... 230 11.2.1 系统分析 .................................................................................................................. 230 11.2.2 文件夹及文件架构 .................................................................................................. 230 6 11.3 技术要点............................................................................................................................. 231 11.3.1 配置视图解析器 ...................................................................................................... 231 11.3.2 解决中文乱码 .......................................................................................................... 231 11.4 开发过程............................................................................................................................. 231 11.4.1 数据表结构 .............................................................................................................. 231 11.4.2 使用表单控制器实现用户注册............................................................................... 232 11.4.3 数据访问对象的设计 .............................................................................................. 233 11.4.4 注册模块的设计 ...................................................................................................... 235 11.4.5 查询模块的设计 ...................................................................................................... 236 11.4.6 修改模块的设计 ...................................................................................................... 239 11.4.7 删除模块的设计 ...................................................................................................... 240 11.4.8 将请求映射到处理器 .............................................................................................. 240 11.4.9 配置 XML 文件 ....................................................................................................... 241 11.4.10 视图组件的实现 .................................................................................................... 241 11.5 发布与运行......................................................................................................................... 243 第 12 章 生成 E xcel文件 ......................................................................................................244 12.1 实例运行结果..................................................................................................................... 244 12.2 技术要点............................................................................................................................. 245 12.2.1 POI 简介................................................................................................................... 245 12.2.2 数据写入的创建过程 .............................................................................................. 246 12.2.3 如何设置字体和单元格样式 .................................................................................. 246 12.2.4 读取 Excel 单元格中的数据 ................................................................................... 247 12.2.5 如何在 Excel 表格中显示数据 ............................................................................... 248 12.3 开发过程............................................................................................................................. 248 12.3.1 创建实现类.............................................................................................................. 248 12.3.2 创建 ReadXlsController........................................................................................... 250 12.3.3 建立 bean_config.xml 文件 ..................................................................................... 252 12.3.4 配置 web.xml ........................................................................................................... 253 12.4 发布与运行......................................................................................................................... 254 第 13 章 留言本 ......................................................................................................................255 13.1 实例运行结果..................................................................................................................... 256 13.2 设计与分析......................................................................................................................... 256 13.2.1 系统分析.................................................................................................................. 256 13.2.2 系统流程.................................................................................................................. 256 13.2.3 文件夹及文件架构 .................................................................................................. 257 13.3 技术要点............................................................................................................................. 257 13.3.1 如何获取日期.......................................................................................................... 257 13.3.2 如何从数据库查询出无重复标题 .......................................................................... 257 13.3.3 如何自动录入日期和时间 ...................................................................................... 258 7 13.3.4 保存用户登录信息 .................................................................................................. 258 13.3.5 如何判断用户是否登录 .......................................................................................... 258 13.3.6 临时中文资源的编码转换 ...................................................................................... 259 13.4 开发过程............................................................................................................................. 260 13.4.1 数据表结构.............................................................................................................. 260 13.4.2 创建控制器.............................................................................................................. 260 13.4.3 创建数据访问对象 .................................................................................................. 265 13.4.4 创建 service 类......................................................................................................... 265 13.4.5 创建 Daily 类 ........................................................................................................... 266 13.4.6 国际化的支持.......................................................................................................... 267 13.4.7 创建 model_bean.xml .............................................................................................. 270 13.4.8 创建 web_config.xml............................................................................................... 271 13.4.9 视图组件的实现 ...................................................................................................... 272 13.5 发布与运行......................................................................................................................... 273 第 14 章 文件上传...................................................................................................................274 14.1 实例运行结果..................................................................................................................... 274 14.2 设计与分析......................................................................................................................... 275 14.2.1 系统分析.................................................................................................................. 275 14.2.2 文件夹及文件架构 .................................................................................................. 275 14.3 技术要点............................................................................................................................. 276 14.3.1 指定上传文件的路径 .............................................................................................. 276 14.3.2 指定上传文件的大小 .............................................................................................. 276 14.3.3 上传文件.................................................................................................................. 277 14.4 开发过程............................................................................................................................. 277 14.4.1 数据表结构.............................................................................................................. 277 14.4.2 创建 bean_cogfig.xml 配置文件 ............................................................................. 277 14.4.3 创建简单 FileForm 类 ............................................................................................. 279 14.4.4 创建数据访问对象 UpLoadDao 类......................................................................... 279 14.4.5 创建上传控制器 ...................................................................................................... 280 14.4.6 查询上传信息.......................................................................................................... 281 14.4.7 创建视图组件.......................................................................................................... 281 14.5 调试、发布与运行............................................................................................................. 282 14.5.1 调试.......................................................................................................................... 282 14.5.2 发布与运行.............................................................................................................. 284 第 15 章 数据分页...................................................................................................................285 15.1 实例运行结果..................................................................................................................... 285 15.2 设计与分析......................................................................................................................... 286 15.2.1 系统分析.................................................................................................................. 286 15.2.2 工作流程图.............................................................................................................. 286 8 15.2.3 文件夹及文件架构 .................................................................................................. 287 15.3 技术要点............................................................................................................................. 287 15.3.1 使用命令控制器 ...................................................................................................... 287 15.3.2 如何获取当前页 ...................................................................................................... 288 15.3.3 如何确定本页需要显示记录的起始位置 .............................................................. 288 15.3.4 显示本页的所有记录 .............................................................................................. 288 15.3.5 如何将分页单位写在配置文件中 .......................................................................... 288 15.4 开发过程............................................................................................................................. 289 15.4.1 数据表结构.............................................................................................................. 289 15.4.2 创建 bean-config.xml............................................................................................... 289 15.4.3 创建 PageConfig 类 ................................................................................................. 291 15.4.4 创建 Action 类 ......................................................................................................... 292 15.4.5 FyAction 的程序流程以及模块 .............................................................................. 292 15.4.6 web.xml 文件的配置 ............................................................................................... 294 15.4.7 视图组件的实现 ...................................................................................................... 294 15.5 发布与运行......................................................................................................................... 296 第 16 章 企业通信软件...........................................................................................................297 16.1 实例运行界面..................................................................................................................... 298 16.2 设计与分析......................................................................................................................... 298 16.2.1 系统分析.................................................................................................................. 298 16.2.2 系统流程.................................................................................................................. 299 16.2.3 Spring 配置文件及类的分布................................................................................... 299 16.3 技术要点............................................................................................................................. 300 16.3.1 系统设计框架.......................................................................................................... 300 16.3.2 多线程...................................................................................................................... 301 16.3.3 Socket 编程 .............................................................................................................. 301 16.4 开发过程............................................................................................................................. 302 16.4.1 数据表结构.............................................................................................................. 302 16.4.2 创建 Spring 配置文件.............................................................................................. 303 16.4.3 获取容器实例及底层操作类 .................................................................................. 304 16.4.4 用户登录逻辑处理 .................................................................................................. 305 16.4.5 发送、接收消息 ...................................................................................................... 309 16.5 运行..................................................................................................................................... 312 第 17 章 在线投票系统...........................................................................................................313 17.1 系统介绍............................................................................................................................. 313 17.2 技术要点............................................................................................................................. 314 17.2.1 防止用户重复投票 .................................................................................................. 314 17.2.2 图形方式显示投票结果 .......................................................................................... 315 17.2.3 获取用户的投票 ...................................................................................................... 317 9 17.3 系统设计............................................................................................................................. 317 17.3.1 系统功能结构.......................................................................................................... 317 17.3.2 数据库设计.............................................................................................................. 318 17.4 系统总体架构设计............................................................................................................. 319 17.4.1 Web 文件架构设计.................................................................................................. 319 17.4.2 类文件架构设计 ...................................................................................................... 319 17.5 开发过程............................................................................................................................. 319 17.5.1 系统文件配置.......................................................................................................... 319 17.5.2 数据库操作的核心类设计 ...................................................................................... 322 17.5.3 系统登录模块设计 .................................................................................................. 323 17.5.4 投票信息维护.......................................................................................................... 328 17.5.5 投票选项模块维护 .................................................................................................. 335 17.5.6 投票信息显示模块 .................................................................................................. 343 17.5.7 投票结果显示模块 .................................................................................................. 345 17.6 部署..................................................................................................................................... 347 第第 44 篇篇 项项 目目 实实 践践 第 18 章 校园管理系统...........................................................................................................351 18.1 需求分析............................................................................................................................. 351 18.2 系统设计............................................................................................................................. 352 18.2.1 项目规划.................................................................................................................. 352 18.2.2 功能结构分析.......................................................................................................... 352 18.3 数据库设计......................................................................................................................... 353 18.3.1 数据表概要说明 ...................................................................................................... 353 18.3.2 主要数据表的结构 .................................................................................................. 353 18.4 系统总体架构设计............................................................................................................. 355 18.4.1 Web 文件架构设计.................................................................................................. 355 18.4.2 类文件架构设计 ...................................................................................................... 356 18.4.3 页面效果图.............................................................................................................. 357 18.5 系统配置与公共类的设计................................................................................................. 358 18.5.1 系统文件配置.......................................................................................................... 358 18.5.2 数据库操作的核心类设计 ...................................................................................... 361 18.6 系统登录模块设计............................................................................................................. 362 18.6.1 编写 Hibernate 实体类及映射文件......................................................................... 363 18.6.2 页面设计.................................................................................................................. 364 18.6.3 控制器设计.............................................................................................................. 365 18.6.4 xml 信息配置........................................................................................................... 366 18.7 代码维护模块设计............................................................................................................. 367 18.7.1 总体架构设计.......................................................................................................... 367 18.7.2 班级代码维护模块设计 .......................................................................................... 368 10 18.8 档案管理模块设计............................................................................................................. 375 18.8.1 总体架构设计.......................................................................................................... 375 18.8.2 学生入校登记设计 .................................................................................................. 376 18.8.3 学生信息维护设计 .................................................................................................. 381 18.8.4 学生登记查询设计 .................................................................................................. 385 18.9 成绩管理模块设计............................................................................................................. 387 18.9.1 总体架构设计.......................................................................................................... 387 18.9.2 成绩录入模块设计 .................................................................................................. 388 18.9.3 成绩查询模块设计 .................................................................................................. 393 18.9.4 班级成绩统计.......................................................................................................... 395 18.10 教职工管理模块设计....................................................................................................... 399 18.10.1 教职工管理功能模块总体架构 ............................................................................ 399 18.10.2 任班主任功能设计 ................................................................................................ 399 18.10.3 班主任查询功能设计 ............................................................................................ 405 18.11 图书馆管理模块设计....................................................................................................... 408 18.11.1 图书馆管理功能模块总体架构............................................................................. 408 18.11.2 图书维护功能设计 ................................................................................................ 409 18.11.3 图书借阅功能设计 ................................................................................................ 413 第 19 章 企业进销存管理系统...............................................................................................418 19.1 需求分析............................................................................................................................. 418 19.2 系统设计............................................................................................................................. 419 19.2.1 项目规划.................................................................................................................. 419 19.2.2 功能结构分析.......................................................................................................... 419 19.3 数据库设计......................................................................................................................... 420 19.3.1 数据表概要说明 ...................................................................................................... 420 19.3.2 主要数据表的结构 .................................................................................................. 420 19.4 系统总体架构设计............................................................................................................. 422 19.4.1 文件架构设计.......................................................................................................... 422 19.4.2 页面效果图.............................................................................................................. 423 19.5 系统配置与公共类的设计................................................................................................. 424 19.5.1 数据库操作的核心类 UserDAO 设计 .................................................................... 424 19.5.2 Spring 的 XML 配置文件........................................................................................ 425 19.6 系统登录模块设计............................................................................................................. 426 19.7 基本信息模块设计............................................................................................................. 432 19.7.1 基本信息模块总体架构 .......................................................................................... 432 19.7.2 商品添加功能设计 .................................................................................................. 433 19.7.3 商品查询功能设计 .................................................................................................. 439 19.8 商品销售模块设计............................................................................................................. 442 19.8.1 商品销售功能模块总体架构 .................................................................................. 442 19.8.2 商品销售功能设计 .................................................................................................. 443 11 19.8.3 销售查询功能设计 .................................................................................................. 451 19.9 库存管理模块设计............................................................................................................. 455 19.9.1 库存管理功能模块总体架构 .................................................................................. 455 19.9.2 商品入库功能设计 .................................................................................................. 455 19.9.3 库存查询功能设计 .................................................................................................. 463 19.9.4 库存价格调整功能设计 .......................................................................................... 471 第 20 章 企业门户网站...........................................................................................................476 20.1 需求分析............................................................................................................................. 476 20.2 系统设计............................................................................................................................. 477 20.3 数据库设计......................................................................................................................... 477 20.3.1 数据表概要说明 ...................................................................................................... 477 20.3.2 主要数据表的结构 .................................................................................................. 477 20.4 系统总体架构设计............................................................................................................. 478 20.4.1 类文件架构设计 ...................................................................................................... 478 20.4.2 页面效果图.............................................................................................................. 479 20.5 系统配置与公共类的设计................................................................................................. 479 20.5.1 系统文件配置.......................................................................................................... 479 20.5.2 数据库操作的核心类设计 ...................................................................................... 481 20.6 网站前台设计..................................................................................................................... 483 20.6.1 公司简介模块设计 .................................................................................................. 483 20.6.2 产品介绍模块设计 .................................................................................................. 484 20.6.3 公司荣誉模块设计 .................................................................................................. 489 20.6.4 售后服务模块设计 .................................................................................................. 493 20.6.5 技术支持模块设计 .................................................................................................. 493 20.7 网站后台设计..................................................................................................................... 497 20.7.1 系统登录模块设计 .................................................................................................. 497 20.7.2 产品管理模块设计 .................................................................................................. 501 20.7.3 其他管理模块.......................................................................................................... 512 20.8 发布与运行......................................................................................................................... 512 1 第 1 篇 基础篇 第 1 章 结识 Spring 第 2 章 Spring 环境搭建 第 3 章 Spring 基础与容器 第 4 章 AOP 编程 主要内容  S pring 开发环境的搭建  在 E clipse 上创建 S pring 实例  在 JB uilder上创建 S pring 实例  应用 S pring 管理 JavaB ean  应用 Spring 注入 JavaBean 依赖 属性  应用 S pring 实现面向切面编程 工欲善其事 必先利其器 第 1 章 初识 Spring 本章带领读者结识 Spring,了解 Spring 的特点、Spring 的 IoC 与 AOP,并对Spring 的核心模块、Context 模块、AOP 模块、DAO 模块、 O/R 模块、Web 模块、MVC 模块等 7 个模块做了简单描述。 本章主要内容如下。  Spring 简介  Spring 的 7 大模块 1.1 Spring 简介 Spring 是一个开源框架,由 Rod Johnson 创建,从 2003 年初正 式启动。它能够降低开发企业级应用程序的复杂性,可以使用 Spring 替代 EJB 开发企业级应用,而不用担心工作量太大、开发进度难以控 制和测试过程复杂等问题。Spring 简化了企业应用的开发、降低了 开发成本、能够整合各种流行框架。它以 IoC(反向控制)和 AOP(面 向切面编程)两种先进的技术为基础,完美地简化了企业级开发的复 杂度。 Spring 的目标是成为一个全方位的应用框架,它会涉及到一些概 念和术语,为了让初学者能够深入了解 Spring,下面介绍 Spring 中 涉及到的概念。 1.轻量级框架 相对于 EJB 之类的(重量级容器)框架而言,Spring 的核心包体 积小、占用的系统资源少、可以用于移动设备的程序开发,也可以用 于应用程序的中间件。它是一个轻量级的框架,是开源的,并且是非 侵入式的优秀框架。 4 第 1 篇 基 础 篇 P A R T 1 2.IoC 容器 Spring 最重要的核心概念是它所提倡的 IoC(控制反转,也称依赖注入)容器。IoC 容器改变了类属性的赋值方式,从容器中主动把依赖的属性注入给类的实例对象,而不是 创建对象时再去寻找依赖的属性。这样,Java 类不需要管属性的赋值问题,把解偶的问题 完全交给了 IoC 容器去管理。 3.AOP 实现 面向切面编程(AOP)是 Spring 的又一强大功能。它可以将程序的业务代码和系统服 务代码(如事务管理、日志记录等)分离开,在业务逻辑完全不知道的情况下为其提供系 统服务。这样业务逻辑只需要负责和业务处理有关的操作,不用关心系统问题,实现了内 聚式的开发方式。 4.数据持久层 Spring 并没有实现自己的持久化方案,它集合了现有的流行持久框架,例如: Hibernate、JDO、iBATIS 等框架。另外,对于喜欢用JDBC 完成数据持久化的程序员,Spring 采用 JDBC 模板封装了 JDBC 的数据库操作,使程序员能以方便的方式使用 JDBC。 5.Spring 的 Web 框架 Spring 提供的 MVC 框架的解决方案,可以完全使用 Spring 的 IoC 和 AOP 的能力,拥 有完善的控制器(Controller)继承架构,能够根据需求使用适当的控制器,使 Web 开发 更为方便。 6.与其他框架的集成 Spring 提供了对其他 Web 框架的整合,使程序开发可以选择适合的 MVC 框架,例如 Struts、WebWork、Tapestry、JSF 等。 7.其他企业级服务 除以上功能外,Spring 还封装了一些企业级服务,它们拥有一致的使用模式,在使用 上更为简化。这些企业服务包括:远程服务(Remoting)、发送电子邮件(E-mail)、JMS、 JNDI、Web Services 和任务调试等。 1.2 依 赖 注 入 IoC 的英文全称是“Inversion of Control”,即控制反转。它使程序组件或类之间尽 量形成一种松耦合的结构,开发者在使用类的实例之前,需要先创建对象的实例。但是IoC 将创建实例的任务交给 IoC 容器,这样开发应用代码时只需要直接使用类的实例,这就是 IoC 控制反转。通常用一个所谓的好莱坞原则 “Don't call me. I will call you.”(请 不要给我打电话,我会打给你)来比喻这种控制反转的关系。Martin Fowler 曾专门写了 一篇文章“Inversion of Control Containers and the Dependency Injection pattern” 5 C H A P T E R 1 第 1 章 初 识 S pring 讨论控制反转这个概念,并提出一个更为准确的概念,叫做依赖注入(Dependency Injection)。 Spring 框架中的各个部分都充分使用了依赖注入技术,它使代码中不再有单实例垃 圾,也不再有麻烦的属性文件,取而代之的是一致和幽雅的程序应用代码。 依赖注入有 3 种实现类型,Spring 支持后两种: (1)接口类型:基于接口将调用与实现分离。这种依赖注入方式必须实现容器所规定 的接口,使程序代码和容器的 API 绑定在一起。这不是理想的依赖注入方式。 (2)赋值类型:基于 JavaBean 的 Set()方法为属性赋值。这种类型在实际开发中得到 了最广泛的应用(其中很大一部分得利于 Spring 框架的影响)。例如: public class SeterExample { private String field1; public String getField1(){ return field1; } public void setField1(String field1){ this.field1 = field1; } } 在上述代码中定义了一个字段属性“field”并且使用了 get()方法和 set()方法,这 两个方法可以为字段属性赋值。 (3)构造类型:基于构造方法为属性赋值。容器通过调用类的构造方法,将其所需的 依赖关系注入其中。例如: public class SeterExample { private String field1; public SeterExample(String fieldVar){ this.field1 = fieldVar; } } 在上述代码中使用构造方法为属性赋值,这样做的好处是,在实例化类对象的同时就 完成了属性的初始化。 1.3 AOP 编程 AOP 是 Aspect Oriented Programming 的缩写,意思是面向切面编程。面向切面的编 程是一种新的编程技术,追求的是调用者和被调用者之间的解偶,它弥补了面向对象编程 (OOP)在跨越模块行为上的不足。AOP 引进了 Aspect 概念,它将多个类的重复代码封装到 一个可重用模块中,允许程序员把横切关注点模块化,进而消除面向对象编程引起的代码 混乱和分散问题,增强系统的可维护性和代码的可重用性,如安全、事务、日志等横切关 注。当系统开发变得越来越庞大时,就可以使用 AOP 轻松地解决横切关注点和调用者与被 调用者之间的解耦问题。 AOP 是 Spring 框架的一个关键技术,它和 Spring 的 IoC 技术一样重要,虽然 AOP 进 6 第 1 篇 基 础 篇 P A R T 1 一步完善了 Spring 的 IoC 容器,但 Spring 的 IoC 容器并不依赖于 AOP,这意味着如果没 有必要可以不使用它。 AOP 在 Spring 中的使用:  提供声明式企业服务,可以作为 EJB 声明式服务的替代品,其中最重要的是声明 式事务管理,它建立在 Spring 事务管理抽象之上;  自定义的切面,用 AOP 进一步完善 OOP 的使用。 1.4 Spring 的 7 大模块 Spring 框架主要由 7 大模块组成,它们提供了企业级开发需要的所有功能,而且每个 模块既可以单独使用,也可以和其他模块组合使用,灵活且方便的部署可以使开发的程序 更加简洁灵活。图 1.1 所示是 Spring 的 7 个模块的部署。 图 1.1 Spring 的 7 个模块 1.4.1 核心模块 Spring Core 模块是 Spring 的核心容器,它实现了 IoC 模式,提供了 Spring 框架的 基础功能。此模块中包含的 BeanFactory 类是 Spring 的核心类,负责 JavaBean 的配置与 管理。它采用 Factory 模式实现了 IoC 容器即依赖注入。 1.4.2 Context 模块 Spring Context 模块继承 BeanFactory(或者说 Spring 核心)类,并且添加了事件处理、 国际化、资源装载、透明装载以及数据校验等功能。它还提供了框架式的 Bean 的访问方式和很 多企业级的功能,如 JNDI 访问、支持 EJB、远程调用、集成模板框架、E-mail 和定时任务调度 等。 1.4.3 AOP 模块 Spring 集成了所有 AOP 功能。通过事务管理可以使任意 Spring 管理的对象 AOP 化。 Spring 提供了用标准 Java 语言编写的 AOP 框架,它的大部分内容都是基于 AOP 联盟的 API 开发的。它使应用程序抛开 EJB 的复杂性,但拥有传统 EJB 的关键功能。 7 C H A P T E R 1 第 1 章 初 识 S pring 1.4.4 DAO 模块 DAO 模块提供了 JDBC 的抽象层,简化了数据库厂商的异常错误(不再从SQLException 继承大批代码),大幅度减少代码的编写,并且提供了对声明式事务和编程式事务的支持。 1.4.5 ORM 映射模块 Spring ORM 模块提供了对现有 ORM 框架的支持,各种流行的 ORM 框架已经做得非常成 熟,并且拥有大规模的市场(例如 Hibernate)。Spring 没有必要开发新的 ORM 工具,它对 Hibernate 提供了完美的整合功能,同时也支持其他 ORM 工具。 1.4.6 Web 模块 Spring Web 模块建立在 Spring Context 基础之上,它提供了Servlet 监听器的 Context 和 Web 应用的上下文。对现有的 Web 框架,如 JSF、Tapestry、Struts 等,提供了集成。 1.4.7 MVC 模块 Spring Web MVC 模块建立在 Spring 核心功能之上,这使它能拥有 Spring 框架的所有 特性,能够适应多种多视图、模板技术、国际化和验证服务,实现控制逻辑和业务逻辑的 清晰分离。 第 2 章 Spring 环境搭建 随着各种开发工具的诞生、使用和更新,IDE 环境已经成为开发 者必不可少的辅助开发工具。在这些开发工具中,JBuilder 和 Eclipse 非常流行,拥有众多的支持者。为方便广大读者使用自己的开发工具, 本章将分别对在普通开发环境和这两种开发工具中如何应用 Spring 框架作简单介绍。主要内容如下。  Spring 资源包  在 JBuilder 2006 中应用 Spring 框架  在 Eclipse 中应用 Spring 框架  Spring IoC 实例 2.1 安装 Java 基础环境 Spring 是一个 Java 技术的产品,它必须运行在 Java 环境中, 所以在搭建 Spring 开发环境之前,需要安装相关的 Java 平台和 Tomcat 服务器以提供基本的技术平台,本书使用了 JDK 1.5 和 Tomcat 5.5 作为 Java 基础开发环境。下面对它们的安装和配置作 简单介绍。 2.1.1 JDK 1.5 的安装与配置 Spring 框架是采用 Java 语言编写的,因此它的应用与运行需要 JDK(Java Development Kits)开发工具的支持。本节将介绍如何在 Windows 2000 操作系统下安装与配置 Java 的 JDK 1.5 开发平台。应 用 JDK 的版本为“jdk-1_5_0_07”,读者可以从 http://java.sun.com 网站下载。 视频录像:.....mr..\.02..\.lx..\.01..\.JDK1.5......的安装录像......exe.... 9 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 1.安装 JDK 1.5 在安装 JDK 1.5 之前,请确认系统中没有安装其他的 JDK,如果安装多个 JDK,在配置 开发环境时可能会有冲突。 (1)关闭所有正在运行的程序,双击 jdk-1_5_0_07-windows-i586-p.exe 文件,运行 安装程序。安装向导会要求接受 Sun 公司的许可协议,选择该对话框中的【我接受该许可 证协议中的条款(A)】单选按钮,接受许可协议,如图 2.1 所示。 图 2.1 JDK 1.5 许可协议 (2)单击【下一步】按钮,将弹出组件安装对话框,在对话框中可以更改安装路径和 功能选项。单击【更改】按钮,将安装路径指定到 D:\jdk1.5.0_07 目录下,单击【下一步】 按钮,开始安装 JDK,如图 2.2 所示。 图 2.2 选择组件安装对话框 (3)JDK 是程序开发所使用的环境,在它的安装文件中还包含了一个 JRE(J2SE Runtime Environment)Java 运行环境,默认一起安装。在 JDK 的安装过程中会弹出如图 2.3 所示 的 J2SE 安装向导,在这个向导中有必须安装的“J2SE Runtime Environment”和可以选择 10 第 1 篇 基 础 篇 P A R T 1 的“其它语言支持”和“其它字体和媒体支持”选项。单击【更改】按钮可以改变 JRE 的 安装位置,本书定义的环境安装在 D 盘的根目录下。单击【下一步】按钮。 图 2.3 选择语言安装界面 (4)在如图 2.4 所示的“浏览器注册”对话框中,要求选择绑定 Java(TM)插件的浏 览器,这里默认选择微软的 IE 浏览器。向导的最后一行说明可以在 Java(TM)插件的控制 面板中更改设置,它位于操作系统的控制面板中,以 Java 命名。单击【下一步】按钮,安 装向导继续安装 JDK。在弹出安装完成的提示对话框中,单击【完成】按钮,即可完成 JDK 的安装。 图 2.4 选择要将 JDK 绑定的浏览器 2.配置 JDK 的环境变量 安装完 JDK 后,需要设置环境变量,分别是 JAVA_HOME、PATH 和 CLASSPATH。具体步 骤如下。 (1)在“我的电脑”上单击鼠标右键,选择“属性”菜单项。在弹出的“系统特性” 对话框中选择“高级”选项卡,如图 2.5 所示。 11 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 图 2.5 系统特性设置 (2)单击【环境变量】按钮,弹出如图 2.6 所示的“环境变量”对话框。在这里可以 添加针对单个用户的“用户变量”和针对所有用户的“系统变量”。单击“系统变量”区域 中的【新建】按钮,将弹出如图 2.7 所示的“新建系统变量”对话框。 图 2.6 设置环境变量 图 2.7 新建系统变量 12 第 1 篇 基 础 篇 P A R T 1 (3)在“变量名”文本框中输入 JAVA_HOME,在“变量值”文本框中输入 JDK 的安装 路径“D:\jdk1.5.0_07”,单击【确定】按钮,完成环境变量 JAVA_HOME 的配置。 (4)在系统变量中查看 PATH 变量,如果不存在,则新建变量 PATH,在原来的变量值 的最前面添加如下配置代码: %JAVA_HOME%\bin; (5)在系统变量中查看 CLASSPATH 变量。如果不存在,则新建变量 CLASSPATH,变量 的值为: %JAVA_HOME%\lib\dt.jar; %JAVA_HOME%\lib\tools.jar 3.测试 JDK JDK 程序的安装和配置完成后,可以测试 JDK 是否能够在计算机上运行。在“运行” 窗口中输入 cmd 命令,在进入的 DOS 环境中直接输入 javac,按〈Enter〉键后,系统会输 出 javac 的帮助信息,如图 2.8 所示。这说明已经成功配置了 JDK,否则需要仔细检查上 面步骤的配置是否正确。 图 2.8 测试 JDK 安装环境 2.1.2 Tomcat 5.5 的安装 Spring 可以在任何 JSP 的应用服务器下运行,如Tomcat、Resin、Websphere、Weblogic 等。本书使用的 JSP 服务器为 Tomcat 5.5.16,读者可以从 http://www.apache.org 网站 下载。下面介绍如何在 Windows 2000 操作系统下安装与启动 Tomcat 5.5.16。 视频录像....:.mr..\.02..\.lx..\.02..\.Tomcat5.5.........的安装录像......exe.... 1.安装 Tomcat5.5 (1)运行 Tomcat 安装文件 apache-tomcat-5.5.16.exe,在弹出的安装向导对话框中, 单击【Next】按钮,将弹出如图 2.9 所示的“许可协议”对话框。 (2)单击【I Agree】按钮,接受许可协议,出现如图 2.10 所示的选择组件对话框, 选择要安装的 Tomcat 组件。 13 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 图 2.9 Tomcat 许可协议对话框 图 2.10 选择 Tomcat 安装模式 (3)这里采用默认的组件安装策略,单击【Next】按钮,将弹出“选择安装位置”对 话框,如图 2.11 所示。 图 2.11 选择安装位置 14 第 1 篇 基 础 篇 P A R T 1 (4)在 Destination Folder 文本框中输入 Tomcat 的安装位置,或单击文本框右侧的 【Browse】按钮,选择服务器安装的位置。笔者将其安装在“D:\Tomcat5.5”文件夹中。单 击【Next】按钮,将弹出如图 2.12 所示的配置对话框。 图 2.12 配置对话框 (5)在图 2.12 所示的“配置”对话框中,需要设置服务器的端口和更改 Tomcat 设置的 后台登录名与密码。默认端口采用“8080”,用户名采用admin,密码空,采用默认值安装。 单击【Next】按钮,安装向导会自动寻找虚拟机(也就是 JDK)的安装位置,如图 2.13 所示。 单击【Install】按钮,开始安装。在弹出的安装完成对话框中单击【Finish】按钮,完成安 装。 图 2.13 JDK1.5 虚拟机位置 (6)安装完成之后,还要解压缩 apache-tomcat-5.5.16-admin.zip 文件,解压后的文件将 覆盖到 Tomcat 的安装目录中,这样可以添加“Tomcat Administration”图形界面的 Tomcat 管理功能。2.启动 Tomcat 5.5 (1)在 Window 2000 系统下,依次单击“开始”/“程序”/“Apache Tomcat 5.5”/ “Configure Tomcat”菜单项,弹出 Tomcat 属性对话框,如图 2.14 所示。单击【start】 按钮,启动 Tomcat 服务器,单击【确定】按钮完成 Tomcat 的启动。 15 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 图 2.14 Tomcat 启动对话框 (2)启动完成之后,在IE 浏览器的地址栏中输入 URL 地址“http://localhost:8080/”, 出现如图 2.15 所示的页面,表示 Tomcat 安装成功。 图 2.15 Tomcat 启动页面 2.2 Spring 环境安装 2.2.1 获得 Spring 发布包 Spring 属于开源项目,在开源网站中可以看到两个版本的 Spring 发布包。  spring-framework-2.0.1 是只包含 Spring 的开发包,可以理解为标准版发布包;  spring-framework-2.0.1 -with-dependencies 是 Spring 的依赖版,它不但包含 Spring 开发包,而且还包含 Spring 需要的所有第三方类库,例如 Hibernate、FreeMarker、 JSF、Ant 等。 可以在网址 http://www.springframework.org 下载最新的发布包。如果没有特殊的情 16 第 1 篇 基 础 篇 P A R T 1 况,建议读者下载Spring 的依赖版,这样可以避免到 Internet 上寻找第三方类库的麻烦。 发布包中包含很多目录,各目录的说明如下。 dist:Spring 的开发类库、Spring 定制的 JSP 标签库、Spring-beans.dtd 文件、 FreeMarker 宏文件(Spring.ftl)和 Velocity 宏文件(Spring.vm)。 docs:存放各类文档,包含 Spring 的 API、Spring 和 MVC 教程、Spring 标签库文档。 lib:存放第三方类库。 mock:存放测试用的 Mock 类。 samples:存放演示实例。 src:存放 Spring 的源代码。 test:存放 Spring 的测试代码。 2.2.2 安装 Spring 获得并打开 Spring 的发布包之后,其dist 目录中包含 Spring 的 8 个 JAR 文件,它们 分别负责 Spring 不同的模块,具体介绍如表 2.1 所示。 表 2.1 Spring 的 JAR 文件 JAR 文件 描 述 spring.jar 整个 Spring 模块,包含表中所有 JAR 的功能 spring-core.jar Spring 的核心模块,包含 IoC 容器 spring-aop.jar Spring 的 AOP 模块 spring-context.jar Spring 的上下文,包含 ApplicationContext 容器 spring-dao.jar Spring 的 DAO 模块,包含对 DAO 与 JDBC 的支持 spring-orm.jar Spring 的 ORM 模块,支持 Hibernate、JDO 等 ORM 工具 spring-web.jar Sping 的 Web 模块,包含 Web application context spring-webmvc.jar Spring 的 MVC 框架模块 如表 2.1 所示,除 spring.jar 文件外,每个 JAR 文件都对应一个 Spring 模块。可以 根据需要引入单独的模块,也可以直接使用 spring.jar 文件应用整个 Spring 框架。在学 习过程中建议选择整个 Spring 框架。 针对不同的应用程序与 Web 应用,Spring 的安装稍有差异。 应用程序使用 Spring 时,直接将需要的JAR 文件复制到应用程序的 ClassPath 指定的 目录中。 对于 Web 应用,必须将需要的 JAR 文件拷贝到 Web 应用的 WEB-INF 文件夹下的 lib 文 件夹中,Web 服务器启动时会自动装载 lib 文件夹中所有的 JAR 文件。如果要使 Web 服务 器的所有应用都支持 Spring,需要根据不同服务器进行设置。 2.3 在 Jbuilder 2006 中应用 Spring 框架 2.3.1 在 JBuilder 2006 中添加 Spring 库 JBuilder 2006 支持最新的 Java 标准,并且缩短了开发周期。它可以使用很多 Java 技术,例如 Struts、JSP 等,但是 JBuilder 2006 并不包含 Spring 框架的资源包,本节以 17 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 spring- framework-2.0.1 为例,介绍如何在 JBuilder 2006 中添加 Spring 库,具体步骤 如下。 视频录像:.....mr..\.03..\.lx..\.03..\.JBuilder2006............中应用...Spring......框架录像.....exe.... (1)首先需要获得 Spring 发布包,解压缩到本地磁盘 C 盘。启动 Jbuilder 2006,选 择 Tools/Configure/Libraries 菜单命令,出现如图 2.16 所示的 Configure Libraries 对话框,在该对话框中单击【New】按钮,弹出如图 2.17 所示的配置类库的对话框。 图 2.16 配置类库 图 2.17 新建类库向导 (2)在图 2.17 所示的“New Library Wizard”对话框的“Name”文本框中,输入新建 类库的名字“spring-framework-2.0.1”,在“Location”下拉菜单中选择“User Home” 菜单项,在“Library Paths”区域中通过单击【Add】按钮添加类库。 (3)在图 2.18 所示的对话框中,选择“spring-framework-2.0.1”的存放位置,单击 【OK】按钮,Jbuilder 2006 会自动搜索所有 JAR 文件并加入到新建的类库中。关闭如图 2.16~图 2.18 所示的对话框。这样 Spring 开发包就添加到了 JBuilder 2006 的资源库中, 在以后开发项目时就可以开发 Spring 程序了。 18 第 1 篇 基 础 篇 P A R T 1 图 2.18 选择 Spring 类库目录 2.3.2 创建工程并加载 Spring 库 在 Jbuilder 2006 中创建新工程时,可以指定需要加载的各种类库和自定义类库。具 体步骤如下。 (1)启动 Jbuilder 2006,单击“File”/“New”或按快捷键打开如图 2.19 所示的“Object Gallery”对话框。 图 2.19 Object Gallery 对话框 选择“Project”节点,在右侧的窗口中选择“Project”选项创建工程。单击【OK】 按钮,关闭对话框。 (2)在图 2.20 所示的“Project Wizard -Step 1 of 3”对话框中,“Name”文本框用 于输入新建工程的名称,这里输入“Test”。“Directory”是将要创建工程的存放位置,可 以通过单击右侧的 按钮指定盘符与目录。“Template”是可供选择的模板,采用默认值 即可。单击【Next】按钮。 19 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 图 2.20 工程向导步骤 1 (3)在“Project Wizard - Step 2 of 3”对话框中单击“Required Libraries”标 签项,如图 2.21 所示,此标签项中是工程需要的类库,单击右侧的【Add】按钮,在弹出 的如图 2.22 所示的对话框中选择之前定义的“spring-framework-2.0.1”类库,单击【OK】 按钮为工程添加 Spring 资源包,然后单击【Finish】按钮。这样就创建了“Test”工程, 并且拥有了对 Spring 框架的支持。 图 2.21 工程向导步骤 2 除在创建新工程时可以添加 Spring 框架外,在现有的工程中也可以添加 Spring 框架。 方法是:右键单击现有工程,选择“Properties”菜单项,在弹出的对话框中选择“Paths” 节点,在右侧的对话框中选择“Required Libraries”选项卡,单击【Add】按钮,添加刚 刚建立的“spring-framework-2.0.1”类库,如图 2.23 所示,单击【OK】按钮。 20 第 1 篇 基 础 篇 P A R T 1 图 2.22 添加工程的类路径 图 2.23 Properties 对话框 这样,这个工程就具备了 Spring 类资源库,此时就可以在工程中开发 Spring 程序了。 在 2.5 节会介绍一个简单的实例,可以验证开发环境是否搭建成功。 2.4 在 Eclipse 中应用 Spring 框架 2.4.1 安装插件 MyEclipse Eclipse 创建的工程默认是 Java 应用工程,如果需要创建 Web 工程则需要加入 Web 开 发插件。在 Eclipse 的 Web 插件中,MyEclipse 以其优越的开发环境和强大的 Web 组件支 持,获得了众多开发者的支持。下面介绍 MyEclipse 插件的安装。 1.下载 MyEclipse 插件 MyEclipse 插件的网址是“http://www.myeclipseide.com”。它非常好记,网站名由 MyEclipse 和 IDE 结合组成。读者可以到网站上下载最新的版本,但是请注意下载文件所 支持的 Eclipse 的版本号一定要符合你的 Eclipse 版本。 21 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 2.安装 MyEclipse 插件 本节以 Eclipse 3.2 使用的 MyEclipse 5.0.1GA 版本为例,介绍 MyEclipse 插件的安 装。它和普通的安装程序差不多,只是在安装过程中需要指定 Eclipse 和 MyEclipse 本身 的安装位置。之所以要指定 Eclipse 的安装位置是因为它需要在 Eclipse 的 Links 文件夹 中定义自己的位置。不过 MyEclipse 做了智能安装,按照安装向导的安装步骤进行安装就 可以。下面介绍安装过程中比较重要的几个步骤。 视频录像:.....mr..\.02..\.lx..\.04..\.MyEclipse5.0.1GA................的安装录像......exe.... (1)运行下载的 MyEclipse 安装文件,在安装向导的准备工作之后,会出现MyEclipse 的介绍,单击【Next】按钮,如图 2.24 所示。 图 2.24 MyEclipse 插件安装向导的介绍界面 (2)在接下来的许可协议界面中将显示安装 MyEclipse 要遵守的协议,看过之后选择 “I accept the terms of the License Agreement”单选按钮,如图 2.25 所示,单击【Next】 按钮。 图 2.25 MyEclipse 安装向导的许可协议界面 22 第 1 篇 基 础 篇 P A R T 1 (3)MyEclipse 的安装要求选择 Eclipse 的安装位置,这一步骤很关键,如图 2.26 所 示。单击【Choose】按钮,在出现的对话框中选择 Eclipse 的安装位置并单击【确定】按 钮,然后回到安装向导界面,单击【Next】按钮。 图 2.26 MyEclipse 安装向导选择 Eclipse 位置的界面 (4)接下来是“选择安装位置”界面。在这个向导界面中提供了一个默认的安装位置 “C:\Program Files\MyEclipse 5.0GA”,如图 2.27 所示,可以直接修改这个位置或单击 【Choose】按钮选择其他安装位置,然后单击【Next】按钮。 图 2.27 MyEclipse 安装向导选择安装位置的界面 (5)至此,所有的项目已设置完成,在安装总结界面中列出了所有设置和磁盘空间信 息,如图 2.28 所示。如果有错误的设置,可以单击【Previous】按钮重新设置,否则单击 【Install】按钮开始自动安装,直到安装程序结束。 23 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 图 2.28 MyEclipse 安装向导的总结界面 2.4.2 创建工程 视频录像:.....mr..\.02..\.lx..\.05..\.Eclipse.......中应用...Spring......框架录像.....exe.... (1)启动 Eclipse,选择“File”/“New”/“Project”或按快捷键, 出现“New”对话框,在该对话框中选择“MyEclipse”节点的“J2EE Projects”子 节点下的“Web Project”子节点,如图 2.29 所示,单击【Next】按钮打开 “New J2EE Web Project”对话框。 在 MyEclipse 中 创建 Web 工程 图 2.29 New 对话框 (2)图 2.30 所示的是通过 MyEclipse 新建 Web 工程的向导对话框,其中的“Project 24 第 1 篇 基 础 篇 P A R T 1 图 2.30 新建 Web 工程对话框 Name”文本框是创建的工程的名字,输入“Test”之后,“Context root URL”文本框会自 动添加工程的访问路径(如:“/Test”)。“Web root folder”文本框是存放 Web 内容的文 件夹,建议把这个文本框中的内容删除。根据个人习惯,也可以选择保留,即指定存放 Web 内容的文件夹。单击【完成】按钮创建工程。 2.4.3 添加 Spring 功能 (1)建立 Web 工程或应用程序工程之后,在如图2.31 所示的包资源管理器中,右键单击“Test”工程选择 “MyEclipse”/“Add Spring capabilities”(也可以通 过菜单项选择“MyEclipse”/“Add Spring capabilities”) 为工程添加 Spring 功能,即 Spring 类资源库。 (2)Spring 有 7 大模块,可以同时使用,也可以单 独使用。所以在步骤(1)中选择了添加 Spring 功能后, 会弹出如图 2.32 所示的“Add Spring Capabilities”向 导对话框,要求指定要添加的 Spring 模块。“Spring 1.2 Core Libraries”是默认添加的模块。可根据工程需要选择将要添加的模块,也可以选择 “UserLibraies”来添加用户自定义的类库。建议选择自定义的类资源库,这样可以添加最 新的 Spring 2.0 或 Hibernate 3.2 的类资源库。单击【Next】按钮,进入如图 2.33 所示 的对话框。 (3)Spring 需要 XML 文件来定义 JavaBean 的配置信息。在如图2.33 所示的对话框中, “File”文本框用于定义配置 Spring 的 XML 文件名。可以采用默认的 “applicationContext.xml”,也可以输入自定义的文件名。“Folder”是创建 Spring 配置文 件的位置,可以单击右侧的【Browse】按钮选择配创建置文件的位置,也可以手动输入文 件位置。最后单击【Finish】按钮,Spring 功能添加完成。 图 2.31 包资源管理器 25 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 图 2.32 添加 Spring 功能的向导 图 2.33 创建 Spring Bean 的 XML 文件 2.5 Spring IoC 实例 本节将给出一个简单的实例来测试开发环境是否搭建成功,并说明一下Spring 的核心 基础,即 IoC(依赖注入)。 实例位置:.....mr..\.02..\.sl..\.01.. 26 第 1 篇 基 础 篇 P A R T 1 首先建立一个玩具的类的接口 Toy,它包含一个toyInfo()方法来输出玩具名称和出厂 状态。建立这个接口的主要目的是为了针对接口编程,使用Java 多态性完成程序的解耦合。 接口代码如下。 public interface Toy { public void toyInfo(); } 然后建立两个 JavaBean,RagBaby 类与 GapGun 类。它们都实现了 Toy 接口,并实现了 toyInfo()方法,定义了“info”属性和对应的 get()方法和 set()方法,同时也定义了一 个初始化的构造方法。因为 Spring 的 XML 配置文件可以基于 set()和构造器两种方法为 JavaBean 赋值,所以在此先预设好对应的方法,以便以后讲解。 RagBaby 类在实现 Toy 接口的 toyInfo()方法中输出“info”属性作为玩具娃娃的出厂 信息,程序代码如下。 public class RagBaby implements Toy { private String info; public RagBaby(){ } public RagBaby(String info) { this.info=info; } public void setInfo(String info) { this.info = info; } public void toyInfo(){ System.out.println("玩具娃娃:"+info); } } CapGun 类也同样实现 Toy 接口和 toyInfo()方法,只是方法的输出不再是玩具娃娃, 而是玩具手枪。info 属性和对应的 get()/set()方法也必须定义。程序代码如下。 public class CapGun implements Toy { private String info; public CapGun(){ } public CapGun(String info) { this.info = info; } public void setInfo(String info) { this.info = info; } public void toyInfo(){ System.out.println("玩具手枪:" + info); } } 在完成玩具类 RagBaby 和 CapGun 的创建之后,需要一个 XML 配置文件来装载这两个 JavaBean。在配置文件中必须定义玩具类的属性依赖关系(本实例中只有一个 info 属性), 27 C H A P T E R 2 第 2 章 Sprin g 环 境 搭 建 这是 IoC 最简单的一个示例。 applicationContext.xml 是本实例的配置文件,其中只设置了 CapGun 类,而没有用 到 RagBaby 类,这是为演示Spring 的 IoC 而做的准备工作,在这之后还会对此配置文件进 行修改,以实现不用修改和重新编译 Java 代码就能改变程序运行结果的功能。Application Context.xml 文件的配置代码如下。 等待出厂检验 上面的配置文件通过构造方法为 CapGun 类赋值,也可以在成对的标签中加入如 下代码来基于 set()方法为 JavaBean 赋值。 等待出厂检验 最后创建主类 Production 来生产玩具,并调用 toyInfo()方法显示玩具名称和出厂状 态。主类通过 Spring I/O 包中的 Resource 接口来获得配置文件的内容,然后传递参数给 XmlBeanFactory 类。创建 JavaBean 时通过 BeanFactory 的 getBean()方法获取指定的 JavaBean 实例。程序代码如下。 import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class Production { public static void main(String[] args) { Resource resource = new ClassPathResource("applicationContext.xml"); BeanFactory factory = new XmlBeanFactory(resource); Toy toy = (Toy) factory.getBean("toy"); toy.toyInfo(); } } 程序运行结果如图 2.34 所示。 28 第 1 篇 基 础 篇 P A R T 1 图 2.34 实例运行结果 1 如果 Production 类不再需要生产玩具手枪,而改为生产玩具娃娃,在以往的程序开发 中,需要重新编辑 Production 类中创建类的语句,来创建 RagBaby 类,然后重新编译、运 行;但在应用Spring 框架的程序中,只需要用Windows 的记事本改写 XML 配置文件中的类 名,然后直接运行程序即可。 更改 applicationContext.xml 中标签的 class 属性内容如下。 保存后,不用重新编译,直接运行程序。 程序运行结果如图 2.35 所示。 图 2.35 实例运行结果 2 从程序运行结果可以看出,现在生产的已经不再是玩具手枪 CapGun 类的实例对象,而 是玩具娃娃 RagBaby 类的实例对象。读者应该能看出,Spring IoC 和以往的 Java 对象创 建和初始化方式的不同,也能体会到 Spring IoC 解耦的方便性。如果读者从这个实例中还 体会不到 Spring IoC 的优点,没关系,本书搭配了丰富的实例讲解,只要认真、细心地通 读本书之后就可以随心所欲地使用 Spring,并在学习过程中完全体会 Spring 的 IoC 和 AOP 功能以及利用 Spring 开发项目的优势和方便性。 第 3 章 Spring 基础与容器 从本章开始,将带领读者走进 Spring 世界去熟悉 IoC 容器。本章 主要介绍 Spring 的基础,BeanFactory 和 ApplicationContext 两个 IoC 容器和容器中 JavaBean 的定义、初始化、使用、销毁以及 ApplicationContext 中增加的功能。 主要内容如下。  Spring IoC  使用 XML 装配 JavaBean  Spring 的两个容器详解 3.1 Spring IoC Spring 实现了 IoC 模式,所以它也是一个 IoC 容器。容器是Spring 框架的核心,重点关注组件的依赖关系以及生命周期,同时它也适用 于 POJO,即简单 Java 对象。Spring 使用 BeanFactory 和 Application Context 两个接口来定义 IoC,它们是 Spring 针对不同应用领域的两 个 IoC 容器。 3.1.1 BeanFactory 介绍 对于 Factory 模式,读者肯定很熟悉,BeanFactory 采用了 Java 经典的工厂模式,通过从 XML 配置文件或属性文件(.properties)中 读取 JavaBean 的定义,来实现 JavaBean 的创建、配置和管理。 BeanFactory 有很多实现类,其中XmlBeanFactory 可以通过流行的 XML 文件格式读取配置信息来装载 JavaBean。第 2 章的 Spring IoC 实例 就使用了这种方法,关键代码如下。 30 第 1 篇 基 础 篇 P A R T 1 Resource resource = new ClassPathResource("applicationContext.xml"); BeanFactory factory = new XmlBeanFactory(resource); Toy toy = (Toy) factory.getBean("toy"); ClassPathResource 读取 XML 文件并传递给 XmlBeanFactory。为方便阅读,再次给出 applicationContext.xml 文件代码。 等待出厂检验 标 签 中 通 过 标 签 定 义 JavaBean 的名称和类型,并通过 标签为 JavaBean 的构造方法传递参数。在程序代码中利用 BeanFactory 的 getBean()方法获取 JavaBean 的实例并且向上转为需要的接口类型,这样在容器中就开 始了这个 JavaBean 的生命周期。 BeanFactory 在调用 getBean()方法之前不会实例化任何对象,只有在需要创建 JavaBean 的实例对象时,才会为其分配资源空间。这使它更适用于物理资源受限制的应用 程序,尤其是内存受限制的环境。 Spring 中 Bean 的生命周期如下。  实例化 JavaBean  初始化 JavaBean  使用 JavaBean  销毁 JavaBean 在 3.3 节中将详细地介绍 Spring 中 JavaBean 的生命周期,在此之前先来了解一下 Spring 的另一个容器。 3.1.2 ApplicationContext 介绍 ApplicationContext 以下简称应用上下文,继承了 BeanFactory 并且增加了很多其他 功能,例如国际化的支持、对事件的发布和监听等。因为它增加了很多适合 J2EE 的功能, 所以大部分开发者都采用应用上下文来开发应用系统。只有在物理资源受限制(如内存限 制)的情况下才使用 BeanFactory 来作 IoC 容器。 3.2 使用 XML 装配 JavaBean 3.2.1 使用 XML 定义 JavaBean 在 Spring 中无论使用哪种容器,都需要从配置文件中读取 JavaBean 的定义信息,再 根据定义信息去创建 JavaBean 的实例对象并注入其依赖的属性。由此可见,Spring 中所 谓的配置,主要是对JavaBean 的定义和依赖关系而言,本节将介绍如何利用 XML 文件配置 31 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 JavaBean 的定义信息和其属性的依赖注入方法。 3.2.2 标签 是配置文件的根节点,所有的节点元素都建立在它的内部。按照XML 编码的规 定,它必须使用成对的标签,即,来定义节点元素。在此标签中包含 5 个默认的全局属性,它们可以被具体的子标签属性覆盖,如表 3.1 所示。 表 3.1 标签的属性表 属 性 名 属 性 说 明 default-autowire 设置自动装配 JavaBean 依赖属性的方式 no:不自动注入依赖属性 byName:根据 JavaBean 属性名字自动装配依赖的 JavaBean byType:根据 JavaBean 属性的类型自动装配依赖的 JavaBean constructor:根据构造方法的参数自动注入依赖属性 autodetect:自动检测依赖注入方式 default-dependency-check 依赖关系检测,当实例化一个 JavaBean 时,将检测这个 JavaBean 所依 赖的属性,如果依赖的属性没有被实例化,将先去实例化依赖的属性。 检测方式有以下 4 种 none:不检测 JavaBean 之间的依赖关系 simple:检测 Java 的基本数据类型,例如 int、float 和 String 数据类 型 objects:如果 JavaBean 所依赖的属性是另一个 JavaBean,先去检测并 实例化另一个 JavaBean all:检测 simple 和 objects 两种依赖关系 default-destroy-method 默认的销毁方法,在 JavaBean 被销毁之前执行指定的销毁方法 default-init-method 默认的初始化方法,在初始化 JavaBean 时,首先执行指定的初始化方法 default-lazy-init 默认的延迟加载方式 可以用表 3.1 的属性定义 Spring 的默认行为。格式举例如下。 ⋯⋯ 除默认的初始化和销毁方法参数外,其余的参数值都是标签的默认值。 3.2.3 标签 这是下的子标签,用于描述当前的 XML 配置文件。语法格式如下。 这里是描述的内容 公 司:明日科技 软 件 名 称:《客户管理系统》 配置文件描述:此文件配置《客户管理系统》中业务逻辑Bean的装载 32 第 1 篇 基 础 篇 P A R T 1 3.2.4 标签 此标签用于导入其他的配置文件。为使配置文件代码工整、便于管理,可将代码分为 几个配置文件,然后通过标签分别导入。例如将数据库连接或操作用的 JavaBean, 定义在名为“database.xml”的配置文件中,那么这个配置文件中包含的全是数据库操作 的配置信息,这样“database.xml”文件的代码会非常工整。语法格式如下。 3.2.5 标签 标签位于标签之内,用于定义 JavaBean 的配置信息,最简单的 标签也需要包含“id”(或“name”)和“class”两个属性来说明 Bean 的实例名称和类信 息。实例化 JavaBean 对象时会以“class”属性指定的类文件来生成 JavaBean 的实例,而 实例对象的名字是由“id”或“name”属性指定的。语法格式如下。 标签使用频繁,属于重点介绍范围。为方便阅读,在此将常用标签的各个属性 以表格的形式加以说明。更加详细的内容说明,可参考 spring-beans.dtd 文件。标签中的 属性说明如表 3.2 所示。 表 3.2 标签中的属性说明 属 性 描 述 举 例 id 代表 JavaBean 的实例对象。在 JavaBean 实 例化之后可以通过 id 来引用 JavaBean 的实 例对象 name 代表 JavaBean 的实例对象名。与 id 属性的 意义基本相同 class JavaBean 的类名(包含路径,如com.test. Example),它是标签定义 JavaBean 必须指定的属性 或者 singleton 是否使用单实例。如果设置成 false,在每 次调用容器的 getBean()方法时,都会返回 新的实例对象,这也是平时使用 new 创建对 象的默认方式。如果采用默认设置 true,那 么在 Spring 容器的上下文中只有一个该 JavaBean 的实例对象,即容器的getBean 方 法只返回 1 个惟一的实例对象。在开发企业 级的项目时经常需要 singleton 模式 autowire Spring 的 JavaBean 自动装配功能,在3.2.8 节会做详细介绍 可参见 3.2.3 节的自动装配 33 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 续表 属 性 描 述 举 例 depends-on 用于保证在 depends-on 指定的 JavaBean 被实例化之后,再实例化自身 JavaBean init-method 指定 JavaBean 的初始化方法 destroy-metho d 指定 JavaBean 被回收之前调用的销毁方法 factory-metho d 指定 JavaBean 的工厂方法。指定的方法必 须是类的静态方法,并且返回 JavaBean 的 实例。如果是在新建工程中使用 Spring, 建议使用 getBean()方法来获得 JavaBean factory-bean 指定 JavaBean 的工厂类。与 factory-method 属性一起使用。如果是在 新建工程中使用 Spring,建议使用 getBean()方法来获得 JavaBean 3.2.6 如何配置 JavaBean 在定义了 JavaBean 装载信息之后需要对其赋值。一个 JavaBean 的赋值可以通过构造 方法完成初始化,或者通过 set()方法初始化和改变属性值。下面分别介绍如何在 XML 中 配置 JavaBean 的属性为构造方法和 set()方法传递参数。 1.为构造方法传递参数 在类被实例化的时候,它的构造方法被调用并且只能调用一次。所以它被用于类的初 始化操作。标签的子标签。通过其子标签可以为构造 方法传递参数。 现在以一个简单的输出学生信息的实例演示如何为构造方法传递参数。 实例位置:.....mr..\.03..\.sl..\.01.. 实例程序创建过程如下。 (1)建立 Student 接口,它是对学生类的简单抽象。程序代码如下。 package com.lzw; public interface Student { public void printInfo(); } (2)建立实现 Student 接口的 Stu1 类,定义姓名、年龄、性别 3 个属性和包含这 3 个参数的构造方法,在实现接口的 printInfo()方法中输出所有属性信息。程序代码如下。 package com.lzw; public class Stu1 implements Student { private String name; private String sex; private int age; public Stu1(String name, String sex, int age) { super(); this.name = name; 34 第 1 篇 基 础 篇 P A R T 1 this.sex = sex; this.age = age; } public void printInfo(){ System.out.println("姓名:" + name); System.out.println("年龄:" + age); System.out.println("性别:" + sex); } } (3)applicationContext.xml 是装配 JavaBean 的配置文件,它通过 3 个 标签为构造方法传递两个 String 类型和一个 int 类型的参数。程序代 码如下。 飞龙 26 (4)创建 Info 类。它是实例的主类,负责载入容器,从容器中获得 Stu1 类的实例, 并调用 Stu1 类的 pringInfo()方法打印学生信息。程序代码如下。 package com.lzw; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class Info { public static void main(final String[] args) { final Resource rs = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(rs); Student stu = (Student) bf.getBean("stu1"); stu.printInfo(); } } 实例运行结果如图 3.1 所示。 35 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 图 3.1 实例运行结果 1 通过这个实例,可以看到容器通过多个标签完成了对构造方法的传 参,但是如果标签的赋值顺序与构造方法中参数的顺序或参数类型不同,程序会产生异常。 标签可以使用“index”属性和“type”属性解决此类问题。下面分别 介绍这两个属性的用法。 type 属性:可以指定参数类型以确定要为构造方法的哪个参数赋值。当需要赋值的属 性在构造方法中没有相同的类型时,可以使用这个参数。因此实例中stu1 的配置信息可以 这样写: 飞龙 26 index 属性:用于指定构造方法的参数索引,指定当前标签为构造 方法的那个参数赋值。因为程序中含有两个 String 类型的参数,type 属性无法做判断, 导致 name 和 sex 参数无法判断正确性。这时需要设置index 属性定义赋值索引来指定构造 方法的参数位置。修改后的 applicationContext.xml 配置文件内容如下。 飞龙 26 使用 type 属性指定 为构造方法的 int 类型参数传参 使用 index 属性指定 构造方法的参数索引 36 第 1 篇 基 础 篇 P A R T 1 在修改后的代码中,两个 String 类型的参数通过“index”属性指定了在构造方法中的 位置,确保了传参的正确性,第 3 个标签的 type 属性可以改写成 “index=2”。现在的 3 个标签可以随意调换顺序而不会影响到构造方法 的正确赋值。 标签还有“ref”和“value”两个属性,分别用于引用其他 JavaBean 和定义新值。由于使用构造方法在配置文件中定义 JavaBean 使用得不多,所以这两个属性 不再介绍,可参考 3.2.6 节“JavaBean 的赋值标签”的内容。 2.为 set()方法传递参数 一个简单的 JavaBean 最明显的规则就是以一个私有属性对应 set()和 get()方法,来 实现对属性的封装。既然 JavaBean 有 set()方法来设置 Bean 的属性,Spring 就会有相应 的支持。除了为构造方法传递参数的标签之外,标签可以为 JavaBean 的 set()方法传递参数,即通过 set()方法为属性赋值。在上面的实例中再添加 一个 Moniter 类。 实例位置:.....mr..\.03..\.s.l.\.01.. 设置 Moniter 类属性和 set()/get()方法。程序代码如下。 package com.lzw; public class Moniter implements Student { private String name; private String sex; private int age; public void printInfo(){ System.out.println("姓名:" + name); System.out.println("年龄:" + age); System.out.println("性别:" + sex); } public int getAge(){ return age; } public void setAge(int age) { this.age = age; } public String getName(){ return name; } public void setName(String name) { this.name = name; } public String getSex(){ return sex; 37 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 } public void setSex(String sex) { this.sex = sex; } } 修改 applicationContext.xml 配置文件。添加名称为 Moniter 的 Java Bean 的定义, 用标签的“name”属性指定要操作的 JavaBean 的属性,然后通过 的子标签为属性赋值。修改后的关键代码如下。 26 欣欣 修改 Info 类,获得 Moniter 的实例,然后调用 printInfo()方法输出信息。修改的关 键代码如下。 final Resource rs = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(rs); Student stu = (Student) bf.getBean("stu1"); stu.printInfo(); Student moniter = (Student) bf.getBean("moniter"); moniter.printInfo(); 程序运行结果如图 3.2 所示。 图 3.2 实例运行结果 2 3.2.7 JavaBean 的赋值标签 以上只介绍了如何用标签赋值,那么复杂的属性如何赋值呢?例如 Null、对 象引用、集合类等。接下来将要介绍对 JavaBean 的复杂属性赋值的标签,它们可以应用到 任何如可以给 JavaBean 的属性赋值的标签中。 1.标签 这是一个普通的赋值标签,可直接在成对的标签中放入数值或其他赋值标签, 38 第 1 篇 基 础 篇 P A R T 1 Spring 会把这个标签提供的属性值注入到指定的 JavaBean 中。语法格式如下。 arg arg:传递给 JavaBean 的属性值。 2.标签 这个标签可以引用其他 JavaBean 的实例对象,当传递的参数是其他 JavaBean 的实例 对象时使用此标签。语法格式如下。 bean:指定引用的 JavaBean。 3.标签 这是为 JavaBean 赋 Null 空值的标签。当JavaBean 的某个属性暂不使用,要将其设置 为 Null 值时使用此标签。语法格式如下。 4.标签 这是为 List 集合类或数组赋值的标签。当JavaBean 的某个属性是 List 集合类型或数 组时,需要使用此标签为 List 集合类型或数组的每一个元素赋值。语法格式如下。 arg1 arg2 :赋值标签,为标签指定的属性赋值。也可以使用其他赋值标签作为集 合类赋值标签的子标签,例如或 3.2.7 节中介绍的使用标签定义匿名内部 JavaBean 的方法。 此标签应用在标签中定义 JavaBean 的完整代码举例如下。 student2 5.标签 和标签类似,标签也是为集合对象赋值,不过赋值的对象是 Set 类型的属 性。语法格式如下。 39 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 arg2 ⋯⋯ 此标签应用在标签中定义 JavaBean 的完整代码举例如下。 name 6.标签 此标签为 Map 集合赋值。因为 Map 以键值对(key/value)的方式存放数据,所以需要 使用子标签装载 key 与 value 数据。 Map 集合的 key 可以是任何类型的对象,而标签的属性 key 是以 String 类型 表示的,所以限制了 Spring 中 Map 的 key 只能用 String 来表示。语法格式如下。 标签的子标签,其“key”属性用于指定 Map 集合类的键值(key)。 此标签应用在标签中定义 JavaBean 的完整代码举例如下。 student2 7.标签 这是为 java.util.Properties 类型属性赋值的标签,和标签类似,但是它的 (key/value)键值全都是 String 类型的,无法赋予 Object 对象类型。语法格式如下。 40 第 1 篇 基 础 篇 P A R T 1 value 的子标签,其“key”属性用于指定 Properties 类的键值(key)。 此标签应用在标签中定义 JavaBean 的完整代码举例如下。 student1 student2 3.2.8 匿名内部 JavaBean 的创建 通过前面的介绍,读者应该对如何使用 XML 装配 JavaBean 有了一定的了解。但是编程 中经常遇到匿名的内部类,在Spring 中该如何利用 XML 装配呢?其实非常简单,在需要匿 名内部类的地方直接用标签定义一个内部类即可。如果要使这个内部类匿名,可以 不指定标签的 id 或 name 属性。例如下面这段代码: 代码中定义了匿名的 Student 类,将这个匿名内部类赋给了 school 类的实例对象。 3.2.9 自动装配 在表 3.2.4 节中曾提到过标签的 autowire 属性,它负责自动装配标签定 义 JavaBean 的属性。这样做可以省去很多配置 JavaBean 属性的标签代码,使代码整洁、 美观。但是它也有负面影响,即使用自动装配之后,无法从配置文件中读懂 JavaBean 需要 什么属性。自动装配存在很多不正确的装配问题,例如错误装载属性、“byType”属性和 “constructor”属性对相同类型参数无法判断等。当然,将自动装配和手动装配混合使用 也能解决此问题。下面通过一个实例来分析如何使用自动装配。 实例位置:.....mr..\.03..\.sl..\.02.. 首先创建创建一个学生类 Student,定义学号、姓名、性别、年龄等属性,并添加对 应的 set()与 get()方法。程序代码如下。 public class Student { private String ID; private String name; private int age; private String sex; public int getAge(){ return age; 41 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 } public void setAge(int age) { this.age = age; } ⋯⋯//省略其他get()/set()方法 } 同样创建一个教师类 Teacher,定义姓名、性别和年龄等属性,并添加对应的 set() 与 get()方法。程序代码如下。 public class Teacher { private String name; private int age; private String sex; public int getAge(){ return age; } public void setAge(int age) { this.age = age; } ⋯⋯//省略其他get()/set()方法 } 创建教学档案类 TeachFile,定义 Teacher 和 Student 两个属性,并添加print()方法 用于输出教师与学生的信息。程序代码如下。 public class TeachFile { private Teacher teacher; private Student student; public Student getStudent(){ return this.student; } public void print(){ System.out.println("------教师信息------"); System.out.println("姓名:"+teacher.getName()); System.out.println("年龄:"+teacher.getAge()); System.out.println("性别:"+teacher.getSex()); System.out.println(); System.out.println("------学生信息------"); System.out.println("学号:"+student.getID()); System.out.println("姓名:"+student.getName()); System.out.println("年龄:"+student.getAge()); System.out.println("性别:"+student.getSex()); } public void setStudent(Student student1){ this.student = student1; } public void setTeacher(Teacher teacher){ this.teacher = teacher; } } 在配置文件 appContext.xml 中定义刚刚创建的类,并为其赋值。其中TeachFile 类采 42 第 1 篇 基 础 篇 P A R T 1 用了自动装配。程序代码如下。 150 李海 16 王刚 32 在这个配置文件中定义了 Student 类和 Teacher 类,并为姓名、年龄和性别属性赋值。 在定义 TeachFile 类时,没有传递任何参数,而是采用了 autowire 属性自动配置 TeachFile 类所需要的属性。下面编写一个主类 PrintInfo 类来输出档案信息。程序代码如下。 import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class PrintInfo { public static void main(String[] args) { Resource res = new ClassPathResource("appContext.xml"); BeanFactory bf = new XmlBeanFactory(res); TeachFile tf = (TeachFile) bf.getBean("teachFile"); tf.print(); } } 程序运行结果如图 3.3 所示。 43 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 图 3.3 PrintInfo 程序运行结果 1 在上面实例的配置文件 appContext.xml 中,TeachFile 类采用自动装配,将 Teacher 类和 Student 类注入到对应的属性中。语法格式如下。 在 autowire 属性中指定类型为“byName”。autowire 属性共支持 5 种装配类型,下面 分别介绍每种装配类型的用法。 (1)no:autowire 采用的默认值,采用自动装配。必须使用 ref 直接引用其他 Bean, 这样可以增加代码的可读性,并且不易出错。 (2)byName:以属性名区分自动装配。在容器中寻找与 JavaBean 的属性名相同的 JavaBean,并将其自动装配到 JavaBean 中。 如果用上面的实例来解释,TeachFile 类的实例对象 teachFile 包含的两个属性分别 是 Teacher 类和 Student 类的实例对象,而配置文件中已经定义了这两个类的实例。在定 义 teachFile 实例时指定了自动装配类型为“byName”,容器会自动寻找 teachFile 实例需 要的属性(即 teacher 和 student 两个 JavaBean),并注入到 teachFile 实例中。 此类自动装配类型存在错误装配 JavaBean 的可能,如果配置文件中定义了与需要自动 装配的 JavaBean 属性相同而类型不同的 JavaBean,那么它会错误地注入不同类型的 JavaBean。读者可以将上面实例中的配置文件修改一下,将 student 和 teacher 两个 JavaBean 的类型保持不变,将名字调换一下,便会出现此问题。这时自动装配无法解决此 问题,只能通过混合使用手动装配来指定装配哪个 JavaBean。 (3)byType:以属性类型区分自动装配。容器会自动寻找与 JavaBean 的属性类型相同 的 JavaBean 的定义,并将其注入到需要自动装配的 JavaBean 中。 如果将上面配置 JavaBean 自动装配的类型修改为 byType,也可以实现相同的结果。 这种自动装配类型也会出现无法自动装配的情况。例如在配置文件中再次添加一个 Student 类或 Teacher 类的实现对象,byType 自动装配类型会因为无法自动识别装配哪一 个 JavaBean 而抛出 org.springframework.beans.factory.UnsatisfiedDependencyException 异常。要解决此 问题,只能通过混合使用手动装配来指定装配哪个 JavaBean。 (4)constructor:通过构造方法的参数类型自动装配。此类型会使容器自动寻找与 JavaBean 的构造方法的参数类型相同的 Bean,并注入到需要自动装配的 JavaBean 中。它 与 byType 类型存在相同的无法识别自动装配的情况。 (5)autudetect:这是最后一个自动装配类型,它首先使用 constructor 方式自动装 配,然后使用 byType 方式。当然它也存在与 byType 和 constructor 相同的异常情况。建 议在使用自动装配时,把容易出现问题的 JavaBean 使用手动装配注入依赖属性。 44 第 1 篇 基 础 篇 P A R T 1 3.3 BeanFactory 容器最重要的任务是创建并管理 JavaBean 的生命周期。在知道了如何装配 JavaBean 之后,需要了解 JavaBean 在容器中是如何工作的。下面分别介绍 Spring 的 BeanFactory 容器和 ApplicationContext 容器。 3.3.1 BeanFactory 中 JavaBean 的生命周期 了解 JavaBean 的生命周期非常重要,它可让用户清楚地知道 JavaBean 在容器中的一 切活动,直到它被销毁。图 3.4 是 BeanFactory 容器中 JavaBean 的生命周期流程图。 实例化 JavaBean XML或属 性文件 初始化 JavaBean 初始化JavaBean 调用Setter方法 实现 BeanNameAware 接口 调用 serBeanName() 方法 是 实现 BeanFactoryA ware接口 否 调用 setBeanFacory() 方法 是 绑定 BeanPostProc essor 否 调用 postProcessBefore Initialization() 方法 是 实现 Initializing Bean接口 调用 afterPropertiesSet() 方法 是 否 指定 init-method 方法 否 调用 init-method 指定的方法 是 调用 postProcessAfter Initialization() 方法 是 执行autowire 使用 JavaBean 调用Bean 的方法 销毁 JavaBean 销毁JavaBean 实现 DisposableBe an接口 调用destroy()方法是 指定 init-method 方法 调用 destroy-method 指定的方法 是 否 图 3.4 BeanFactory 中 JavaBean 的生命周期流程图 45 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 BeanFactory 中 JavaBean 的生命周期分为实例化、初始化、使用和销毁 4 个阶段,下 面针对每一步工作作详细介绍。 1.实例化 JavaBean 调用 BeanFactory 的 getBean()方法时,在容器中会创建 JavaBean 的实例对象,并根 据指定的 XML 或属性文件的定义,自动收集JavaBean 的定义信息,通过 标签为 JavaBean 构造方法传参初始化 JavaBean 的实例对象。 2.初始化 JavaBean 在 JavaBean 被实例化的过程中,容器会按照JavaBean 的定义,初始化JavaBean 的所 有属性和依赖关系。具体的步骤如下。 (1)在 JavaBean 的定义中,如果标签使用了 autowire 属性,Spring 会对 JavaBean 完成自动装配。 (2)通过 get()和 set()方法配置 JavaBean 的属性。 (3)如果JavaBean 实现了 BeanNameAware 接口,容器将调用 JavaBean 的 setBeanName() 方法来传递 JavaBean 的 ID。 (4)同样,如果 JavaBean 实现了 BeanFactoryAware 接口,容器将调用 JavaBean 的 setBeanFactory()方法将容器本身注入到 JavaBean 中。 (5)如果在容器中注册了 BeanPostProcessor 接口的实现类,将调用这个实现类的 postProcessBeforeInitialization()方法,完成 JavaBean 的预处理方法。 (6)如果 JavaBean 实现了 InitializingBean 接口,容器会调用 JavaBean 的 afterPropertiesSet()方法修改 JavaBean 的属性。 (7)在 XML 中配置 JavaBean 时,如果用 init-method 属性指定了初始化方法,那么容 器会执行指定的方法来设置属性。 (8)最后,容器中如果注册了 BeanPostProcessor 的实现类,将调用实现类的 postProcessAfterInitialization()方法完成 JavaBean 的后期处理。 3.使用 JavaBean 在 XML 配置文件中定义了 JavaBean 以后,便可以直接在 Spring 的应用程序中调用容 器的 getBean()方法来获得并使用 JavaBean 的实例对象。例如调用 JavaBean 中某个属性 的 get()方法获取属性值。 4.销毁 JavaBean 关闭容器时,容器会销毁所有 JavaBean,但是如果 JavaBean 定制了特殊的销毁方法, 容器会在销毁该 JavaBean 之前调用这个方法完成资源回收等操作。详细说明如下。 (1)在销毁 JavaBean 之前,如果 JavaBean 实现了 DisposableBean 接口,那么容器会 调用 JavaBean 的 destroy()方法来完成销毁前的工作,例如在 JavaBean 销毁之前关闭其 使用的数据库连接,关闭文件数据流等。 (2)如果在 JavaBean 的定义信息中指定了 JavaBean 的销毁方法,那么在 JavaBean 被销毁之前会先去执行指定的方法。如果同时实现了步骤(1)的接口,会先执行步骤(1) 的 destroy()方法,即 DisposableBean 接口优先于 JavaBean 的定义。 46 第 1 篇 基 础 篇 P A R T 1 3.3.2 JavaBean 的预处理和后处理 在 Spring 管理的 JavaBean 生命周期的初始化阶段,容器会查看是否实现了 BeanPostProcessor 接口的 JavaBean,并且在初始化JavaBean 之前与初始化之后分别调用 接口的 postProcessBeforeInitialization()和 postProcessAfterInitialization()两个 方法。这使得程序开发人员可以切入到 JavaBean 的初始化阶段,实现预处理和后处理。下 面介绍 BeanPostProcessor 接口的定义,代码如下。 package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; } JavaBean 的预处理是在 JavaBean 初始化之前调用的方法,通过接口的 postProcessBeforeInitialization()方法定义预处理的业务逻辑或初始化数据,可以用于 定义属性的默认值等操作。而 JavaBean 的后处理是在预处理及 JavaBean 的装配信息指定 的初始化方法执行完以后,调用 postProcessAfterInitialization()方法来检查或修改 JavaBean 的属性配置、对属性添加或过滤等。 编写实现了 BeanPostProcessor 接口的 JavaBean 之后,并不能马上实现预处理和后处 理功能,需要在容器中调用 addBeanPostProcessor()方法进行注册才能发挥其应有的功 能。下面以简单的工厂招聘实例来说明预处理和后处理的实现。 实例位置:.....mr..\.03..\.sl..\.03.. 创建 Worker 接口,它包含一个 printInfo()方法输出本次招聘的结果信息。程序代 码如下。 package com.lzw.factory; public interface Worker { public void printInfo(); } 建立实现 Worker 接口的 TempWorker 类。工厂需要招收临时工人,如果招收的工人技 能优秀,则直接转为正式工人。在TempWorker 类中简单定义了 3 个属性,工人的姓名(name)、 工作类型(workType)、薪水(pay),并添加 get()和 set()方法,实现了接口的 pringInfo() 方法。程序代码如下。 package factory; public class TempWorker implements Worker { private String name;// 工人姓名 private String workType;// 工作类型,临时或正式 private String pay;// 基本工资 public String getPay(){ return pay; } 47 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 public void setPay(String pay) { this.pay = pay; } public String getWorkType(){ return workType; } public void setWorkType(String workType){ this.workType = workType; } public void printInfo(){ System.out.println("\n姓 名:" + name); System.out.println("工作类型:" + workType); System.out.println("基本工资:" + pay); } public String getName(){ return name; } public void setName(String name) { this.name = name; } } 创建招聘记录的 logFile 类存储应聘信息。属性workerList 记录了所有应聘合格的工 人,程序代码如下。 package factory; import java.util.ArrayList; import java.util.List; public class LogFile { private List workList = new ArrayList(); public List getWorkList(){ return workList; } public void setWorkList(List workList){ this.workList = workList; } } 编写 JavaBean 的配置文件。其中定义的3 个 JavaBean 都是 TempWorker 类,分别命名 为“worker1”、“worker2”和“worker3”代表 3 个工人,并初始化工人的姓名,“worker2” 被直接转为正式工人,他们都被记录到 logFile 中。程序代码如下。 48 第 1 篇 基 础 篇 P A R T 1 王强 500 宋钢 正式工人 2800 王良 创建 BeanPostProcessorExample 类来负责 JavaBean 的后处理任务。在配置文件中, 每个工人都必须有姓名属性,而基本工资和工作种类并没有全部填写。本类通过 JavaBean 的后处理方式,将未赋值的属性调整为默认值。其关键代码如下。 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof TempWorker){ TempWorker worker = (TempWorker) bean; if (worker.getPay() <= 800) worker.setPay(800); if (worker.getWorkType() == null || worker.getWorkType().equals("")) worker.setWorkType("临时工"); return worker; } return bean; } 49 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 最后编写 TakeIn 类输出本次招聘信息。实现方法很简单,从 logFile 中读取记录,直 接迭代输出本次的招聘者信息即可。程序关键代码如下。 package com.lzw.factory; import java.util.Iterator; import java.util.List; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class TakeIn { public static void main(String[] args) { Resource rs = new ClassPathResource("applicationContext.xml"); XmlBeanFactory bf = new XmlBeanFactory(rs); BeanPostProcessorExample bp = new BeanPostProcessorExample(); bf.addBeanPostProcessor(bp); LogFile log = (LogFile) bf.getBean("log"); List workerList = log.getWorkList(); System.out.println("本次招收" + workerList.size() + "位工人,资料如下:"); Iterator it = workerList.iterator(); while (it.hasNext()){ Worker worker = (Worker) it.next(); worker.printInfo(); } } } 运行 TakeIn 类,程序会输出本次招收的工人数量和工人信息。程序运行结果如图 3.5 所示。 图 3.5 招工记录 3.4 ApplicationContext BeanFactory 实现了 IoC 控制,所以它可以称为“IoC 容器”,而 ApplicationContext 在容器中注册 BeanPostProcessor 的实例来对 JavaBean 进行预处 理和后处理 50 第 1 篇 基 础 篇 P A R T 1 扩展了 BeanFactory 容器并添加了对 I18N(国际化)、生命周期事件的发布监听等更加强 大的功能,使之成为Spring 中强大的企业级 IoC 容器。在这个容器中提供了对其他框架和 EJB 的集成、远程调用、WebService、任务调度和 JNDI 等企业服务。在 Spring 应用中, 大多采用 ApplicationContext 容器来开发企业级的程序。 ApplicationContext 接口有 3 个实现类,可以实例化其中任何一个类来创建 Spring 的 ApplicationContext 容器。下面分别介绍这 3 个实现类。 (1)ClassPathXmlApplicationContext 类 ClassPathXmlApplicationContext 是 ApplicationContext 接口的 3 个实现类之一, 它从当前类路径中检索配置文件并装载它来创建容器的实例。具体语法格式如下。 ApplicationContext context=new ClassPathXmlApplicationContext(String configLocation); 参数说明: configLocation:指定了 Spring 配置文件的名称和位置。 (2)FileSystemXmlApplicationContext 类 FileSystemXmlApplicationContext 类同样是 ApplicationContext 接口的实现类,它 和 ClassPathXmlApplicationContext 类的区别在于读取 Spring 配置文件的方式。它不再 从类路径中获取配置文件,而是通过参数指定配置文件的位置,可以获取类路径之外的资 源。具体语法格式如下。 ApplicationContext context=new FileSystemXmlApplicationContext(String configLocation); (3)XmlWebApplicationContext 类 这个类主要应用在 Web 系统中,详细介绍可参见第 9 章。 JavaBean 在 ApplicationContext 容器和在 BeanFactory 容器中的生命周期基本相同, 只是在初始化的第 4 步(setBeanFactory()方法)之后和第 5 步(BeanPostProcessor 的 postProcessBeforeInitialization() 方 法 ) 之 前 , 如 果 JavaBean 实 现 了 ApplicationContextAware 接口,容器会调用 JavaBean 的 setApplicationContext()方法 将容器本身注入到 JavaBean 中,使 JavaBean 包含容器的应用。 3.4.1 容器的后处理方法 本章的 3.3.2 节介绍了如何在 Spring 容器中利用 BeanPostProcessor 接口对容器中的 JavaBean 进行预处理和后处理,而容器本身也有后处理功能。在容器载入所有 JavaBean 的定义信息之后和实例化这些 JavaBean 之前执行 BeanFactoryPostProcessor 接口的 postProcessBeanFactory()方法。下面的例子定义了 BeanFactoryPostProcessor 接口的实 现,并将容器中所有 JavaBean 的名字输出到控制台。 实例位置:.....mr..\.03..\.sl..\.04.. 首先需要定义实现 BeanFactoryPostProcessor 接口的 PostBeanFactory 类,在 postProcessBeanFactory()方法中遍历容器中所有 JavaBean 的名称,代码如下。 package com.lzw; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; public class PostBeanFactory implements BeanFactoryPostProcessor{ 51 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); for (int i = 0; i < beanDefinitionNames.length; i++) { System.out.println(beanDefinitionNames[i]); } } } 然后编写 Spring 的配置文件 applicationContext.xml。其中定义了 PostBeanFactory 类的实例“postBeanFactory”,再编写一个空类“test”,在配置文件中配置这个空类的 3 个实例。配置代码如下。 最后编写 Factory 类来创建容器,它什么也不用做,只需要创建容器,程序运行时会 输出容器中所有 JavaBean 的名称。程序代码如下。 package com.lzw; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Factory { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); } } 程序运行结果如图 3.6 所示。 图 3.6 输出容器中所有 JavaBean 的名字 遍历容器中所有的 JavaBean 的名字 52 第 1 篇 基 础 篇 P A R T 1 3.4.2 定制属性编辑器 属性编辑器在 Spring 之外,来自java.beans.PropertyEditor 接口,它支持各种不同 类型显示和更新属性值的方式。大多数属性编辑器只需要支持 PropertyEditor 接口中的部 分方法。简单的属性编辑器可能只支持 getAsText()和 setAsText()方法。当定制与参数对 象类型相对应的属性编辑器时,每个属性编辑器都必须编写 setValue()方法,都应该有一 个空的构造方法。 PropertyEditor 接口有很多当前项目用不到的方法,如果用 PropertyEditor 接口定 制编辑器,则需要实现接口中定义的所有方法。但是使用继承接口的实现类 java.beans. PropertyEditorSupport 定制属性编辑器,只须重写需要的方法(例如setAsText())即可。 Spring 需要做的只是将现有的属性编辑器注册给指定的类。具体注册属性编辑器的语法格 式可参见下面的实例。 在下面输出用户信息的例子中,自定义了 MyDateEditor 编辑器,将 String 类型转换 为 Date 类型。 实例位置:.....mr..\.03..\.sl..\.05.. 建立 UserInfo 类来存储用户信息,其中除3 个基本类型的姓名、年龄和性别属性之外, 还有 java.util.Date 类型的出生日期属性。关键代码如下。 package com.lzw; import java.util.Date; public class UserInfo { private String name; //姓名 private char sex; //性别 private int age; //年龄 private Date date; //出生日期 public Date getDate(){ return date; } public void setDate(Date date) { this.date = date; } ⋯⋯ 此处省略部分get()/set()方法 public void printInfo(){ System.out.println("姓名:" + name + "\n性别:" + sex + "\n年龄:" + age + "\n出生日期:" + date.toLocaleString()); } } 编写 Date 类型的属性编辑器 MyDateEditor 类。继承 PropertyEditorSupport 类编写 属性编辑器只需要重写需要的方法。本实例定制的属性编辑器重写了 setAsText()方法, 根据 String 类型的参数创建 Date 类型的对象。程序代码如下。 package com.lzw; import java.beans.PropertyEditorSupport; import java.util.Date; 53 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 public class MyDateEditor extends PropertyEditorSupport { public void setAsText(String text) throws IllegalArgumentException { Date date=new Date(text); setValue(date); } } 编写 Spring 的 JavaBean 配置文件“applicationContext.xml”,其中定义了用户信息 的 UserInfo 类。最主要的是注册自定义的属性编辑器 MyDateEditor 类,这样在给 UserInfo 类注入 Date 类型的出生日期属性时,可以直接输入日期字符串而不必再实例化 Date 对象。 程序代码如下。 飛龍 26 1981/2/8 编写 PrintUserInfo 类获得用户信息的实例,并调用其 printInfo()方法输出用户信 息,其中的出生日期就是利用定制的属性编辑器 MyDateEditor 类注入的属性。程序代码如 下。 package com.lzw; import org.springframework.context.ApplicationContext; 这里的字符串将被实例 中的属性编辑器定义为 java.util.Date 类型的对象 注册属性编辑 器的方法 54 第 1 篇 基 础 篇 P A R T 1 import org.springframework.context.support.ClassPathXmlApplicationContext; public class PrintUserInfo { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); UserInfo ui=(UserInfo)context.getBean("user1"); ui.printInfo(); } } 运行 PrintUserInfo 类,程序将输出获 得的用户信息,结果如图 3.7 所示。 Spring 中有很多内置的属性编辑器可 以满足大部分需求,并且部分编辑器已经自 动注册到容器中,程序可以直接使用这些编 辑器。Spring 的属性编辑器包含在 org.spring framework.beans.propertyeditors 包中,如表 3.3 所示。 表 3.3 Spring 内置的属性编辑器列表 3.4.3 容器与 JavaBean 的耦合 Spring 利用 IoC 将 JavaBean 所需要的属性注入其中,而不需要编写程序代码来初 始化 JavaBean 的属性,使程序代码整洁、规范化,更重要的是它降低了 JavaBean 之间 的耦合度。Spring 开发的项目中的 JavaBean,不需要修改任何代码就可以应用到其他 程序中,但是在某些特殊情况下或者某些特殊的 JavaBean 中,它们需要知道自己在容 器中的名字和所在的容器。通常编写这样的 JavaBean 都是为了在 Spring 中实现特殊功 能,因为它们无法脱离 Spring 容器单独使用。接下来介绍 JavaBean 与 Spring 的两种 容器的耦合方法,使 JavaBean 获得容器的引用,另外还会介绍如何使 JavaBean 获得自 属性编辑器 说 明 ByteArrayPropertyEditor 字节数组编辑器 CharacterEditor 字符类型编辑器 CharArrayPropertyEditor 字符数组编辑器 ClassEditor Class 类型编辑器 CustomBooleanEditor Boolean 类型编辑器 CustomCollectionEditor Collection 集合类型编辑器 CustomDateEditor Date 类型编辑器 CustomNumberEditor 数值类型编辑器 FileEditor 文件类型编辑器 InputStreamEditor 输入流类型编辑器 LocaleEditor 地域信息编辑器 PropertiesEditor 属性类型编辑器 StringArrayPropertyEditor 字符串数组编辑器 StringTrimmerEditor 自动修正字符串属性的编辑器 图 3.7 定制属性编辑器运行结果 55 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 己在容器中的名字。 3.4.4 获得 BeanFactory 容器的引用 Spring 定义了 BeanFactoryAware 接口,容器会自动为所有实现了这个接口的 JavaBean 注入容器的引用(即将容器本身注入 JavaBean 中)。BeanFactoryAware 接口只包含一个方 法。其语法格式如下。 void setBeanFactory(BeanFactory beanFactory) throws BeansException; 获得 BeanFactory 容器的引用必须实现该接口的 setBeanFactory()方法,方法中传递 的 参 数 beanFactory 就是注入进来的容器的引用也就是容器本身。可参考以下 BeanFactoryAware 接口的实现代码。 public class AwareBeanFactory implements BeanFactoryAware { XmlBeanFactory beanFactory; public void setBeanFactory(BeanFactory arg0) throws BeansException { beanFactory=(XmlBeanFactory)arg0; } ⋯⋯调用beanFactory的方法 } 将上述代码定义的 AwareBeanFactory 类配置到 Spring 的 BeanFactory 容器中,这个 类就会自动获得容器的引用。在这个类中还可以调用容器的方法来实现特殊功能(例如调 用 beanFactory.getBeanDefinitionNames()获得所有 JavaBean 名字的数组或者调用 beanFactory. getSingletonNames()获得容器中所有定义为单一实例的 JavaBean 名字的数 组等)。 3.4.5 获得 ApplicationContext 容器的引用 ApplicationContext 是 BeanFactory 的扩展容器,并且添加了很多强大的功能。Spring 中也提供了 ApplicationContextAware 接口用于实现可以获取 ApplicationContext 容器引 用的 JavaBean。和BeanFactoryAware 接口类似,ApplicationContextAware 接口也只包含 了 一 个 注 入 容 器 引 用 的 setApplicationContext() 方 法 。 请 参 考 以 下 ApplicationContextAware 接口的实现代码。 public class AwareApplicationContext implements ApplicationContextAware { ApplicationContext context; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context=applicationContext; } ⋯⋯调用容器的方法完成特殊操作 } 将这个实现类配置到容器中,就可以自动获取容器的引用。 3.4.6 获得 JavaBean 在容器中的 ID 名称 除获取容器引用的接口之外,Spring 还提供了 BeanNameAware 接口用于获得 JavaBean 在容器中的名字,接口中只包含一个 setBeanName()方法。Spring 容器会在调用任何初始 化 JavaBean 的方法之前检测是否实现了 BeanNameAware 接口。如果 JavaBean 实现了该接 56 第 1 篇 基 础 篇 P A R T 1 口,容器会将该 JavaBean 的名字作为属性注入到 JavaBean 中。可参考以下的接口实现代 码。 public class AwareBeanName implements BeanNameAware { String beanName; public void setBeanName(String name) { beanName=name; } } setBeanName()方法中的 name 参数就是 JavaBean 在容器中的 ID 名称。 3.4.7 实现 Spring 的国际化 国际化几乎是大型项目所必备的功能,它以不同国家的语言构建本地化的页面。它之所以 被称为 I18N 是因为它的英文名称“internationalization”的第一个“I”和最后一个“N” 之间有 18 个字母。Spring 中定义了 MessageSource 接口来解析消息资源, ApplicationContext 实现了 MessageSource 接口,所以 ApplicationContext 容器可以解 析国际化资源。 消息的国际化需要编制多个对应特定语言的资源文件和一个必须的默认资源文档。例 如消息资源名为“messages”,那么对应的默认资源文件就是“messages.properties”,它 是容器找不到对应语言的资源时默认读取的资源文件,而“messages_en_US.properties” 对应着美国英语资源,其他语言如德语、墨西哥语言等都有对应的资源文件。 ApplicationContext 装配 JavaBean 的时候,会留意 ID 为“messageSource”的 JavaBean。它必须是 MessageSource 接口的实现类,并调用其中的方法获得消息资源。 MessageSource 接口的定义代码如下。 package org.springframework.context; import java.util.Locale; public interface MessageSource { String getMessage(String code, Object[] args, String defaultMessage, Locale locale); String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException; String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessage Exception; } 接口中包含的 3 个 getMessage()方法用于获得消息资源。ApplicationContext 实现了 MessageSource 接口,所以可以直接调用容器实现的接口方法。下面以应用程序的登录界 面为例完成简单的国际化功能。 实例位置:.....mr..\.03..\.sl..\.06.. 首先需要编制 3 个消息资源文件。 messages.properties 文件默认存储着简体中文消息资源,程序代码如下。 LoginFrame.loginName=\u7528\u6237\u540D\uFF1A LoginFrame.loginPass=\u5BC6\u7801\uFF1A LoginFrame.login=\u767B\u9646 LoginFrame.exit=\u9000\u51FA 57 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 messages_en_US.properties 文件存储着美国英语消息资源,程序代码如下。 LoginFrame.loginName=User Name: LoginFrame.loginPass=Password: LoginFrame.login=Login LoginFrame.exit=Exit messages_zh_TW.properties 文件以繁体字存储着台湾消息资源,程序代码如下。 LoginFrame.loginName=\u7528\u6236\u540D\uFF1A LoginFrame.loginPass=\u5BC6\u78BC\uFF1A LoginFrame.login=\u767B\u9678 LoginFrame.exit=\u95DC\u9589 然 后 编 写 Spring 的 配 置 文 件 , 其 中 定 义 ResourceBundleMessageSource 类 的 JavaBean,这个类是 MessageSource 接口的实现类,其ID 名称必须定义为“messageSource”, ApplicationContext 容器在初始化时会查找该名称的 JavaBean 来解析文本信息。本实例 中的 Spring 配置文件为“applicationContext.xml”,其配置代码如下。 messages 最后需要编写主程序 LoginFrame 类。它是一个应用程序主类,包含一个登录的主窗体 界面,其中应用到了用户名、密码和登录、退出两个按钮的文本消息。程序中使用容器对 MessageSource 接口的实现方法 getMessage()来获取国际化的消息文本。LoginFrame 类的 关键代码如下。 package com.lzw; ⋯⋯//省略import public class LoginFrame extends JFrame { private ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); static Locale locale = Locale.TAIWAN; public static void main(String[] args) { JFrame frame = new LoginFrame(); frame.setVisible(true); } private final JLabel label = new JLabel(); private final JTextField textField = new JTextField(); private final JLabel label_1 = new JLabel(); 初 始 化 ApplicationContext 容器并且定义 Locale 区域 为台湾省 58 第 1 篇 基 础 篇 P A R T 1 private final JTextField textField_1 = new JTextField(); private final JButton button = new JButton(); private final JButton button_1 = new JButton(); public LoginFrame(){ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int width = 200, height = 100; setBounds((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); final GridLayout gridLayout = new GridLayout(0, 2); gridLayout.setVgap(5); gridLayout.setHgap(5); getContentPane().setLayout(gridLayout); getContentPane().add(label); label.setHorizontalAlignment(SwingConstants.RIGHT); label.setText(context.getMessage("LoginFrame.loginName", null, locale)); getContentPane().add(textField); getContentPane().add(label_1); label_1.setHorizontalAlignment(SwingConstants.RIGHT); label_1.setText(context .getMessage("LoginFrame.loginPass", null, locale)); getContentPane().add(textField_1); getContentPane().add(button); button.setText(context.getMessage("LoginFrame.login", null, locale)); getContentPane().add(button_1); button_1.setText(context.getMessage("LoginFrame.exit", null, locale)); } } 程序运行结果如图 3.8 所示。修改程序中的 locale 属性可以获得不同的显示界面。 图 3.8 国际化的登录界面 3.4.8 事件的监听与发布 ApplicationContext 容器拥有发布和监听事件的能力,它通过容器的publishEvent() 方法将继承 ApplicationEvent 类编写的事件发布给容器中所有实现了 ApplicationListener 接口的 JavaBean。其中ApplicationListener 是 Spring 的监听器接 口。 1.Spring 的事件 在 Spring 的 org.springframework.context.event 包中包含两个事件类,它们都是 Application Event 事件的子类。 (1)ContextClosedEvent 事件 每个组件都使用 Application Context 容器的 getMessage() 方法获得本地消息文本。 59 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 此事件是容器的关闭事件。在容器被关闭时会产生该事件并发布给所有实现了监听器 接口的 JavaBean。 (2)ContextRefreshedEvent 事件 此事件是容器的刷新事件。该事件在容器刷新时产生。容器内所有实现了监听器接口 的 JavaBean 都会接收到 ContextRefreshedEvent 事件,另外容器初始化时也会产生该事件。 在 Spring 的 org.springframework.web.context.support 包中还有一个 Web 请求事件 Request HandledEvent,应用在 Web 容器中,当 Web 容器处理一个客户端请求之后会产生该事 件。 2.事件监听器 并不是所有在容器中声明的 JavaBean 都可以监听到 Spring 的事件。要想监听到 Spring 的事件必须实现 ApplicationListener 监听器接口,容器在产生事件时会自动通知 所有实现了该接口的 JavaBean。ApplicationListener 接口的定义代码如下。 package org.springframework.context; import java.util.EventListener; public interface ApplicationListener extends EventListener { void onApplicationEvent(ApplicationEvent event); } 接口中只包含一个 onApplicationEvent()方法,这个方法会获得一个 ApplicationEvent 事件的参数 event,接口的实现类(即监听器)需要判断这个参数的类 型是何种事件并完成相应的处理工作。 3.发布自定义事件 在项目的开发过程中,有时需要自定义事件来完成特定的功能。例如利用事件机制在成功 验证用户的登录信息以后,通知所有监听器初始化并启动项目。下面介绍这个功能的实现代码。 实例位置:.....mr..\.03..\.sl..\.07.. 编写自定义的 LoginEvent 事件,来存储事件源和登录的用户名并给出用户名的 get() 方法。程序代码如下。 package com.lzw; import org.springframework.context.ApplicationEvent; public class LoginEvent extends ApplicationEvent { String userName; public LoginEvent(Object source, String name) { super(source); userName = name; } public String getUserName() {//给出用户名的get()方法 return userName; } } 编写 LoginListener 事件监听器来捕获 LoginEvent 事件并且显示用户登录成功的信 息。程序关键代码如下。 package com.lzw; 60 第 1 篇 基 础 篇 P A R T 1 ⋯⋯//省略import public class LoginListener implements ApplicationListener { public void onApplicationEvent(ApplicationEvent event) { if (event instanceof LoginEvent) { //判断是否LoginEvent事件 LoginEvent e = (LoginEvent) event; JOptionPane.showMessageDialog((JFrame) e.getSource(), "恭喜" + e.getUserName() + ",登录成功!!!"); } } } 在 Spring 的配置文件中定义监听器,它的定义方法和定义普通 JavaBean 一样。其关 键代码如下。 定义 LoginFrame 登录窗体类。在 LoginFrame 类中创建容器对象,当用户输入登录信 息并单击“登录”按钮时验证登录信息。如果验证成功,则创建 LoginEvent 事件并调用容 器的 publishEvent()方法发布事件。关键代码如下。 package com.lzw; ⋯⋯//省略import public class LoginFrame extends JFrame { private final JLabel label = new JLabel(); private final JTextField userName = new JTextField(); private final JLabel label_1 = new JLabel(); private final JPasswordField password = new JPasswordField(); private final JButton button = new JButton(); private final JButton button_1 = new JButton(); ApplicationContext context; public static void main(String[] args) { JFrame frame = new LoginFrame(); frame.setVisible(true); } public LoginFrame(){ context = new ClassPathXmlApplicationContext("applicationContext.xml"); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int width = 200, height = 100; setBounds((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); ⋯⋯//省略部分组件的定义 } private class ButtonActionListener implements ActionListener { public void actionPerformed(final ActionEvent e) { if (userName.getText().equals("mr") || password.getText().equals("mrsoft")){ LoginEvent event = new LoginEvent(LoginFrame.this, userName .getText()); 验证登录信息并 发布登录事件。 61 C H A P T E R 3 第 3 章 Sprin g 基 础 与 容 器 context.publishEvent(event); } } } ⋯⋯//省略退出按钮事件代码 } 运行本实例,输入用户名“mr”和密码“mrsoft”,如图 3.9 所示,程序会创建并发布 LoginEvent 事件,LoginListener 监听器会捕获该事件并提示登录成功,结果如图 3.10 所示。 图 3.9 登录页面 图 3.10 提示页面 第 4 章 AOP 编程 AOP(Aspect Oriented Program)面向切面编程,是现在比较热 门的话题。提起AOP,可以追溯到 1990 年,当时,面向对象编程(OOP) 已经趋于成熟,并应用于软件开发。但是来自PARC 研究中心的研究人 员发现,在使用面向对象编程的过程中会产生局限性,他们对这种局 限性做了深入的分析后,提出了一种新的编程思想,这种编程思想就 是今天的 AOP。 从 AOP 的历史根源上看,它起源于 OOP 思想,是对 OOP 思想的补 充和完善,解决了 OOP 无力解决的问题。它们之间不能相互替代,只 能是互为补充、共利共赢。 AOP 思想的提出距今已经有 17 年历史,我们今天看到的各种 实现 AOP 的技术都源于 AOP 思想的提出。AOP 编程有以下显著特点。  AOP 能剥离与模块功能不相干的代码,使模块间分工明确  AOP 能有效地减少模块间的重复代码,提高程序员的工作效率  AOP 可以把公共逻辑分散地注入到程序的任何位置 4.1 AOP 简介 AOP(Aspect Oriented Program)被译作面向切面编程。“Aspect” 在英语中有“方面”的意思,传统的语义是指一件事情在从不同角度 上显现的特性。 AOP 正是站在程序运行的角度上来看待程序的结构。 “切面”可以形象地想象成一根长木棒切断后的形成的“截面”。 在 OOP 对象操作过程中,程序代码是上下衔接的。例如,某行对象操作抛出了异常,接 概 述 63 C H A P T E R 4 第 4 章 AOP 编 程 下来就要对异常做出处理。抽象地说程序是上下结构的。而 AOP 则面向“截面”,如,程序 在某个点抛出了异常,接下来不必编写处理异常的代码,可以把它看作是透明的,继续做下 面的事情。也许读者会不解,那产生的异常怎么办?异常到底是在哪里处理的?告诉大家, 在抛出异常时,AOP 会将程序拦住,并执行拦截器里面的代码,这段代码就用于处理异常。 这就是说 AOP 可以拦截程序流程,执行“拦截面”里的代码。 那么,这样做好处是什么呢?为什么要拦截程序流程呢?接下来举例给读者解答。假 如,有个业务要求:统计初一班级学生数学的平均成绩。前提条件:各科成绩在数据库表 中保存。从题目的要求上看,解决问题的关键步骤有 3 步。 (1)将数学成绩从数据库里取出来 (2)数学成绩相加求和 (3)将总分数除以学生总数,求得平均成绩。 然而,在使用 OOP 编程实现的时候,程序代码却远不止这 3 步。首先,要考虑创建数 据库的连接;将数学成绩取出后,还要考虑关闭连接。其次,还要考虑异常问题,在代码 中需要对数据库异常做出处理。 这样连接数据库、关闭数据库和异常代码与主业务逻辑取数据、相加求和、除以学生 总数是无关的,但是必须把它们写在一起。这样,与当前业务无关的代码和当前的主要业 务代码会纠缠在一起,不光降低了程序的可读性和可维护性,而且还使程序员过多地考虑 跟当前主要业务无关的事情,不能专注于当前业务逻辑的编写。 使用 AOP,则是上述问题很好的解决方案。它能有效地剥离跟当前主要业务无关的代 码,把与当前业务无关的代码统统写到一个代理对象里面去,由它“代劳”。这样,编写的 业务代码会更“干净”,也可以提高代码的可读性和可维护性,而且使程序模块之间的专业 分工更加明确,让编程人员专注于当前业务的编写。 当然,使用 AOP 还有其他很多特点,在这里就不一一列举了。通过本章的学习相信读 者会对 AOP 有一个深入的了解。 4.1.1 Spring AOP 概述 Spring AOP 是继 Spring IoC 之后 Spring 框架的又一大特性,它也是 Spring 框架的 核心内容。AOP 是一种思想,所有符合AOP 思想的技术,都可以是看作AOP 的实现。AspectJ 对 Java 语言进行了扩充,是一个语言级的 AOP 框架,虽然功能强大,但是在使用上过于复 杂。Spring AOP 建立在 Java 的代理机制之上,要比 AspectJ 简单得多,虽然在功能上没 有 AspectJ 那么强大,但是,Spring 框架已经基本实现了 AOP 的思想。在众多的 AOP 实现 技术中,Spring AOP 做得最好,也是最为成熟的。 Spring AOP 会实现 AOP 联盟(Alliance)制定的接口规范。AOP 联盟由许多团体组成, 这些团体致力于各个 Java AOP 子项目的开发,它们与 Spring 有相同的信念:让 AOP 使开 发复杂的企业级应用变得更简单,脉络更清晰。同时它们也在很保守地为 AOP 制定标准化 的统一接口,使得不同的 AOP 技术之间相互兼容。Spring AOP 的接口都实现了 AOP 联盟定 制标准化接口,这就意味着 Spring AOP 已经走向了标准化,将得到更快的发展。 Spring AOP 的实现基于 Java 的代理机制,从 JDK1.3 开始就支持代理功能。但是性能 成为一个很大问题,为了解决 JDK 代理性能问题,出现了 CGLIB 代理机制。它可以生成字 节码,所以它的性能会高于 JDK 代理。Spring 支持这两种代理方式。但是,随着 JVM(Java 虚拟机)的性能的不断提高,这两种代理性能的差距会越来越小。 64 第 1 篇 基 础 篇 P A R T 1 4.1.2 Spring AOP 术语 学习 Spring AOP 之前,首先要了解它的术语,它们是 Spring AOP 的基本组成部分。 1.切面(Aspect) 切面是对象操作过程中的截面,如图 4.1 所示。 图 4.1 切面图表示法 如图 4.1 所示,由于平行四边形拦截了程序流程,Spring 形象地把它叫切面。本书以 后提到的“切面”即指这个“平行四边形”。 “切面”实际上是一段程序代码,这段代码将被“植入”到程序流程中。 2.连接点(Join Point) 连接点是对象操作过程中的某个阶段点,如图 4.2 所示。 图 4.2 连接点图表示法 如图 4.2 所示,在程序流程上的任意一点,都可以是连接点。 它实际上是对象的一个操作,例如,对象调用某个方法、读写对象的实例或者某个方 法抛出了异常等等。 3.切入点(Pointcut) 切入点是连接点的集合,如图 4.3 所示。 如图 4.3 所示,切面与程序流程的“交叉点”便是程序的切入点,确切地说它是“切 面注入”到程序中的位置,换句话说,“切面”是通过切入点被“注入”的。在程序中可以 有很多个切入点。 4.通知(Advice) 通知是某个切入点被横切后,所采取的处理逻辑。也就是说,在“切入点”处拦截程 序后,通过通知来执行切面,如图 4.4 所示。 65 C H A P T E R 4 第 4 章 AOP 编 程 图 4.3 切入点图表示法 图 4.4 通知图表示法 5.目标对象(Target) 所有被通知的对象(也可以理解为被代理的对象)都是目标对象。目标对象被 AOP 所 关注,它的属性的改变会被关注,它行为的调用也会被关注,它方法传参的变化仍然会被 关注。AOP 会注意目标对象的变动,随时准备向目标对象“注入切面”。 6.织入(Weaving) 织入是将切面功能应用到目标对象的过程,它通过代理工厂创建一个代理对象,这个 代理对象可以为目标对象执行切面功能。 AOP 的织入方式有 3 种:编译时期(Compile time)织入、类加载时期(Classload time) 织入、执行期(Runtime)织入。 Spring AOP 一般多见于执行期(Runtime)织入。 7.引入(Introduction) 对一个已编译完的类(class),在运行时期,动态地向这个类里加载属性和方法。 4.2 Spring AOP 的实现 现在以一个简单的 Spring AOP 的示例,来巩固前面所学的基本术语,加深对概念的理 解,为进一步的学习做铺垫。下面将讲解 Spring AOP 简单实例的实现过程,从而了解 AOP 编程的特点。 4.2.1 实现简单的 AOP 对方法做日志输出是常见的基本功能。传统的做法是把输出语句写在方法体的内 部。在调用该方法的时候,用输出语句输出信息来记录方法的执行。AOP 可以分离与业 务无关的代码。日志输出与方法都做些什么是无关的,它主要的目的是记录方法被执行 过。现在将利用 Spring AOP 使日志输出与方法分离,在调用目标方法之前执行日志输 出。 (1)编写目标对象 Target 类是被代理的目标对象,其中有一个 execute()方法,它可以专注与自己的职 能,现在使用 AOP 对 execute()方法做日志输出。在执行 execute()方法前,做日志输出。 目标对象的代码如下。 66 第 1 篇 基 础 篇 P A R T 1 实例位置:.....mr..\.04..\.sl..\.01.. public class Target { public void execute(String name){ System.out.println("executeMethod is here" + name); } } (2)创建通知 通知可以拦截目标对象的 execute()方法,并执行日志输出。创建通知的代码如下。 public class LoggerExecute implements MethodInterceptor { public Object invoke(MethodInvocation arg0) throws Throwable { before(); arg0.proceed(); return null; } private void before(){ System.out.println("executeMethod is exe!"); } } before()在 arg0.proceed()之前执行,用于输出一行英文。arg0 为 MethodInvocation 类型,arg0.proceed()是执行目标对象的 execute()方法。 可以看出,before()是在 execute()之前执行的。 4.2.2 创建代理 若想使用 AOP 的功能必须创建代理。可以用代码创建代理。 public static void main(String[] args) { Target target = new Target(); //创建目标对象 ProxyFactory di=new ProxyFactory(); di.addAdvice(new BeforeExecute()); di.setTarget(target); Target proxy=(Target)di.getProxy(); proxy.execute(" ni hao"); //代理执行execute()方法 } 运行结果如图 4.5 所示。 日志输出 图 4.5 运行结果 可见,在执行 execute(" ni hao")之前,先输出了日志。 创建代理 67 C H A P T E R 4 第 4 章 AOP 编 程 4.3 Spring 的切入点 Spring 的切入点是 Spring AOP 比较重要的概念,它表示注入切面的位置。根据切入 点织入的位置不同,Spring 提供了 3 种类型的切入点:静态切入点、动态切入点和自定义 切入点。下面分别进行讲解。 4.3.1 静态切入点 静态往往意味着不变,例如一个对象的方法签名是固定不变的,无论在程序的任何位 置调用,方法名都不会改变。静态切入点可以为对象的方法签名。例如在某个对象调用 execute()方法时,这个execute 方法就可以是静态切入点。静态切入点需要在配置文件指 定,关键配置如下。 //指定通知 .*getConn*.//指定所有以getConn开头的方法名都是切入点 .*closeConn*.  说明 ① * getConn *.是一个正则表达式,表示所有以 getConn 开头的方法都是切入点 ② * closeConn *.是一个正则表达式,表示所有以 closeConn 开头的方法都是切入 点 正则表达式由数学家 Stephen Kleene 于 1956 年提出来的,利用它可以匹配一些方法, 而不是列出每一个方法的名称。下面举例说明经常使用的正则表达式的用法。 (1)“.”,匹配任何单个字符。例如正则表达式“b.t”,它会匹配“bat”、“bet”、“b*t”、 “b#t”等。 (2)“*”,表示匹配前一个字符 0 次或多次,例如正则表达式“b.*t”,它会匹配“bt”、 “bet”、“beet”、“b*t”等。 (3)“+”,表示匹配前一个字符 1 次或多次,例如正则表达式“b.+t”,它会匹配“bet”、 “beet”、“b*t”、“b**t”等。 (4)“?”,表示只能匹配 0 或 1 次,用于描述紧靠该符号左边的字符出现的次数。例 如正则表达式“b.?t”,它会匹配“bet”、“b*t”、“bt”、“bat”等。 (5)“\”,表示连接符,例如正则表达式“b.\-t”,它会匹配“ba-t”、“b*-t”、“b-t”、 等。 68 第 1 篇 基 础 篇 P A R T 1 4.3.2 动态切入点 动态切入点是相对于静态切入点的。静态切入点只能应用在相对不变的位置,,而动态 切入点应用在相对变化的位置,例如方法的参数上。由于在程序运行过程中传递的参数是 变化的,所以切入点也会随之变化,它会根据不同的参数来织入不同的切面。由于每次织 入都要重新计算切入点的位置,而且结果不能缓存,所以动态切入点比静态切入点的性能 低很多,但是它能够随着程序中参数的变化而织入不同的切面,所以它要比静态切入点要 灵活很多。因此静态切入点和动态切入点可以选择使用,当程序对性能要求很高而且相对 注入不是很复杂时可以选用静态切入点,当程序对性能要求不是很高而且注入也比较复杂 时,可以使用动态切入点。 4.3.3 静态切入点的优缺点 优点:由于静态切入点只在代理创建的时候执行一次,然后将结果缓存起来,下一 次被调用的时候直接从缓存中取得即可,所以,在性能上使用静态切入点要远高于动态 切入点。 就是说,程序在运行的时候织入并运行切面的实现代码,这实际上是利用 Java 的反射 机制作到的。Java 的反射机制允许程序在运行的时候,动态生成对象、执行对象方法和改 变对象属性。但是,每一次利用反射来改变对象的状态,都会大大降低程序的性能。所以 反射使用的越多程序性能越差。 静态切入点在第一次织入切面时,首先会计算切入点的位置。它通过反射在程序运行 的时候获得调用的方法名。如果这个方法名是定义的切入点,就会织入切面。然后,将第 一次计算结果缓存起来,以后就不需要再进行计算了。这样使用静态切入点的程序性能会 好很多。 虽然使用静态切入点的性能会高一些,但是它也具有一些缺点,当需要通知的目标对 象的类型多于一种,而且需要织入的方法很多时,使用静态切入点编程会很繁琐。而且使 用静态切入不是很灵活、性能降低。这时可以选用动态切入点。 4.3.4 深入静态切入点 前面已经说过,静态切入点是在某个方法名上织入切面的,所以在织入程序代码前, 要进行方法名的匹配。首先判断当前正在调用的方法是不是已经定义的静态切入点,如果 该方法已经被定义为静态切入点,则方法匹配成功,织入切面;如果该方法没有被定义为 静态切入点,则匹配失败,不织入切面。这个匹配过程是 Spring 自动进行的,不需要人为 编程的干预。 实际上 Spring 是使用 boolean matches(Method,Class)方法来匹配切入点的,用 method. getName()反射取得正在运行的方法的名。在 boolean matches(Method,Class)方 法中 Method 是 java.lang.reflect.Method 类型,method.getName()利用反射取得正在运 行的方法的名。Class 是目标对象的类型。该方法在 AOP 创建代理的时候被调用,并返回 结果,true 表示将切面织入,false 则不织入。下面介绍静态切入点的匹配过程,代码如 下。 69 C H A P T E R 4 第 4 章 AOP 编 程 .*execute.*//指定切入点 以下是 Matches()方法匹配成功后的代码。 public bollean matches(Method method,Class targetClass){ return(method.getName().equals(“execute”))//匹配切入点成功 } 4.3.5 深入 Spring 切入点底层 掌握 Spring 切入点底层将有助于更加深刻地理解切入点。下面将简单讲解 Spring 切 入点底层机制。 Pointcut 接口是切入点的定义接口,用它规定可切入的连接点的属性。通过扩展此接 口可以处理其他类型的连接点,例如域等(这样做很罕见)。切入点接口定义的代码如下。 public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); } 使用 ClassFilter 接口来匹配目标类,代码如下。 public interface ClassFilter { boolean matches(Class clazz); } 可以看到,在 ClassFilter 接口中定义了 matches()方法,意思是与⋯相匹配。其中 clazz 代表被检测的 Class 实例,该实例是应用切入点的目标对象,如果返回 true 表示目 标对象可以被应用切入点,如果返回 false 表示目标对象不可以应用切入点。 使用 MethodMatcher 接口来匹配目标类的方法或方法的参数,代码如下。 public interface MethodMatcher { boolean matches(Method m,Class targetClass); boolean isRuntime(); boolean matches(Method m,Class targetClass,Object[] args); } Spring 支持两种切入点,静态和动态。究竟执行静态切入点还是动态切入点,取决于 isRuntime()方法的返回值。在匹配切入点之前,Spring 会调用 isRuntime(),如果返回 false,则执行静态切入点,如果返回 true,则执行动态切入点。 4.3.6 自定义切入点 如果 Spring 提供的切入点无法满足开发需求,可以自己定义切入点。Spring 提供的 切入点很多,可以选择一个切入点继承它,并重载matches 方法;也可以直接继承 Pointcut 接口并且重载 getClassFilter()方法和 getMethodMatcher()方法,这样可以完成自己编写 切入点的实现。 70 第 1 篇 基 础 篇 P A R T 1 4.3.7 Spring 常见的切入点 Spring 提供了丰富的切入点,可供用户选择使用,目的是使切面灵活地注入到程序中。 例如使用流程切入点,可以根据当前调用堆栈中的类和方法来实施切入。下面列出了 Spring 常见的切入点,如表 4.1 所示。 表 4.1 Spring 切入点 切入点的实现类 说 明 org.springframework.aop.support.JdkRegexpMethodPointcut Jdk 正则表达式方法切入点 org.springframework.aop.support.NameMatchMethodPointcut 名称匹配器方法切入点 org.springframework.aop.support.StaticMethodMatcherPointcut 静态方法匹配器切入点 org.springframework.aop.support.ControlFlowPointcut 流程切入点 org.springframework.aop.support.DynamicMethodMatcherPointcut 动态方法匹配器切入点 4.4 创 建 通 知 Spring AOP 提供了 5 种类型的通知,它们分别是 Before Advice(前置通知)、After Returning Advice(后置通知)、Interception Around Advice(周围通知)、Throws Advice (异常通知)和 Introduction Advice(引入通知)。下面分别进行介绍。 4.4.1 Before 通知 从 Before 通知的字面意思可以看出,它会在目标对象的方法执行之前执行。在具体应 用中,需要实现org.springframework.aop.MethodBeforeAdvice 接口,并且要重写默认的 before()方法,该方法会在目标对象所指定的方法之前执行。在 before()方法执行完后, 如果没有异常,将会接着执行目标对象的方法。下面结合一个具体的实例讲解 Before 通知 的用法。 (1)创建目标对象 Message 类是被代理的目标对象,其中有一个run()方法,它会在控制台上打印出一 句话。该方法在执行前会被拦截,执行 Before 通知中的 before()方法。目标对象的代 码如下。 实例位置:.....mr..\.04..\.sl..\.02.. package com.spring.aop; public class Message { public void run(){ System.out.println("This is before advice!"); } } (2)创建 Before 通知 BeforeAdviceExample 类为目标对象的通知类,它实现了 MehtodBeforeAdvice 接口, 71 C H A P T E R 4 第 4 章 AOP 编 程 并重写了 before()方法。该方法会在目标对象的 run()方法执行之前被执行,其代码如下。 package com.spring.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class BeforeAdviceExample implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass()); System.out.println(method + "will running!"); } } before()方法中有 3 个参数,target 指目标对象,method 和 args 分别是该对象的方 法和参数。 (3)创建代理工厂 目标对象和通知类都已经创建好了,现在需要将目标对象和通知类“联系”起来,让 目标对象的 run()方法被拦截,执行通知中的 before()方法。 org.springframework.aop.framework.ProxyFactoryBean 类是目标对象和通知类的 “纽带”,配置这个类的时候要指定目标对象和拦截器的名称。本实例的目标对象是 Message 类,拦截器为 Before 通知。关键配置如下。 advice (4)编写测试类 TestSpringAOPAdvice 类用于测试目标对象方法在执行 run()方法之前执行 Before 通 知的 before()方法。其代码如下。 package com.spring.aop; import org.springframework.context.ApplicationContext; 指定目标对象 为 Message 指定拦截器为 Before 通知 72 第 1 篇 基 础 篇 P A R T 1 import org.springframework.context.support.FileSystemXmlApplicationContext; public class TestSpringAOPAdvice { public static void main(String args[]){ ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); Message message = (Message) context.getBean("proxyFactoryBean"); message.run(); } } 本实例的运行结果如图 4.6 示。 Run()执行结果 图 4.6 运行结果 从上面的结果可以看出,在执行 run()方法之前先执行了 Befoore 通知。 4.4.2 Throws 通知 Throws 通知在方法抛出异常时才会执行,但它和 After 通知一样,是在目标对象的方 法执行完毕后执行。与 After 通知不同的是,目标对象需要抛出异常,Throws 通知才会被 触发执行。使用它时需要实现 org.springframework.aop.ThrowsAdvice 接口,但这个接口 没有定义任何的方法,所以开发者可以在实现这个接口的类中定义自己的方法,例如 AfterThrowing([Method],[args],[target], Throwable)。包含在[ ]中的参数是可以省略 的,但一定要有 Throwable 的子类作为其中的参数。 下面结合实例进一步介绍 Throws 通知。 (1)创建目标对象 Message 类非常简单,其中有一个带参数的 run()方法,该方法用于抛出异常。其代码如下。 实例位置:.....mr..\.04..\.sl..\.03.. package com.spring.aop; public class Message { public void run(String str)throws Exception{ System.out.println("Hello " + str ); throw new Exception("程序异常!"); } } (2)创建 Throw 通知 使用 ThrowsAdviceExample 类创建 Throw 通知。该类在 Message 类抛出异常时,介入 Throw 通知,但该类要实现 ThrowAdvice 接口。其代码如下。 package com.spring.aop; 目标对象抛 出异常 Run()被拦截执 行 Before 通知 73 C H A P T E R 4 第 4 章 AOP 编 程 import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class ThrowsAdviceExample implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target,Throwable subclass){ System.out.println(target.getClass()); } } (3)创建代理工厂 在配置文件 applicationContext.xml 中创建 bean。这个配置文件和 Before 的配置文 件没有太大的区别,只不过这个是 After 通知。其代码如下。 throwAdvice (4)编写测试类 现在可以介绍测试文件 TestSpringAOPAdvice 类了,在调用 Message 类中的 run()方 法时要抛出异常。其代码如下。 package com.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class TestSpringAOPAdvice { public static void main(String args[]){ ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); Message message = (Message) context.getBean("proxyFactory"); try{ message.run("freefog"); }catch(Exception e){ System.err.println(e); 目标对象抛异 常触发 Throws 通知 74 第 1 篇 基 础 篇 P A R T 1 } } } 在对程序做 Throws 通知的时候,Throws 通知并不介入应用,对应用程序产生影响。 它只是记录一些异常信息。下面是该实例的运行结果,如图 4.7 所示。 异常通知执行结果 图 4.7 运行结果 4.4.3 After 通知 After 通知和 Before 通知非常相似,不过它会在目标对象的方法执行之后执行。在具 体应用中需要实现 org.springframework.aop.AfterReturningAdvice 接口,还需要实现 AfterReturning Advice 接口中的 afterReturning()方法。该方法会在目标对象所指定的 方法之后执行。下面通过一个具体的实例来讲解 After 通知的用法。 (1)创建目标对象 仍然使用 Message 类作为目标对象,关键代码如下。 实例位置:.....mr..\.04..\.sl..\.04.. package com.spring.aop; public class Message { public void run(){ System.out.println("This is after advice!"); } } (2)创建 After 通知 AfterAdviceExamplel 类为目标对象的通知类,它实现了 AfterReturningAdvice 接口, 并实现了 afterReturning ()方法。其代码如下。 package com.spring.aop; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class AfterAdviceExample implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass()); System.out.println(method + "was running!"); } } 在 afterReturning ()方法中有 4 个参数,returnValue 指本方法的返回值,target 指目标对象,method 和 args 是分别是该对象的方法和参数。 (3)创建代理工厂 75 C H A P T E R 4 第 4 章 AOP 编 程 目标对象和通知类都已经创建好了,现在需要将目标对象和通知类“联系”起来,让 目标对象的 run()方法执行之后,执行通知中的 afterReturning ()方法。 org.springframework.aop.framework.ProxyFactoryBean 类是目标对象和通知类“纽 带”,配置这个类的时候要指定目标对象和拦截器的名称。本实例的目标对象是 Message 类,拦截器为 Before 通知。关键配置如下。 advice (4)编写测试类 TestSpringAOPAdvice 类用于测试在目标对象方法中执行 run()方法之后执行 After 通知的 afterReturning ()方法。其代码如下。 package com.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class TestSpringAOPAdvice { public static void main(String args[]){ ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); Message message = (Message) context.getBean("proxyFactoryBean"); message.run(); } } 本实例的运行结果如图 4.8 所示。 After 通知执 行结果 图 4.8 运行结果 76 第 1 篇 基 础 篇 P A R T 1 从上面的结果可以看出,在执行 run()方法之后先执行了 After 通知。 4.4.4 Around 通知 Around 通知会在目标方法执行前和执行后被执行,如果需要,可以跳过目标方法不予 执行。同样,在使用它的时候需要实现 org.aopalliance.intercept.MethodInterceptor 接口,它是 AOP 联盟所提供的标准接口,并重写默认的 invoke()方法。 (1)创建目标对象 仍然使用 Message 类作为目标对象,打印一条语句。关键代码如下。 实例位置:.....mr..\.04..\.sl..\.05.. package com.spring.aop; public class Message { public void run(){ System.out.println("This is after advice!"); } } (2)创建 Around 通知 AfterAdviceExamplel 类为目标对象的通知类,它实现了 MethodInterceptor 接口, 并重写 invoke()方法。在目标对象调用 run()方法的时候,执行此通知。关键代码如下。 public class AroundAdivce implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { before(); invocation.proceed(); after(); return null; } public void before(){ System.out.println("this is before method execute !!!"); } public void after(){ System.out.println("this is after method execute !!!"); } } invocation.proceed()返回的是连接点类型,这里表示激活并执行目标对象的 run() 方法。从程序可以看出,在执行 invocation.proceed()语句之前先执行了 before()方法, 该方法用于打印一条输出语句,当然,在应用的时候 before()方法可以写复杂语句。待 before()方法执行完毕后,开始执行目标对象的 run()方法,主要使用 invocation.proceed()语句实现,当 run()方法执行完毕后,才开始执行 after()方法,打 印一条输出语句。可以看出,before()方法和 after()方法分别包含了 invocation.proceed()语句,并且在 invocation.proceed()执行的前后执行,这就是 Around 通知。  注意 在使用 invocation 对象来激活目标对象的 run()方法的时候,可以跳过目标方法不 予执行,甚至可以替换目标方法执行。这也是 Around 通知的一大特点。 在目标对象执行 run()方法的时候,将其拦截并执行 Around 通知的 invoke()方法。进 执行目标方法 77 C H A P T E R 4 第 4 章 AOP 编 程 入该方法后首先执行 before()方法,然后通过invocation.proceed()语句来激活并执行目 标对象的 run()方法执行。之后,再执行 after()方法。 (3)创建代理工厂 代理工厂的创建与 Before 通知、After 通知和 Throw 通知的代理工厂差不多,只不过 是将诸如 Before 通知等换成 Around 通知。相信通过前面的学习,下面的代码将很容易理 解,这里就不再详细说明了,如果不理解的可以参考 Before 通知、After 通知和 Throw 通 知的代理工厂的创建。关键代码如下。 aroundAdivce (4)编写测试类 TestSpringAOPAdvice 类是用来测试目标对象方法,在执行run()方法执行的前后分别 执行 before()方法和 after()。关键代码如下。 public class TestSpringAOPAdvice { public static void main(String args[]){ ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); Message message = (Message) context.getBean("proxyFactoryBean"); message.run(); } } 本实例的运行结果如图 4.9 所示。 目标对象 run()执行结果 图 4.9 运行结果 78 第 1 篇 基 础 篇 P A R T 1 4.4.5 Introduction 通知 Introduction 通知(引入通知)和前面的几个通知有很大的不同,它用于为目标对象 添加属性和方法。由于针对的是整个对象的所有方法,所以它没有切入点的概念,它能对 整个目标对象所有方法进行拦截并感知目标对象的状态属性的改变。 Introduction 通知首先要继承 DelegatingIntroductionInterceptor 类,然后重写它 的默认 invoke()方法。更重要的是 Introduction 通知还要实现混入接口,混入接口定义 了需要给目标对象引入新功能方法声明。 (1)创建目标对象 这个对象是被引入功能的对象,它在程序运行的时候,被引入新增功能。以学生成绩 单对象为例,目标对象代码如下。 public class Score { private String name; private float chinese; private float math; private float english; private float physics; private float chemistry; ⋯⋯ //setxxx和getxxx (2)创建混入接口 混入接口是目标对象在运行的时候获得新功能的方法声明。在通知类中将实现该接口 的功能,并在程序运行的时候,由引入通知将该功能注入到目标对象中,使目标对象动态 地获得新增功能。需要明确的是,混入接口功能模块是独立于目标类的,也就是说目标对 象不是通过继承混入接口来获得接口提供的功能,而是由引入通知将接口的新功能注入到 目标对象中。如果要为目标对象添加新功能,首先定义该功能的接口。下面是求学生平均 成绩的功能接口。定义如下。 实例位置:.....mr..\.04..\.sl..\.06.. public interface IAverage { public float average(); } (3)创建引入通知 引入通知主要实现了 IAverage 接口的 average()的功能,并将该功能添加给 Score 对 象。引入通知可以拦截 Score 对象调用的所有方法,并获得方法的传参,将参数相加求和, 再除以 5,求得平均成绩。通知继承了 DelegatingIntroductionInterceptor 类,并实现 IAverage 接口,重写它的 invoke()方法来处理拦截,并调用父类 super.invoke()方法来 返回结果。代码如下。 public class IntroductionAverageAdvice extends DelegatingIntroductionInterceptor implements IAverage { private static float sum; public float average(){ return IntroductionAverageAdvice.sum / 5; 79 C H A P T E R 4 第 4 章 AOP 编 程 } public Object invoke(MethodInvocation mi) throws Throwable { if ((mi.getArguments().length > 0)){ Object ob = (Object) mi.getArguments()[0]; if (ob instanceof Float){ Float object = (Float) mi.getArguments()[0]; this.sum += object.floatValue(); } } return super.invoke(mi); } } (4)创建测试引用代码 该代码主要是测试为目标对象 Score(学生成绩单)添加求平均成绩的功能。引入通知 会拦截所有的 setxxx 方法,并获得setxxx 方法里的参数。引入通知将参数相加(见invoke 里代码)得到一个静态变量 sum,然后在测试程序中调用接口的功能 avg.average()求平均 成绩。关键代码如下。 public static void main(String[] args) { Score target=new Score(); IntroductionAverageAdvice average=new IntroductionAverageAdvice(); //创建通知 对象 IntroductionAdvisor advisor=new DefaultIntroductionAdvisor(average); //创建引入 通知 ProxyFactory factory=new ProxyFactory(); //创建代理 工厂 factory.setTarget(target); //设置目标 对象 factory.addAdvisor(advisor); //添加通知 factory.setOptimize(true); //使用CGLIB代理 Score score=(Score) factory.getProxy(); //获得Score 代理 score.setName("linda"); //设置姓名为linda 说明:当程序运行至此,会拦截此方 法 score.setChemistry(89.0f); //设置化学成绩 说明:当程序运行至此,会拦截此方 法 score.setChinese(145f); //设置语文成绩 说明:当程序运行至此,会拦截此方法 score.setEnglish(120f); //设置英语成绩 说明:当程序运行至此,会拦截此方法 score.setMath(56f); //设置数学成绩 说明:当程序运行至此,会拦截此方法 score.setPhysics(130f); //设置物理成绩 说明:当程序运行至此,会拦截此方法 String name=score.getName(); IAverage avg=(IAverage)score; //转换为IAverage 接口 float average1=avg.average(); //求平均成 绩 System.out.println(name+" :的平均成绩是:"+average1);} 运行结果如图 4.10 所示。 80 第 1 篇 基 础 篇 P A R T 1 图 4.10 运行结果 81 C H A P T E R 4 第 4 章 AOP 编 程 4.5 Spring 的 Advisor Advisor 是切入点的配置器,它能将 Adivce(通知)注入程序中的切入点的位置,可 以直接编程实现 Advisor,也可以通过 XML 来配置切入点(Pointcut)和 Advisor。由于 Spring 的切入点有多样性,而 Advisor 是为各种各样的切入点而设计的配置器,因此相应地 Advisor 也有很多。下面介绍几个常用的 Advisor。 4.5.1 DefaultPointcutAdvisor 它位于 org.springframework.aop.support.DefaultPointcutAdvisor 包下,是默 认切入点通知者。它可以把一个通知配置给一个切入点。使用之前,它首先要先创建一 个切入点和通知。 (1)创建一个通知 这个通知可以是自定义的,关键代码如下。 public TestAdvice implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { Object Val=mi.proceed(); return Val; } } (2)创建自定义切入点 Spring 提供了很多类型的切入点,可以选择一个继承它并且分别重写 matches ()方法 和 getClassFilter()方法,实现自己定义的切入点。关键代码如下。 public class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches (Method method Class targetClass){ return (“targetMethod”.equals(method.getName())); } public ClassFilter getClassFilter(){ return new ClassFilter(){ public boolean matches(Class clazz) { return (clazz==targetClass.class); } }; } } (3)分别创建通知和切入点实例 分别创建一个通知和切入点的实例,关键代码如下。 Pointcut pointcut=new TestStaticPointcut (); //创建一个切入点 Advice advice=new TestAdvice (); //创建一个通知 (4)创建 Advisor 把通知配置给切入点 Advisor advisor=new DefaultPointcutAdvisor(pointcut,advice); //创建Advisor 82 第 1 篇 基 础 篇 P A R T 1 (5)创建 AOP 代理 如果使用 SpringAOP 的切面注入功能,需要创建AOP 代理。可以通过 Spring 的代理工 厂来实现。 Target target =new Target();//创建一个目标对象的实例 ProxyFactory proxy= new ProxyFactory(); proxy.setTarget(target);//target为目标对象 //前面已经对“advisor”做了配置,现在需要将“advisor”设置在代理工厂里 proxy.setAdivsor(advisor); Target proxy = (Target) proxy.getProxy(); Proxy.⋯⋯//此处省略的是代理调用目标对象的方法,目的是实施拦截注入通知 4.5.2 NameMatchMethodPointcutAdvisor 它位于 org.springframework.aop.support. NameMatchMethodPointcutAdvisor 包下, 是方法名切入点通知者,使用它可以更加简洁地将方法名设定为切入点。关键代码如下。 NameMatchMethodPointcutAdvisor advice=new NameMatchMethodPointcutAdvisor(new TestAdvice()); advice.addMethodName(“targetMethod1name”); advice.addMethodName(“targetMethod2name”); advice.addMethodName(“targetMethod3name”); advice.addMethodName(“targetMethod3name”); ⋯⋯//可以继续添加方法的名称 ⋯⋯//省略创建代理,可以参考上一小节创建AOP代理 其 中 new TestAdvice() 为 一 个 通 知 , advice.addMethodName(“targetMethod1name”)⋯⋯中的 targetMethod1name 是一个方 法的名称,advice.addMethodName(“targetMethod1name”)表示将 targetMethod1name() 方法添加为切入点。当程序调用 targetMethod1()方法时,会被执行通知(TestAdvice)。 4.6 使用 ProxyFactoryBean 创建代理 为了使目标对象能够获得切面的功能,使用 Spring 的代理工厂为目标对象创建 AOP 代理,织人切面功能。SpringIoC 容器和 SpringAOP 并不是完全分开的两个特性。使用 ProxyFactoryBean 可以让 SpringIoC 容器来管理 SpringAOP 组件,这可以实现更强大的功 能。 ProxyFactoryBean 类是设置 AOP 的核心,它实现了 org.springframework.beans. FactoryBean 接口,这样就可以使用 xml 文件来配置目标对和通知,只须定义 bean 即可被 IoC 容器所管理。ProxyFactoryBean 可以为目标对象生成代理,它通过 getObject()方法 来创建对象。下面介绍一个简单的使用 ProxyFactoryBean 代理的示例。 (1)编写目标对象 HelloWorld.java 非常简单,其中的方法 Said()和 get()各输出一句话。其代码如下。 package com.spring.aop; public class HelloWorld { public void Said() { System.out.println("Hello World!"); } 83 C H A P T E R 4 第 4 章 AOP 编 程 public void get(){ System.out.println("ni hao a!"); } } (2)创建 before 通知 SimpleBeforeAdvice.java 扩展了 MethodBeforeAdvice 接口,并实现了 MethodBefore Advice 接口的 before 方法。写这个类的目的是让程序在执行 Said()和 get()方法之前, 执行通知的 before()方法。其代码如下。 package com.spring.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class SimpleBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("Before Method: " + method.getName()); } } (3)创建 ProxyFactoryBean 代理 该配置文件相信读者不会感到陌生,在 4.4 节中曾经使用过 ProxyFactoryBean 代理。 使用 ProxyFactoryBean 代理的主要目的是让 IoC 容器管理 AOP 组件的依赖关系。关键代码 如下。 advisor .* 指定目标对象 指定 befroe 通知 指定拦截器 定义切入点配 置器 84 第 1 篇 基 础 篇 P A R T 1 经过前面的学习相信读者对这段代码已经非常熟悉了,这里笔者首先配置了 Proxy FactoryBean 代理类,并指明了它所代理的目标类是 HelloWorld,接着指定了拦截器 advisor。  注意 advisor 里边的属性 pattern 对应的“*”是表示对目标对象(HelloWorld)中的所 有方法都进行拦截。 (4)编写测试类 该测试类用于测试在调用 Said()方法和 get()方法之前执行 befroe()方法。关键代码 如下。 package com.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class ProxyFactoryBeanTest { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); HelloWorld helloworldbean = (HelloWorld) context .getBean("helloworldbean"); helloworldbean.Said(); helloworldbean.get(); } } 首先通过 ApplicationContext 来加载 applicationContext.xml 文件,接着获取代理 类的实例,最后调用 HelloWorld.java 的两个方法。 本实例的运行结果如图 4.11 所示。 get()方法的执行结果 Said()方法的执行结果 图 4.11 运行结果 从上图所显示的结果可以看出,目标对象分别在调用 Said()方法和 get()方法之前输出了一行语句,这说明 通过 ProxyFactoryBean 类实现了代理功能。 85 C H A P T E R 4 第 4 章 AOP 编 程 4.7 使用自动代理 在上一节中通过 ProxyFactoryBean 来为目标对象提供 Advice,但是当程序大量使用 AOP 的时候,使用 ProxyFactoryBean 就需要写大量的配置文件,这是非常费时的一件事。 为此,Spring 提供了自动代理对象 BeanNameAutoProxyCreator 类和 DefaultAdvisorAuto ProxyCreator 类来解决这个问题。下面将会结合具体的实例来详细讲解它们的用法。 4.7.1 BeanNameAutoProxyCreator 类 BeanNameAutoProxyCreator 类允许开发者指定适当的 Bean 名称,这样 Spring 会自动 用指定的通知代理这些 Bean,同时它还允许在 Bean 名称中使用通配符,这样就能匹配所 有符合命名规则的 Bean。 下面是一个简单的示例,在这个示例中会使用通配符来匹配符合命名规则的 Bean,并 且 Bean 中的方法也是用通配符来匹配执行的。 HelloWorld 类非常简单,其中有两个方法 get()和 getTo()。其代码如下。 实例位置:.....mr..\.04..\.sl..\.08.. package com.spring.aop; public class HelloWorld { public void get() { System.out.println("Hello World!"); } public void getTo(){ System.out.println("ni hao a!"); } }  注意 这两个方法都是以 get 开头的。 SimpleBeforeAdvice 类 扩 展 了 MethodBeforeAdvice 接 口 , 并 实 现 了 MethodBeforeAdvice 接口的 before 方法。其代码如下。 package com.spring.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class SimpleBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("Before Method: " + method.getName()); } } aplicationContext.xml 文件的关键代码如下。 86 第 1 篇 基 础 篇 P A R T 1 get* *bean advice 从上面的代码可以看出,这里配置了 3 个 bean,helloworldbean 和 hellobean 和 helloworld,它们的类型都是 HelloWorld,使用advice 来实施通知。在这里使用beanName ProxyCreator 来代理这 3 个 bean,并定义了它的名称列表。注意这里指定了*bean 和 get*, *bean 是指只要下面对 bean 配置的 id 的值是以”bean”结尾的都会被代理,get*是指对 于 bean 中的方法只要是以 get 开头的都会被拦截。 测 试 类 ProxyFactoryBeanTest 类 , 它 首 先 通 过 ApplicationContext 来 加 载 application Context.xml 文件,接着获取 bean 的实例,最后调用 HelloWorld.java 的两 个方法。其代码如下。 package com.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class ProxyFactoryBeanTest { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); HelloWorld helloworldbean = (HelloWorld) context .getBean("helloworldbean"); HelloWorld hellobean = (HelloWorld)context.getBean("hellobean"); HelloWorld helloworld = (HelloWorld)context.getBean("helloworld"); helloworldbean.get(); helloworldbean.getTo(); System.out.println(""); hellobean.get(); hellobean.getTo(); System.out.println(""); helloworld.get(); 87 C H A P T E R 4 第 4 章 AOP 编 程 helloworld.getTo(); } } 本实例的运行结果如图 4.12 所示。 图 4.12 运行结果 从上面的运行结果图中可以看出,HelloWorld.java 前两次的调用都接到了通知,这 是因为在配置文件中它们的 id 值都是以 bean 结尾的,和上面是匹配的,而最后一次却没 有接到通知是因为它和上面的配置是不匹配的。 从这个小的示例可以看出,它与上一节的示例相比少了很多的配置文件,却能做更多 的事情。但要注意,bean 的命名要遵循命名规则。 4.7.2 DefaultAdvisorAutoProxyCreator 类 使 用 DefaultAdvisorAutoProxyCreator 只 须 创 建 相 应 的 通 知 者 , 就 可 以 在 Application Context 配置文件中通知任何的 bean 了。如下面的示例。 HelloWorld 类的代码如下。 package com.spring.aop; public class HelloWorld { public void getTo(){ System.out.println("Hello World!"); } } SaidHello 类的代码如下。 package com.spring.aop; public class SaidHello { public void getTo(){ System.out.println("ni hao a!"); } } PointCutImpl 类给切入点提供了一个与类 HelloWorld 匹配的 ClassFilter,和一个匹 配所有的方法的 MethodMatcher。这说明该切入点只与类 HelloWorld 相匹配。其代码如下。 package com.spring.aop; 88 第 1 篇 基 础 篇 P A R T 1 import org.springframework.aop.ClassFilter; import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; public class PointCutImpl implements Pointcut { public ClassFilter getClassFilter(){ return new ClassFilter(){ public boolean matches(Class clss){ return (clss == HelloWorld.class); } }; } public MethodMatcher getMethodMatcher(){ return MethodMatcher.TRUE; } } SimpleBeforeAdvice 类 扩 展 了 MethodBeforeAdvice 接 口 , 并 实 现 了 MethodBeforeAdvice 接口的 before 方法。其代码如下。 package com.spring.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class SimpleBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("Before Method: " + method.getName()); } } ApplicationContext.xml 文件的关键代码如下。 89 C H A P T E R 4 第 4 章 AOP 编 程 在上面的配置文件中,首先配置了自动代理类 DefaultAdvisorAutoProxyCreator,然 后配置两个 bean,它们的类型分别为 HelloWorld 和 SaidHello,最后配置通知者 DefaultPointcut Advisor , 并 在 其 中 配 置 切 入 点 PointCutImpl 和 通 知 SimpleBeforeAdvice。 测 试 类 ProxyFactoryBeanTest 首 先 通 过 ApplicationContext 来 加 载 applicationContext.xml 文件,接着获取 bean 的实例,最后调用实例的方法。其代码如 下。 package com.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class ProxyFactoryBeanTest { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "src/applicationContext.xml"); HelloWorld helloworldbean = (HelloWorld) context .getBean("helloWorldbean"); SaidHello hellobean = (SaidHello)context.getBean("saidHellobean"); helloworldbean.getTo(); System.out.println(""); hellobean.getTo(); } } 本实例运行结果如图 4.13 所示。 图 4.13 运行结果 从上面的结果可以看出,HelloWorld 的 getTo()方法被通知了,而SaidHello 的 getTo() 方法则没有被通知。 4.8 AOP 范例 4.8.1 范例概述 本章的范例是以一个用户注册的示例来演示实现 Spring AOP 编程的全过程。从题目上 看,范例实现的功能比较简单,只需要将用户的注册信息插入到数据库即可。范例的主要 目的是让读者通过简单的操作来理解 Spring AOP 的思想。 90 第 1 篇 基 础 篇 P A R T 1 下面演示一下操作的过程。启动 Tomcat,在浏览器中键入“http://127.0.0.1:8080/user/?” 即可,如图 4.14 所示。 图 4.14 注册界面 填写注册用户的信息,如图 4.15 所示。 图 4.15 注册界面 保存数据到数据表中,如图 4.16 所示。 91 C H A P T E R 4 第 4 章 AOP 编 程 图 4.16 数据库表中的数据 下面是控制台中的信息,如图 4.17 所示。 图 4.17 控制台信息 4.8.2 范例的特点 本范例的最大特点是,在插入数据之前,利用 Before 通知创建好数据库连接,插入 数据之后,利用 After 通知将数据库连接关闭。也就是说,将数据库的连接和关闭分别 写到两个切面中去。这两个切面分别在插入数据前创建连接和插入数据后关闭连接。这 样做的好处在于不必担心数据库的连接和关闭问题,可以专心编写插入数据的主要业务 逻辑。  说明 本范例这个特点,可以用Spring 提供的 JdbcTemplate 来实现。使用它的主要特点也 是不用考虑数据库的连接和关闭问题。本范例只是起到说明问题的作用,在实际开发中并 不实用。以后读者遇到访问数据库的需求时,可以直接使用 JdbcTemplate,使用它要比 本范例简单得多。本书将在第 5 章详细介绍 JdbcTemplate。 4.8.3 范例应用的知识点 Before 通知:在切入点执行前拦截,执行切面应用,创建数据库连接。 切入点:为 execute()方法执行插入数据的操作。 After 通知:在切入点执行后,执行切面应用,将数据库连接关闭。 切面 1: Before 中的切面,为创建数据库连接。 切面 2: After 中的切面,为关闭数据库连接。 92 第 1 篇 基 础 篇 P A R T 1 4.8.4 实现过程 1.创建代理接口 在该接口中声明了 3 个方法,getConn()为连接数据库方法,execute(sql)是执行 SQL 语句的方法,closeConn()为关闭数据方法。代码如下。 实例位置:.....mr..\.04..\.sl..\.10.. package com.mr.aop; public interface UserInterface { public abstract void getConn();//获得连接方法声明 public abstract void ExecuteInsert (String sql);//执行数据的插入操作 public abstract void closeConn();//关闭连接的方法声明 } 2.数据库的连接 该抽象类主要实现接口的 getConn()方法完成数据库的连接。首先要继承 UserInterface 接口,然后实现接口中的 getConn()方法,分别在程序中定义一个私有的 成员变量 Con 和 Stmt。当连接创建好后,其他对象可以通过调用 getStmt()来获得数据 库连接。关键代码如下。 public abstract class ConnClass implements UserInterface { private Connection Con = null; private Statement Stmt = null; public void getConn(){ String url = "jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=db_aop4";// 数据库路径 try { Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");//数据库注册驱动 Con = DriverManager.getConnection(url, "sa", ""); //创建连接 System.out.println("Connection 已经创建!"); //向控制台输出创建连 接 Stmt = Con.createStatement(); //创建连接状态 System.out.println("Statement 已创建!"); //向控制输出创建状态 信息 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public Statement getStmt(){ return this.Stmt; } public void setStmt(Statement stmt) { Stmt = stmt; } 创建数据库连 接的方法 提供获得连接 的方法 93 C H A P T E R 4 第 4 章 AOP 编 程 //下面方法只是实现了部分关闭功能,全部的关闭功能在它的子类exeAndClose中完全实现 public void close(){ if (this.Con != null) if (this.Stmt != null) { try { this.Con.close();//关闭连接 System.out.println("Connection 已关闭!");//向控制台输出关闭连接的信息 this.Stmt.close();//关闭连接状态 System.out.println("Statement 已关闭!");//向控制台输出关闭连接状态的信 息 } catch (SQLException e) { e.printStackTrace(); } } } 3.创建业务类 ExecuteInsert 类主要用于完成主业务的编写。本实例业务是将注册信息插入数据库, 由 execute()方法来完成。在编写该方法时,只需要专心编写注册业务逻辑,不需要考虑 数据库的连接和关闭问题。 ExecuteInsert 类首先继承了 BaseProxyClass 抽象类。由于 BaseProxyClass 抽象类 继承了 UserInterface 接口,而 executeInsert 类又继承了 BaseProxyClass 抽象类,所以 ExecuteInsert 类间接的继承了 UserInterface 接口,并从而实现接口中的 execute()方法。 关键代码如下。 public class executeInsert extends ConnClass { private Statement state; public Statement getState(){ return state; } public void setState(Statement state) { this.state = state; } public void execute(String sql) { try { this.state.execute(sql); //将数据插入数据库 System.out.println("注册成功!"); //提示数据录入成功 } catch (SQLException e) { e.printStackTrace(); } } public void closeConn(){ super.close(); //调用父类的关闭功能将父类创建的连接关 闭  注意 读者应仔细阅读程序中向控制台打印的信息,它能够帮助读者理解程序的执行过程。 本实例实现的功能不是主要的,而重点在于理解程序的执行过程。 94 第 1 篇 基 础 篇 P A R T 1 if (this.state != null) { try { this.state.close(); //将本类的连接状态关闭 System.out.println("数据库关闭成功!"); //提示数据库关闭成功 } catch (SQLException e) { e.printStackTrace(); } } } } 4.数据库的关闭 数据库的关闭分两部分来完成。由于 ConnClass 类创建连接后,executeInsert 类需 要获得一个由 ConnClass 类创建的连接状态,才能把数据插入数据库,所以在 executeInsert 类中也需要将连接状态关闭。 (1)在父(ConnClass)类中关闭连接 在调用该类的 getConn()方法时,会创建一个数据库连接和一个连接状态,待数据库 插入完毕后,要关闭此类中的连接和连接状态。该类的 close()方法可以关闭本类的数据 库连接。关键代码如下。 public void close(){ if (this.Con != null) if (this.Stmt != null) { try { this.Con.close();//关闭连接 System.out.println("Connection 已关闭!"); //向控制台输出关闭连接的 信息 this.Stmt.close();//关闭连接状态 System.out.println("Statement 已关闭!"); //向控制台输出关闭连接状 态的信息 } catch (SQLException e) { e.printStackTrace(); } } (2)在子(ExecuteInsert)类中关闭连接状态 由于 ExecuteInsert 类需要把数据插入到数据库中,在插入数据前要获得由父类创建 的数据库连接,通过调用父(ConnClass)类的 getStmt()来获得连接状态。如此看来,在 ExecuteInsert 类中也有连接没有关闭,所以需要在子(ExecuteInsert)类中关闭连接状 态。由于 ExecuteInsert 类间接地继承了 UserInterface 接口,因此要实现接口中的 closeConn()方法。关键代码如下。 public void closeConn(){ super.close(); //调用父类的关闭功能将ConnClass类创建的连接 关闭 if (this.state != null) { try { this.state.close(); //关闭本类中的连接状态 System.out.println("数据库关闭成功!"); //提示数据库关闭成功 95 C H A P T E R 4 第 4 章 AOP 编 程 } catch (SQLException e) { e.printStackTrace(); } } } } 在上面代码的第 2 行有“super.close()”,它是调用父类中 close()方法,来关闭父 类创建的数据库连接。 5.创建 Before 通知 该通知会在 execute()方法执行之前执行,目的是创建数据库连接。为插入数据(执 行 execute())做准备。关键代码如下。 public class BeforeAdivsor implements MethodBeforeAdvice { public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("Before通知开始。。。。。。。") ; //提示BeforeAdivsor开始执 行 if (arg2 instanceof UserInterface){ UserInterface di = (UserInterface) arg2; // arg2为目标对象 di.getConn(); //调用getConn()创建连接 } //以下是将getConn()创建的连接状态,传递给ExecuteInsert实现类 ConnClass ci = (ConnClass) arg2; //转换为抽象类对象 ExecuteInsert bi = (ExecuteInsert) arg2; //转换为实现类对象 //将连接状态设置给实现类。目的是让execute方法执行前先获得连接 bi.setState(ci.getStmt()); } } 6.创建 After 通知 该通知会在 execute()方法执行之后执行,目的是关闭数据库的连接。关键代码如下。 public class AfterAdvice implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("After通知开始。。。。。。。"); //提示AfterAdvice开始 执行 if (method.getName().equals("execute")){ if (target instanceof UserInterface){ UserInterface di = (UserInterface) target; //target为目标对 象 di.closeConn(); //关闭数据库连接 } } } } 96 第 1 篇 基 础 篇 P A R T 1 7.创建 servlet_cogfig.xml 配置文件 该配置文件主要是定义了切入点,配置了 Before 通知、After 通知、目标类以及对目 标类的 execute 方法拦截器。 (1)定义切入点通知者(Advisor) //Before通知拦截 .*execute*.以execute开头的是切入点 (2)Before 通知注册 bean 容器 (3)After 通知注册 bean 容器 (4)目标对象 ExeAndClose 注册 bean 容器 (5)创建 ProxyFactoryBean 代理 com.mr.aop.UserInterface//代理接口 //拦截的目标对象 PointcutAdvisor//Before通知拦截 affterAdvice// After通知通知拦截 (6)配置 SpringMVC SpringMVC 在本章不作讨论,将在以后的章节详细介绍。不懂的读者可以跳过此处, 或者可以理解为 StructMVC。只要知道是将表单数据提交给 commitAction 类,在 commit Action 类中的 execute()方法中处理表单数据就可以了。有兴趣的读者可以参考下面 97 C H A P T E R 4 第 4 章 AOP 编 程 Spring MVC 的配置。关键代码如下。 org.springframework.web.servlet.view.JstlView / .jsp commitAction action execute (7)配置 commitAction 处理类 该类的 execute()方法用于添加注册数据,为能够使用 AOP 功能,这里将配置好的代 理工厂注入使用,关键配置如下。 //指定上面配置好的代理工厂 98 第 1 篇 基 础 篇 P A R T 1 8.创建 commitAction 类 该类是一个 Spring MVC 的控制器类,类似于 Struts 中 Action 类,将在后面的章节中 详细介绍。在这里可以把它理解为一个 Servlet。它的功能主要是获得表单数据,然后插 入数据库。代码如下。 public class CommitAction extends MultiActionController { private UserInterface myCheckClass; public UserInterface getMyCheckClass(){ return myCheckClass; } public void setMyCheckClass(UserInterface myCheckClass){ this.myCheckClass = myCheckClass; } public ModelAndView execute(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { request.setCharacterEncoding("gb2312"); String username = request.getParameter("username"); String password = request.getParameter("password"); String realname = request.getParameter("realname"); String age = request.getParameter("age"); String tel =request.getParameter("tel"); String sql="insert into tab_user values('"+username+"','"+password+"','"+realname+"','"+age+"','"+tel+"')"; //编写插入数据库的插入语句 System.out.println("..........................."); //标注 execute(sql)执行前 myCheckClass.execute(sql); //执行sql语句 System.out.println("..........................."); //标注 execute(sql)执行后 Map msg = new HashMap(); msg.put("msg", "注册成功!"); return new ModelAndView("index", msg); } } 运行后的控制台信息,如图 4.18 所示。 图 4.18 控制台信息 2 第 2 篇 高级篇 第 5 章 数据持久化管理 第 6 章 事务管理 第 7 章 远程服务 第 8 章 企业级服务 第 9 章 Spring 的 Web 框架 第 1 0 章 集成 Web 框架 主要内容  在 S pring 中使用 JD B C 模板  在 S pring 中使用 H ibernate 框架  在 S pring 中使用编程式事务  在 S pring 中使用声明式事务  在 S pring 中使用 R M I远征方法调用  在 S pring 中使用 H TTP Invoker 服务  S pring 中对 W eb S ervice 的支持  使用 S pring 的 JN D I服务  使用 S pring 发送电子邮件 工欲善其事 必先利其器 第 5 章 数据持久化服务 在 Spring 中关于数据持久化的服务主要是对数据访问对象(DAO) 和数据库 JDBC 的支持,本章主要讲解在 Spring 框架中如何使用 DAO 和 JDBC。主要内容如下。  Spring 中 DAO 框架  Spring 中操作 JDBC  Spring 整合 Hibernate 5.1 Spring 中 DAO 框架 DAO 代表数据访问对象(Data Access Object),它描述了一个应 用中 DAO 的角色。DAO 的存在提供了读写数据库中数据的一种方法,把 这个功能通过接口提供对外服务,程序的其他模块通过这些接口来访 问数据库。这样会有很多好处,首先,由于服务对象不再和特定的接 口绑定在一起,使得它们易于测试,因为它提供的是一种服务,在不 需要连接数据库的条件下就可以进行单元测试,极大地提高了开发效 率。其次,通过使用与持久化技术无关的方法访问数据库,在应用程 序的设计和使用上都有很大的灵活性,对于整个系统无论是在性能上 还是应用上都是一个巨大的飞跃。 5.1.1 数据访问对象 DAO 简介 DAO 全称是(Data Access Object)数据访问对象,它属于 O/R Mapping 技术的一种。在 O/R Mapping 技术发布之前,开发者需要直 接借助于 JDBC 和 SQL 来完成与数据库的相互通信。在 O/R Mapping 技术出现之后,开发者能够使用 DAO 或其他不同的 DAO 框架来实现与 RDBMS(关系数据库管理系统)的交互。借助于 O/R Mapping 技 100 第 2 篇 高 级 篇 P A R T 2 术,开发者能够将对象属性映射到数据表的字段、将对象映射到 RDBMS 中,这些 Mapping 技术能够为应用自动创建高效的 SQL 语句等。除此之外,O/R Mapping 技术还提供了延迟 加载、缓存等高级特征。而 DAO 是 O/R Mapping 技术的一种实现,因此,使用 DAO 能够大 量节省程序开发时间,减少代码量和开发的成本。 DAO 的主要目的就是将和持久性相关的问题与一般的业务规则和工作流隔离开来。它 为定义业务层可以访问的持久性操作引入了一个接口并且隐藏了实现的具体细节,该接口 的功能将依赖于采用的持久性技术而改变,但是 DAO 接口可以基本上保持不变。 5.1.2 Spring 的 DAO 支持 Spring 提供了一套抽象的 DAO 类,供开发者扩展,这有利于以统一的方式操作各种 DAO 技术,例如 JDO、JDBC 等。这些抽象 DAO 类提供了设置数据源及相关辅助信息的方法,而 其中的一些方法同具体 DAO 技术相关。目前,Spring DAO 抽象提供了以下几种类。 JdbcDaoSupport:JDBC DAO 抽象类。开发者需要为它设置数据源(DataSource),通 过子类,开发者能够获得 JdbcTemplate 以访问数据库。 HibernateDaoSupport:Hibernate DAO 抽象类。开发者需要为它配置 Hibernate Session Factory。通过其子类,开发者能够获得 Hibernate 实现。 JdoDaoSupport:Spring 为 JDO 提供的 DAO 抽象类。开发者需要为它配置 Persistence ManagerFactory,通过其子类,开发者能够获得 JdoTemplate。 在使用 Spring 的 DAO 框架进行数据库存取的时候,无须接触使用特定的数据库技术通 过一个数据存取接口来操作即可。下面通过一个简单的实例来讲解如何实现 Spring 中的 DAO 操作。 5.1.3 DAO 的简单应用 实例位置:.....mr..\.05..\.sl..\.01.. (1)定义一个实体类对象 User,然后在类中定义对应数据表字段的属性,并且编写相 应的 get()/set()方法,程序代码如下。 public class User { private Integer id; private String name; private Integer age; public Integer getId(){ return id; } public void setId(Integer id) { this.id = id; } public String getName(){ return name; } public void setName(String name) { this.name = name; } 101 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 public Integer getAge(){ return age; } public void setAge(Integer age) { this.age = age; } } (2)在 com 包中建立一个接口 IUserDAO.java 文件,并定义用于执行数据添加的 insert()方法和 find()查询方法。其中 insert()方法中使用的参数是一个实体对象的 JavaBean,它是步骤(1)创建的 User 实体类,该方法可以将这个实体类对象包含的数据 存储到数据库中,IUserDAO 接口的关键代码如下。 public interface IUserDAO{ public void insert(User user); public User find(Integer id); } (3)编写实现这个 DAO 接口的 UserDAO 类,并在这个类中实现接口中定义的方法。首 先定义一个用于操作数据库的数据源对象 DataSource,通过它创建一个数据库连接对象建 立与数据库的连接,这个数据源对象在 Spring 中提供了 javax.sql.DataSource 接口的实 现,只须在 Spring 的配置文件中进行相关的配置就可以。稍后会讲到关于 Spring 的配置 文件。这个类中分别实现了接口的两个抽象方法 insert()方法和 find()方法,通过这两个 方法来完成对数据库的访问。 在 find()方法中,定义了一个参数用于检索用户的基本信息,关键代码如下。 public User find(Integer id) { Connection conn = null; Statement stmt = null; try { conn = dataSource.getConnection(); stmt = conn.createStatement(); ResultSet result = stmt.executeQuery( "SELECT * FROM tb_user WHERE id=" + id.intValue()); if(result.next()){ Integer i = new Integer(result.getInt(1)); String name = result.getString(2); Integer age = new Integer(result.getInt(3)); User user = new User(); user.setId(i); user.setName (name); user.setAge(age); return user; } } catch (SQLException e) { e.printStackTrace(); } finally { 102 第 2 篇 高 级 篇 P A R T 2 if(stmt != null) { try { stmt.close(); } catch(SQLException e) { e.printStackTrace(); } } if(conn != null) { try { conn.close(); } catch(SQLException e) { e.printStackTrace(); } } } return null; } 在 insert()方法中定义一个 User 实体类的对象作为参数,为这个实体类对象进行私 有变量的赋值,然后调用执行数据添加的方法来完成数据的操作。关键代码如下。 public void insert(User user) { String name = user.getName(); int age = user.getAge().intValue(); Connection conn = null; Statement stmt = null; try { conn = dataSource.getConnection(); stmt = conn.createStatement(); stmt.execute("INSERT INTO tb_user (name,age)" + "VALUES('" + name + "'," + age + ")"); } catch (SQLException e) { e.printStackTrace(); } finally { if(stmt != null) { try { stmt.close(); } catch(SQLException e) { e.printStackTrace(); } } if(conn != null) { try { 103 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 conn.close(); } catch(SQLException e) { e.printStackTrace(); } } } } (4)编写 Spring 的配置文件 applicationContext.xml。在这个配置文件中首先定义 一个 JavaBean 名称为 DataSource 的数据源,它是 Spring 中的 DriverManagerDataSource 类的实例,然后在配置前编写完 userDAO 类,并且注入它的 DataSource 属性值,其具体的 配置代码如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=db_database05 sa (5)接下来建立一个 DAODemoTest 测试类,用于测试前面编写的 DAO 类。在 com 包下 建立一个 Java 类,该类还有 main()方法。在这个类中首先引入程序所须的类文件包,然 后定义 Spring 中的容器类 ApplicationContext 来读取 Spring 的配置文件。通过这个容器 来获得配置中的 JavaBean 对象,然后执行对象的相应方法,其代码如下。 public static void main(String[] args) { ApplicationContext context =new FileSystemXmlApplicationContext("applicationContext.xml"); User user = new User(); user.setId(new Integer(1)); 104 第 2 篇 高 级 篇 P A R T 2 user.setName("caterpillar"); user.setAge(new Integer(30)); IUserDAO userDAO = (IUserDAO) context.getBean("userDAO"); userDAO.insert(user); user = userDAO.find(new Integer(1)); System.out.println("name: " + user.getName()); System.out.println("name: " + user.getAge()); } (6)运行前面的测试类,在控制台会输出运行结果,如图 5.1 所示。 图 5.1 DAO 应用实例运行结果 通过这个实例,相信读者对于在 Spring 中如何使用 DAO 应该有所掌握。在这个实例中 使用的是 DriverManagerDataSource 类,它没有提供连接池的功能,只是用来简单的单机 连接测试。在真正的项目开发中并不提倡使用它,而是使用 BasicDataSource 类来获得连 接池的功能,它位于包 org.apache.commons.dbcp 下。关于 DBCP 包可以在 Spring 的相关 版本中找到,那里提供相关的方法和使用说明,这里不作具体的讲解,如果读者有兴趣可 以参考相关的资料,接下来将介绍如何在 Spring 框架中使用 JDBC。 5.2 Spring 中操作 JDBC Spring 对 JDBC 进行了良好的封装,通过提供相应的模板和辅助类,在相当程度上降 低了 JDBC 操作的复杂性,并且Spring 良好的隔离设计,使JDBC 封装类库可以脱离 Spring Context 独立使用。也就是说,即使系统并没有采用 Spring 作为结构性框架,也可以单独 使用 Spring 的 JDBC 部分(spring-dao.jar)来改善程序代码。 5.2.1 使用 JDBC 存在的问题 Java 数据库连接(JDBC)是一个标准 SQL(Structured Query Language,结构化查询 语言)数据库访问接口,可以为多种关系数据库提供统一访问。它也是一种基准,可以根 据此基准构建更高级的工具和其他接口。 在 Java/J2EE 等应用领域中 JDBC 是最为重要、基础的技术。无论任何 O/R Mapping 技术,其最终与 RDBMS(关系数据库管理系统)交互的技术都是 JDBC。 虽然 JDBC 有强大、灵活以及其他种种优点,但是在使用的时候仍存在问题,其中最主 105 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 要的问题就是数据访问的错误处理和数据库的连接关闭操作。  错误处理 SQLException 如果访问数据库有问题,所有的 JDBC 实现都会抛出一个 SQLException 异常,这也是 唯一使用的异常。在异常里只提供了两组代码,错误代码和 SQL 状态码,因为这些代码是 不同数据库厂商针对自己的产品来指定的。例如在 SQL Server 2000 中和在 Oracle 中同一 种错误对应的错误代码是不相同的,它们都是针对自己的数据库产品来给出不同的错误信 息,想要做到各种数据库产品都统一相当困难。因此使用原始的 JDBC 编写需要对特定的、 可移植的代码是不可能的。  与数据库连接、关闭有关的各种操作 在通过 JDBC 提供的各种接口类进行直接操作数据库的时候,其中最基本的ARUD(add、 retrieve、update 和 delete)4 种操作中,除了要编写所用的核心代码操作之外,还必须 编写数据库连接和关闭操作代码,用于保持对资源的释放和错误的处理。这样每个操作方 法代码中都必须包括这样的 try-catch-finally 代码块。具体关键代码如下。 public void insert(student stu) throws java.sql.SQLException{ java.sql.Connection conn = null; java.sql.PreparedStatement pstmt = null; try{ con = CommonaJdbc.conection; ⋯⋯⋯⋯.//实现添加代码 }catch(java.sql.SQLException sql){ sql.printStackTrace(); }finally{ try{ conn.close(); }catch(java.sql.SQLException sqlexception){ sqlexception.printStackTrace(); } } } } 在编写这样的代码结构时,要保证正确书写这些代码,否则会导致不可预测的程序错 误,并直接影响整个系统的性能,而且会在程序中存在大量冗余的代码,从而影响到开发 的效率。 Spring 提供的 JDBC 服务抽象框架完全解决了上面使用 JDBC 所带来的两个棘手的问题, JDBC 框架服务对象能够帮助管理这些操作,以便用户集中精力编写应用程序代码,而不是基 础代码,消除很多重复代码,例如异常处理、数据库连接、资源的打开和关闭等操作。 5.2.2 一个简单的实例 在进行 Spring 中 JDBC 框架学习的过程中,先看一个简单的实例,这个实例演示了怎 样对数据库中的 system_users 表进行数据添加和读取操作。这个实例使用的主要类是 JdbcTemplate,它也是框架中的核心类,代码如下。 106 第 2 篇 高 级 篇 P A R T 2 实例位置:.....mr..\.05..\.sl..\.02.. JdbcTemplateTest 类是一个测试类,目的在于演示在 Spring 中如何使用 JDBC 的模板 完成数据库的操作。程序代码如下。 package com.spring; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import java.util.List; import java.util.Iterator; import java.util.Map; public class JdbcTemplateTest { public JdbcTemplateTest(JdbcTemplate jtp,obj_user user){ this.queryObject(jtp,user); } private int queryObject(JdbcTemplate jtp,obj_user user){ String sql ="insert into system_users(userid,username,password) values(?,?,?)"; Object[] params= new Object[]{user.getUserid(),user.getUsername(),user.getPassword()}; jtp.update(sql,params); System.out.println("************************"); System.out.print("用户ID\t"); System.out.print("用户姓名\t"); System.out.println("用户口令\t"); System.out.println("************************"); List userList = jtp.queryForList("select * from system_users;"); Iterator iterator = userList.iterator(); while(iterator.hasNext()){ Map userMap = (Map)iterator.next(); System.out.print(userMap.get("userid") + "\t"); System.out.print(userMap.get("username") + "\t"); System.out.println(userMap.get("password") + "\t"); } System.out.print("**************************"); return 0; } public static void main(String[] args) { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); dataSource.setUrl("jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName= db_database05"); dataSource.setUsername("sa"); dataSource.setPassword(""); JdbcTemplate jt = new JdbcTemplate(dataSource); obj_user user = new obj_user(); user.setUserid("mr"); user.setUsername("明日科技"); user.setPassword("mrsoft"); new JdbcTemplateTest(jt,user); 设置 Spring 中的 数据源 DataSource 107 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 } } 在本例中,首先设置了一个 Spring 中的数据源 DataSource。使用 DataSource 是通过 依赖注入的方式,但是为了在一个代码片断中实现,这里将通过编程的方式创建它,然后 通过该数据源建立一个 JdbcTemplate,把DataSource 作为惟一的参数传递给构造方法 Jdbc Template,然后执行 JdbcTemplate 中的 update 方法完成进行数据更新功能。接下来调用 JdbcTemplate 的 queryForList 方法进行数据读取操作。 在这些代码中没有异常处理,也不需要 tyr-finally 块来关闭连接,因为JdbcTemplate 将管理这些连接,并且使编写的代码更加简洁。 编译、运行然后在控制台中输出相应的运行结果,如图 5.2 所示。 图 5.2 JdbcTemplateTest 运行结果 5.2.3 核心类 JdbcTemplate 实现 JDBC 操作 JDBC 框架中最主要的类是 JdbcTemplate,可以在 org.springframework.jdbc.core 包中找到它。JdbcTemplate 类在内部已经处理完了数据库资源的建立和释放,并可以避免 一些常见的错误,例如关闭连接、抛出异常等。因此,使用 JdbcTemplate 类简化了编写 JDBC 时所使用的基础代码。 JdbcTemplate 类可以直接通过数据源的引用实例化,然后在服务中使用,也可以通过 依赖注入的方式在 ApplicationContext 中产生并作为 JavaBean 的引用给服务使用。  注意 数据源应当总是作为一个 JavaBean 在 ApplicationContext 中配置。 JdbcTemplate 类运行了核心的 JDBC 工作流程,例如应用程序要创建和执行 Statement 对象,只须在代码中提供 SQL 语句。还有这个类可以执行 SQL 中的查询、更新或者调用存 储过程等操作,同时生成结果集的迭代数据。它还可以捕捉 JDBC 的异常并将它们转换成 org.springframework.dao 包中定义的通用的能够提供更多信息的异常体系。 在实际应用中使用这个类的时候,只须根据明确定义的规范来实现回调接口。例 如在使用 PreparedStatementCreator 回调接口的时候,必须创建一个由 JdbcTemplate 类所提供的连接对象并建立一个 PreparedStatement 对象,同时提供 SQL 语句和任何 必要的参数。 下面介绍 JdbcTemplate 类中的常用方法。 1.JdbcTemplate 类中的常用方法 JdbcTemplate 类中提供了接口来方便访问和处理数据库中的数据。这些方法提供 了基本的选项用于执行查询和更新数据库操作。数据查询和更新的方法中, JdbcTemplate 类提供了很多重载的方法,为程序的编写带来了相当的灵活性。表 5.1 列出了常用的方法。 108 第 2 篇 高 级 篇 P A R T 2 表 5.1 JdbcTempalate 类中的常用方法 操 作 类 型 方 法 名 称 说 明 int QueryForInt(String sql) int QueryForInt(String sql,Object[] args) sql:查询条件的语句 args:查询语句的条件 返回查询的数量,通常是聚合函数数值 long QueryForLong(String sql) long QueryForLong(String sql,Object[] args) sql:查询条件的语句 args:查询语句的条件 返回查询的数量 Object queryforObject (string sql,Class requiredType) Object queryforObject (string sql,Class requiredType,Object[] args ) sql:查询条件的语句 requiredType:返回对象的类型 args:查询语句的条件参数 返回满足条件的查询对象 List queryForList(String sql) 数据查询操作 List queryForList(String sql,Object[] args) sql:查询条件的语句 args:查询语句的条件参数 返回满足条件的对象列表 int update(String sql) int update(String sql, Object[] args)数据更新操作 int update (String sql,Object[] args,int[] argTpes) sql:查询条件的语句 args:查询语句的条件 返回更新数据的行数值 这些方法只适合简单的数据查询和更新操作。JdbcTemplate 还有很多方法来满足更高 级的处理要求,例如回调接口的使用以及用于处理返回结果的方法等,这些方法的使用将 在后面介绍。 范例位置:mr\05\fl\01范例 05-01 简单的 ARDU 操作 录像位置:mr\05\lx\01 本范例主要讲解如何使用 JdbcTemplate 类所提供的常用方法,来完成对数据库的 ARUD 不同操作。其中数据源DataSource 和 JdbcTemplate 类是通过 Spring 的依赖注入方式来完 成的。在 applicationContext.xml 的配置文件中首先配置了一个 bean,它的名字为 jdbc Template,对应的 class 值为 org.springframework.jdbc.core.JdbcTemplate,同时设置 它的 dataSource 属性,然后在配置一个名字为 dataSource 的 bean,并为属性进行赋值, 具体代码如下。 109 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=db_school sa 完成配置文件之后,编写一个可运行的 arduTest 类文件,在这个类中首先引入 Spring 框架中的相应类包以及系统类包,然后通过 Spring 的配置文件获得 jdbcTemplate 类,接 着执行相应的方法,具体代码如下。 package com.spring; import java.util.Iterator; import java.util.List; import java.util.Map; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; public class arduTest { private DriverManagerDataSource ds = null; private JdbcTemplate jtl = null; public arduTest(){ Resource resource = new ClassPathResource("applicationContext.xml"); BeanFactory factory = new XmlBeanFactory(resource); jtl =(JdbcTemplate)factory.getBean("jdbcTemplate"); } private void operatortable(){ int effectrow = jtl.update("insert into system_users(userid,username,password) values('mr0','明日科技0','mrsoft0')"); 110 第 2 篇 高 级 篇 P A R T 2 effectrow += jtl.update("insert into system_users(userid,username,password) values('mr1','明日科技1',' mrsoft1')"); effectrow += jtl.update("insert into system_users(userid,username,password) values('mr2','明日科技2', 'mrsoft2')"); effectrow += jtl.update("insert into system_users(userid,username,password) values('mr3','明日科技3', 'mrsoft3')"); System.out.println("共添加" + effectrow + "行,数据"); loginfo("添加后的数据如下"); effectrow = jtl.update("update system_users set password = 'ppp' where userid = ?",new Object[]{"mr1"}); System.out.println("影响的行数" + effectrow + "行,数据"); effectrow = jtl.update("delete from system_users where userid =?",new Object[]{"mr1"}); System.out.println("影响的行数" + effectrow + "行,数据"); loginfo("删除后的数据如下"); } private void loginfo(String infotitle){ System.out.println("**********" + infotitle + "*********"); System.out.print("用户ID\t"); System.out.print("用户姓名\t"); System.out.println("用户口令\t"); System.out.println("************************"); List userList = jtl.queryForList("select * from system_users"); Iterator iterator = userList.iterator(); while(iterator.hasNext()){ Map userMap = (Map)iterator.next(); System.out.print(userMap.get("userid") + "\t"); System.out.print(userMap.get("username") + "\t"); System.out.println(userMap.get("password") + "\t"); } System.out.println("**************************"); } public static void main(String[] args) { new arduTest().operatortable(); } } 使用 JdbcTemplate 中的 update、delete 和 queryForList 等方法来完成数据的添加、 修改、删除和查询功能,编写完成之后在控制台中输出执行结果,如图 5.3 所示。 图 5.3 程序运行结果 111 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 2.用 JdbcTemplate 类写入数据 JdbcTemplate 类进行数据写入主要是通过 update 方法,它实现了很多方法的重载特 征。在前面使用了 JdbcTemplate 类写入数据的常用方法 update(String),除了基本方法 之外,JdbcTemplate 类中也使用了更高一级的写入数据方式,即使用几个回调接口来向数 据库中写入数据。每一个接口的用法都有不同之处,首先看一下两个比较简单的接口,然 后讨论 Jdbc Template 类所提供的一些简洁处理。  update(PreparedStatementCreator)方法 这个接口的实现是负责创建预处理对象 PreparedStatement,它提供了以下方法。 public PreparedStatement createPreparedStatement(Connection con) throws SQLException; 当实现这个接口时,要从 Connection 参数创建并返回一个 PreparedStatement 对象, 但无须考虑异常的处理,这样在执行 JdbcTemplate 类的 update 方法时,可以用这种方式 来完成对数据表的添加,关键代码如下。 jtl.update(new PreparedStatementCreator(){ public PreparedStatement createPreparedStatement(Connection con) throws SQLException { String sql = "INSERT INTO system_users(userid,username,password) VALUES(?,?,?)"; PreparedStatement ps = con.prepareStatement(sql); ps.setString(1,"mr"); ps.setString(2,"明日科技"); ps.setString(3,"mrsoft"); return ps; } } );  update(String sql, PreparedStatementSetter )方法 这个接口负责为预处理对象 PreparedStatemen 进行参数赋值,它提供了以下方法。 public void setValues(PreparedStatement pst) throws SQLException; 当实现这个接口时,要调用参数 PreparedStatement 对象的 setString 方法为 SQL 语 句进行参数赋值,这样在执行 JdbcTemplate 类的 update 方法时,可以通过这种方式来处 理,其代码如下。 jtl.update("INSERT INTO system_users(userid,username,password) VALUES(?,?,?)",new PreparedStatementSetter(){ public void setValues(PreparedStatement pst) throws SQLException { pst.setString(1,"mr"); pst.setString(2,"明日科技"); pst.setString(3,"mrsoft"); } });  update(String, Object[], int[])方法 112 第 2 篇 高 级 篇 P A R T 2 将前面的代码修改成如下代码: String sql = "insert into system_users(userid,username,password) values(?,?,?)"; Object[] params = new Object[]{“mr”,”明日科技”,”mrsoft”}; int[] types = new int[]{Types.VARCHAR,Types.VARCHAR,Types.VARCHAR}; jtl.update(sql,params,types); 这是一种非常简洁的代码。JdbcTemplate 类在内部已经完成了创建 PreparedStatement Creator 和 PreparedStatementSetter 的对象,在使用的时候只要求 提供相应的 SQL 语句和参数即可完成数据写入操作。 3.用 JdbcTemplate 类读取数据 通过 JDBC 原始代码在数据库读取数据的时候需要通过 executeQuery 方法获得结果集 对象 ResultSet,这是任何读取操作所必须执行的一个步骤。在 Spring 的 JDBC 框架中已 经完成了这一步的处理工作,在使用JdbcTemplate 进行读操作的时候最常用的就是 query() 方法。在 Spring 中提供了很多 query()的方法,用于获取不同的结果集,例如在上面实例 中使用的 queryForList(String sql): List userList = jtl.queryForList("select * from system_users"); 这个方法从数据中读取了 system_users 表中的所有记录并将结果存储在 List 集合中 返回到调用者。 5.2.4 以对象方式操作 JDBC 通过上节的学习,相信读者已经掌握了如何在 Spring 框架中使用集成的 JDBC 操作类, 也了解了完全不需要在使用底层的数据库技术的情况下就可以编写逻辑代码的方法。但是 在它的相关方法中依然要编写相应的 SQL 语句,并且熟悉相关的 SQL 语法,也就是说这些 代码还是要和 SQL 语句紧密关联的。如果能够使用面向对象的方式来使用 JavaBean 操作数 据库,也就是将数据库中的数据绑定到 JAVA 对象中,就可以直接通过访问 JavaBean 的属 性读取数据表的数据。 Spring 提供的 org.springframework.jdbc.object 包,能够以一种对象的方式来设 计编写数据库的各种操作程序,只要事先继承或者直接实例化相对应的类并编译,然后 就可以重复利用这个实例,执行它的方法进行数据库相关的操作,而不用接触 SQL 语句 等相关细节。 下面将通过实例,介绍如何通过 Spring 框架实现以对象方式进行 JDBC 的操作。这里 还是以 5.1 节中的 JDO 实例为基础进行重新修改编写程序。 范例位置:mr\05\fl\02范例 05-02 深入 DAO 的应用范例 录像位置:mr\05\lx\02 该实例中使用了 SqlFunction 类、SqlUpdate 类以及 MappingSqlQuery 类,通过这几 个类来讲解如何以对象方式操作 JDBC,具体步骤如下。 (1)创建一个 SqlFunction 类的子类 CountUser 用于执行 SQL 中的函数。这里将通过 它来执行 SQL 中的 count()函数,统计查询数据的数目,其代码如下。 import javax.sql.DataSource; 113 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 import org.springframework.jdbc.object.SqlFunction; public class CountUser extends SqlFunction { public CountUser(DataSource datasource){ super(datasource,"select count(*) from tb_user"); compile(); } } (2)创建一个 SqlUpdate 类的子类 UserInsert 用于完成数据的添加操作。这里将通过 设置相关参数来完成对 SQL 的封装操作,其代码如下。 import java.sql.Types; import javax.sql.DataSource; import org.springframework.jdbc.object.SqlUpdate; public class UserInsert extends SqlUpdate { public UserInsert(DataSource dataSource){ super(dataSource, "INSERT INTO tb_user (id,name,age) VALUES(?,?,?)"); int[] types = {Types.INTEGER,Types.VARCHAR, Types.INTEGER}; setTypes(types); compile(); } } 其中,方法 setTypes(types)用于设定 SQL 中的“?”参数的数据类型。 (3)创建一个 MappingSqlQuery 的一个子类 UserQuery 用于查询用户的基本信息,其 代码如下。 import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.jdbc.object.MappingSqlQuery; public class UserQuery extends MappingSqlQuery { public UserQuery(DataSource dataSource){ super(dataSource, "SELECT * FROM user"); compile(); } protected Object mapRow(ResultSet rs, int count) throws SQLException { User user = new User(); user.setId(new Integer(rs.getString("id"))); user.setName(rs.getString("name")); user.setAge(new Integer(rs.getString("age"))); return user; } } (4)编写完前面的类之后,开始改写原来的接口类和 DAO 类。首先在接口类中改写定 义的接口方法,其代码如下。 import java.util.List; 114 第 2 篇 高 级 篇 P A R T 2 public interface IUserNew { public void insert(User user); public List allUser(); public int count(); } (5)定义一个新的 UserDAO 类,它实现了 IUserNew 接口。在这个类中首先引入相应的 类包文件,并且定义私有类型分别为 SqlUpdate、SqlQuery 和 SqlFunction 的私有变量来 完成数据的相应操作,其代码如下。 import java.util.List; import javax.sql.DataSource; import org.springframework.jdbc.object.SqlFunction; import org.springframework.jdbc.object.SqlQuery; import org.springframework.jdbc.object.SqlUpdate; public class UserDaoNew implements IUserNew { private SqlUpdate userUpdate; private SqlQuery userQuery; private SqlFunction userFunction; public void setDataSource(DataSource datasource){ userUpdate = new UserInsert(datasource); userQuery = new UserQuery(datasource); userFunction = new CountUser(datasource); } public List allUser(){ return userQuery.execute(); } public int count(){ return userFunction.run(); } public void insert(User user) { userUpdate.update(new Object[]{user.getId(),user.getName(),user.getAge()}); } } (6)修改测试类文件 SpringDAOTest 来完成新的测试程序,它的main 方法的修改代码 如下。 List list = userDAO.allUser(); for(int i = 0; i < list.size(); i++) { User next = (User) list.get(i); System.out.println("\n\tId:\t" + next.getId()); System.out.println("\tName:\t" + next.getName()); System.out.println("\tAge:\t" + next.getAge()); } (7)在 Spring 中的配置文件 applicationContext.xml 中修改如下代码。 (8)运行测试类文件,其运行效果如图 5.4 所示。 115 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 图 5.4 DAO 范例运行结果 5.3 Spring 整合 Hibernate 5.3.1 Hibernate 入门应用 Hibernate 是一个高性能、开放源码的持久框架,是对象/关系映射的解决方案。简单 地讲,就是将Java 中的对象实体及实体之间的关系映射至关系数据库中的表及表之间的关 系。Hibernate 提供了这个过程中的自动对应转换方案。它不仅提供了基础的对象/关系映 射,而且支持所有其他的复杂功能,例如缓存、延时加载、主动抓取和分布式缓存等。 Hibernate 框架是 Java 应用和关系数据库之间的桥梁,它负责 Java 对象和关系数据 库之间的映射。在Hibernate 内封装了通过 JDBC 访问数据库的操作,向上层应用提供了面 向对象的数据库访问 API,它协调应用与关系数据库的交互,让开发者专心于解决业务问 题。它可以与大多数新的或者现有的应用平稳的集成,并对JDBC 进行了非常轻量级的对象 封装,使得 Java 程序员可以随心所欲地使用对象编程思维来操作数据库。 Hibernate 可以应用在任何使用 JDBC 的场合,既可以在 Java 的客户端程序中使用, 也可以在 Servlet/JSP 的 Web 应用中使用,还可以在应用EJB 的 J2EE 架构中替代 CMP 完成 数据持久化的任务。 Hiberante 框架通过 XML 配置文件把实体对象映射到一个关系型数据库中。具体做法 是,每个持久类都有一个相应的 XML 映射文件,其扩展名为“.hbm.xml”,在一个典型的应 用系统中,建立很多这样的配置文件用来被系统所读取,并用它来创建 SessionFactory。 一个 SessionFactory 将作用于应用程序的整个声明周期,并可以获取Session 对象,从而 访问数据库。 目前,由于 Hibernate 框架已经成为了事实上标准的 O/R 映射技术,因此在 Spring 框架中已经对 Hibernate 框架进行了最深入的集成,即借助于 Spring Ioc 和 Spring AOP 对 Hibernate 框架进行了最为有效的集成。 下面以 Hibernate3 为例编写一个简单的 Hibernate 应用实例,使读者对 Hibernate 有一个简单的认识。 116 第 2 篇 高 级 篇 P A R T 2 实例位置:.....mr..\.05..\.sl..\.03.. (1)编写 Hibernate 配置文件 Hibernate 通过配置文件获得连接数据库的相关信息,该配置文件应该放在 classes 文件夹下,程序运行时要先读取这个配置文件进行系统初始化的设置。下面以 SQL Server 2000 数据库为例,介绍配置文件的配置方法。Hibernate 配置文件采用 XML 格式,默认的 文件名为 hibernate.cfg.xml,部分代码如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;databaseName=db_database05 sa true (2)持久化类的创建 首先,创建一个持久化类,实际上只是一个 JavaBean,其中没有引入任何 Hibernate API,里面只包含一些属性及与之对应的 set ()和 get ()方法。持久化类中的属性要与前 面创建的表的字段一一对应(使用的表结构和 5.1 节中的相同),其部分代码如下。 package com; public class User { private Integer id; private String name; private Integer age; public Integer getId(){ return id; } public void setId(Integer id) { this.id = id; } ⋯⋯..//省略了部分代码 } 其中的 set ()和 get ()方法必须符合特定的命名规则,set 和 get 后要紧跟属性名, 并且属性名的首字母要大写。如果属性的类型为 boolean,那么还可以用 isXXX()代替 getXXX()方法名。 (3)创建实体类 User 的映射文件 一个映射文件中可以有多个元素。如果有多个持久化类需要映射,则可以在同 一个文件中完成。但通常推荐每个持久化类对应一个映射文件,该映射文件的扩展名 为.hbm.xml,具体代码如下。 117 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 (4)Configuration 类是 Hibernate 的入口,负责在 Hibernate 初始化时加载默认文 件路径下的配置文件信息(hibernate.properties 或 hibernate.cfg.xml)到程序中,并 通过它的对象加载指定的映射文件到内存。然后创建一个SessionFactory 对象,把读入的 配置信息拷贝到 SessionFactory 对象的缓存中,最后通过 SessionFactory 对象来创建一 个 Session 对象。 Session 对象在 Hibernate 中具有举足轻重的作用,负责管理所有与持久化相关的操 作,例如数据库的存取、事务的管理和对象的生命周期等。它是轻量级的,创建和销毁不 会浪费太多的资源。Session 与 SessionFactory 恰恰相反,它不是线程安全的,应避免多 个线程共享同一个 Session。 在包 com 中新建一个 HibernateUtil 类,在类中首先引入 Hibernate 类包,然后定义 3 个静态方法用于处理 Hiberante 中的 Session 对象,具体的代码如下。 import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { sessionFactory = new Configuration().configure() .buildSessionFactory(); } catch (HibernateException ex) { throw new RuntimeException("Exception building SessionFactory:" + ex.getMessage(), ex); } } public static Session currentSession() throws HibernateException { Session s = sessionFactory.openSession(); 118 第 2 篇 高 级 篇 P A R T 2 return s; } public static void closeSession(Session s) { s.close(); } } (5)运行程序,编写一个用于测试 Hibernate 类的文件 HibernateUserDemo。在该类 中,首先引入 Hibernate 中不同的类包,定义一个 User 实体类变量并进行赋值,然后通过 HibernateUtil 类生成 Session 对象,最后完成数据库的存盘操作,其代码如下。 mport java.util.List; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; public class HibernateUserDemo { static Session session=null; public static void createStu(User user) { try { session = HibernateUtil.currentSession(); //开启连接 Transaction tx = session.beginTransaction(); //开启事务 session.save(user); tx.commit(); } catch (HibernateException e) { //捕捉例外 e.printStackTrace(); }finally { HibernateUtil.closeSession(session); } } public static void main(String[] args) { User user = new User(); user.setId(new Integer(1)); user.setName("王永"); user.setAge(new Integer(26)); createStu(user); } } (6)运行这个测试类,在控制台输出结果如图 5.5 所示。 图 5.5 Hibernate 入门实例运行结果 119 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 5.3.2 Spring 对 Hibernate 的支持 Spring 整合了对 Hibernate 的设定,并且提供了 HibenateTemplate 类和 HibernateDao Support 类以及相应的子类,使用户在结合 Hibernate 使用的时候可以简化程序编写的资 源,完全可以与JDBC 相类似的使用模型一样简洁方便。同时还提供使用 Hiberante 时的编 程式的事务管理与声明式的事务管理。 在 Spring 中,Hibernate 的连接、事务管理等是以建立 SessionFactory 类开始的。 SessionFactory 在应用程序中通常只存在一个实例,因而 SessionFactory 底层的 DataSource 可以使用 Spring 的 IoC 注入,之后再注入 SessionFactory 到依赖的对象之中。 正如 5.3.1 中所述,在应用的整个生命周期中,只要保存一个SessionFactory 实例就 可 以 了。 在 Spring 中 配置 这 个 SessionFactory 对 象是 通 过实 例 化 LocalSessionFactoryBean 类来完成的。为了让这个 SessionFactory 知道连接的后台数据 库是什么,需要配置一个数据源 dataSource,配置方法如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=db_database05 sa 通过一个 LocalSessionFactoryBean 配置 Hibernate,Hibernate 本身有很多属性,通 过这些属性可以控制它的行为。其中最重要的一个就是 mappingResources 属性,通过设置 该属性中的 value 值,可以指定 Hibernate 所使用的映射文件,代码如下。 com/User.hbm.xml 120 第 2 篇 高 级 篇 P A R T 2 org.hibernate.dialect.SQLServerDialect true 配置完成之后,就可以使用 Spring 中所提供的很多支持 Hibernate 的类。例如通过 HibenateTemplate 类和 HibernateDaoSupport 的子类,完全可以实现 Hibernate 的大部分 功能,这给实际项目的编写带来了很大的方便。 下面将通过一个实例来介绍 Spring 是如何集成 Hibernate 的。 范例位置:mr\05\fl\03范例 05-03 Spring 集成 Hibernate 的应用范例 录像位置:mr\05\lx\03 在这个范例中主要演示了在 Spring 中如何使用 Hibernate 框架完成数据持久化。它继 承了 Spring 的 HibernateDaoSupport 类来创建操作数据的 UserDaoSupport 类,在类中编 写完成数据库操作的方法。范例实现过程如下: (1)建立 Spring 的配置文件 applicationContext.xml,该配置文件用于完成数据源 datasource 和 LocalSessionFactoryBean 的配置。其配置方法以 5.3.2 节中所讲述的内容为 基础。 (2)编写一个进行数据库操作的 DAO 类文件 UserDaoSupport,该类继承 Spring 框架 中的 HibernateDaoSupport 类。定义一个添加方法,其参数为JavaBean 的实体类对象 User, 然后通过 getHibernateTemplate()方法获得 Hibernate 的模板类,通过这个类执行数据添 加操作,其代码如下。 import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class UserDaoSupport extends HibernateDaoSupport { public void insert(User user){ this.getHibernateTemplate().save(user); } } (3)将该类配置到 Spring 的配置文件中,同时为它的 SessionFactory 属性注入数据 源 DataSource,具体设置如下。 (4)定义一个用于进行测试的类 SpringUserDemo。在该类中首先引入 Spring 需要的 类包文件,然后在 main 方法中定义一个实体类对象用于完成数据的添加,具体代码如下。 import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class SpringUserDemo { 121 C H A P T E R 5 第 5 章 数 据 持 久 化 服 务 public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml"); UserDaoSupport userDAO = (UserDaoSupport) context.getBean("userdao"); User user = new User(); user.setId(new Integer("2")); user.setName("caterpillar"); user.setAge(new Integer(30)); userDAO.insert(user); System.out.println("数据添加成功!!!"); } } (5)运行该测试程序,在控制台输出,结果如图 5.6 所示。 图 5.6 范例运行结果 第 6 章 事务管理 Spring 提供了一流的事务管理。在 Spring 中可以支持声明式事务和 编程式事务,本章将针对 Spring 中的事务应用进行讲解,其主要内容如 下。  Spring 事务  事务属性  事务管理器  编程式事务  声明式事务 6.1 Spring 中的事务 事务管理在应用程序中起着至关重要的作用:它是一系列任务组成 的工作单元,在这个工作单元中,所有的任务必须同时执行。它们只有 两种可能的执行结果,要么所有任务全部成功执行、要么全部执行失败。 Spring 中提供了丰富的事务管理功能,它们超越了 EJB 并且和 EJB 一样支持声明式事务和编程式事务。重要的是Spring 提供了一致的事 务管理,它有如下优点。  为不同事务的 API 提供一致的编程模式  提供更简单、更易于使用的编程式事务管理  支持 Spring 声明式事务  整合 Spring 对数据访问的抽象 6.2 事务的 ACID 特性 事务使用 ACID 特性来衡量事务的质量。这些特性包括原子性、一 致性、隔离性和持续性。下面分别对 ACID 的 4 个特性作简单介绍。 123 C H A P T E R 6 第 6 章 事 务 管 理 6.2.1 原子性(Atomicity) 事务必须是原子的。在事务结束的时候,事务中的所有任务必须全部成功完成,否则 全部任务失败,事务回滚到事务开始之前的状态。 6.2.2 一致性(Consistency) 事务必须保证和数据库的一致性,即数据库中的所有数据必须和现实保持一致。如果 事务失败数据必须返回到事务执行之前的状态,反之修改数据和现实同步。 6.2.3 隔离性(Isolation) 隔离性是事务与事务之间的屏障。每个事务必须与其他事务的执行结果隔离开,直到 该事务执行完毕。它保证了事务所访问的任何数据不会受其他事务执行结果的影响。 6.2.4 持久性(Durability) 如果事务成功执行,无论系统发生任何情况,事务的持久性都必须保证事务的执行结 果是永存的。 6.3 事务之间的缺陷 在事务处理中有违反 ACID 特性的 3 个问题:脏读取、不可重复读和幻影行。如果存在 多个并发的事务在运行,而这些事务操作了同一个数据来完成它们的任务,就会导致 3 个 问题的存在。要解决它们,就必须在事务之间定义合适的隔离级别。下面先说明事务之间 存在的 3 个问题。 为保证事务的完整性,必须解决事务之间可能存在的 3 个问题。 (1)脏读取(Dirty read) 当一个事务读取了另外一个事务尚未提交的更新,就叫做脏读取。在另一个事务回滚 的情况下,当前事务所读取的另一个事务的数据就是无效的。 (2)不可重复读取(Nonrepeatable read) 在一个事务中执行多次同样的查询操作,但每次查询的结果都不相同,就叫做不可重 复读取。通常这种情况是由于数据在两次查询之间被另一个并发的事务所修改。 (3)幻影行(Phantom rows) 这是对事务危害最小的一个问题,它类似于不可重复读取,也是一个事务的更新结果 影响到另一个事务的问题。但是它不仅会影响另一个事务的查询结果,而且还会使查询语 句返回一些不同的记录行。 这 3 个问题按其危害程度依次为:脏读危害最大、不可重复读取其次、幻影行危害程 度最小。 6.4 事务的属性 本节主要介绍把事务策略应用到方法的属性描述,其内容包括事务的传播行为、事务 的隔离级别、事务的只读和超时属性。 124 第 2 篇 高 级 篇 P A R T 2 6.4.1 事务的传播行为 传播行为是事务应用于方法的边界,它定义了事务的建立、暂停等行为属性。表 6.1 描述了 Spring 的 7 个传播行为属性。 表 6.1 Spring 事务的传播行为属性表 传 播 行 为 说 明 PROPAGATION_MANDATORY 规定了方法必须在事务中运行,否则会抛出异常 PROPAGATION_NESTED 使方法运行在嵌套的事务中,否则这个属性和 PROPAGATION_REQUIRED 属性的意义相同 PROPAGATION_NEVER 使当前方法永远不在事务中运行。否则抛出异常 PROPAGATION_NOT_SUPPORTED 定义为当前事务不支持的方法。在该方法运行期间,正在运行 的事务会被暂停 PROPAGATION_REQUIRED 规定当前方法必须运行在事务中,如果没有事务就创建一个新 事务。一个新的事务和方法一同开始,随着方法的返回或抛出 异常而终止 PROPAGATION_REQUIRES_NEW 当前方法必须创建新的事务来运行,如果现存的事务正在运行 就暂停它 PROPAGATION_SUPPORTS 规定当前方法支持当前事务处理,但如果没有事务在运行就使 用非事务方式执行 表 6.1 中的定义分别对应了 EJB 的事务(CMT)中所有的传播行为,其中 PROPA- GATION_NESTED 是 Spring 在(CMT)之外定义的传播行为。 6.4.2 事务的隔离级别 为解决事务之间的 3 个缺陷,必须在事务之间建立隔离关系来保证事务的完整性。 Spring 的事务隔离级别如表 6.2 所示。 表 6.2 Spring 的事务隔离级别表 隔 离 级 别 说 明 ISOLATION_DEFAULT 使用数据库中默认的隔离级别 ISOLATION_COMMITTED 允许读取其他并发事务已经提交的更新。(防止脏读取) ISOLATION_READ_UNCOMMITTED 允许读取其他并发事务还未提交的更新。(会导致事务之间的 3 个缺陷发生)。这是速度最快的一个隔离级别,但同时它的隔离 级别也是最低的 ISOLATION_REPEATABLE_READ 除非事务自身修改了数据,否则规定事务多次读取的数据必须 相同。(防止脏读取、不可重复读取) ISOLATION_SERIALIZABLE 这是最高的隔离级别,它可以防止脏读取、不可重复读取和幻 影行等问题,但因其侵占式的数据记录完全锁定,导致它影响 事务的性能,成为隔离级别中最慢的一个 并不是所有的资源管理器都支持表中所示的所有的隔离级别,可针对不同的资源管理 器使用表 6.2 中的隔离级别。 125 C H A P T E R 6 第 6 章 事 务 管 理 6.4.3 事务的只读属性 在对数据库的操作中,查询是使用最频繁的操作。每次执行查询操作时都要从数据 库中重新读取数据,有时多次读取的数据都是相同的,这样的数据操作不仅浪费了系统 资源,还影响了系统速度。对访问量大的程序来说,节省这部分资源可以大大提升系统 速度。 如果将事务声明为只读的,那么数据库可以根据事务的特性优化事务的读取操作。事 务的只读属性需要配合事务的 PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_ NEW 和 PROPAGATION_NESTED 传播行为共同设置。例如: PROPAGATION_REQUIRED,readonly 6.4.4 事务的超时属性 这个属性和事务的只读属性一样需要搭配事务的 PROPAGATION_REQUIRED、 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 传播行为共同设置,它设置了事务的 超时时间。事务本身可能会因为某种原因很长时间没有回应,在这期间事务可能锁定了数 据库的表格,这样会出现严重的性能问题。通过设置事务的超时时间,从开始执行事务起, 在规定的超时时间内如果事务没有完成就将它回滚。事务的超时属性以 timeout_为前缀和 一个整型数字定义,例如: PROPAGATION_REQUIRED,timeout_5,readOnly 其中的“timeout_5”表示如果查询任务超过 5 秒便抛出异常结束事务。 6.5 Spring 的事务管理器 Spring 的事务管理器有 5 个,都实现了 PlatformTransactionManager 接口。表 6.3 列举了 Spring 中不同平台使用的事务管理器。 表 6.3 Spring 事务管理器 事务管理器 说 明 路 径 DataSourceTransactionManager JDBC 事务管理器 org.springframework.jdbc.datas ource HibernateTransactionManager Hibernate 事务管理器 org.springframework.orm.hiber nate JdoTransactionManager JDO 事务管理器 org.springframework.orm.jdo JtaTransactionManager JTA 事务管理器 org.springframework.transacti on.jta PersistenceBrokerTransactionMan ager Apache 的 OJB 事务管理器 org.springframework.orm.ojb 6.5.1 定义 DataSource DataSource 定义了连接数据库的数据源,其中定义了连接数据库的基本信息:用户名、 密码、连接数据库的 URL 和数据库的驱动类等。为方便事务管理器的讲解,本节统一使用 Spring 的 DriverManagerDataSource 类定义 DataSource 连接 SQL Server 2000 数据库。 DataSource 在 Spring 配置文件中定义如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=db_database06 sa  注意 在配置 DataSource 连接数据库的 URL 或任何字符串时,由于大多数集成开发工具 (IDE)都有自动格式化代码的功能,当 URL 较长时经常被折成两行或多行。这种情况下, Spring 会抛出 java.sql.SQLException 异常,并提示:“No suitable driver(没有合适 的驱动)”,所以配置 DataSource 连接数据库的 URL 时,一定要保持 URL 信息配置在单行 代码中。 6.5.2 JDBC 事务管理器 DataSourceTransactionManager 类是针对 JDBC 操作的 PlatformTransactionManager 接 口的一个实现类,在以 JDBC 持久化数据时将使用该事务管理器。下面是 DataSourceTransaction Manager 管理器在 Spring 配置文件中的定义。 ⋯⋯//此处代码省略 其中的 dataSource 引用 6.5.1 节中的定义。 6.5.3 Hibernate 事务管理器 Hibernate 持久化框架是目前最流行的 ORM(对象/关系映射)工具。Spring 完美地集 成了 Hibernate 框架,并为其提供了特性的支持和事务管理的能力。HibernateTransaction 127 C H A P T E R 6 第 6 章 事 务 管 理 Manager 类是 Spring 提供的 Hibernate 的事务管理器,它需要注入“sessionFactory”属 性,下面的代码是 Hibernate 事务管理器在 Spring 中的关键配置代码。 classpath:com/hibernate/model org.hibernate.dialect.SQLServerDialect.class true ⋯⋯//此处代码省略 6.5.4 JDO 事务管理器 JDO 的全称是(Java Data Object),它是 SUN 定制的持久化规范。这个规范有一个 PersistenceManagerFactoryBean 接口,它类似于 Hibernate 的 SessionFactory,用于存 储持久化配置信息。JdoTransactionManager 是 JDO 的事务管理器,它在 Spring 中的关键 配置代码如下。 128 第 2 篇 高 级 篇 P A R T 2 6.5.5 OJB 事务管理器 PersistenceBrokerTransactionManager 是 OJB 在 Spring 中的事务管理器,其关键配 置信息如下。 ⋯⋯//此处代码省略 6.5.6 JTA 事务管理器 JtaTransactionManager 类是 Spring 的 JTA 管理器,它可以跨越多个事务资源、协调 程序和多个数据资源之间的事务。它在 Spring 中的关键配置代码如下。 java:/JtaTransactionManager 6.6 编程式事务 在 Spring 中主要有两种编程式事务的实现方法。  使用实现 PlatformTransactionManager 接口的事务管理器  使用 TransactionTemplate 实现 推荐使用第二种实现方法,因为它符合Spring 的模板模式。本节将分别介绍两种实现 方法。 6.6.1 使用 PlatformTransactionManager 接口 在 6.5 节中介绍的所有事务管理器都是 PlatformTransactionManager 接口的实现类, 它们针对不同的平台完成了 PlatformTransactionManager 接口的不同实现并以 IoC 完成属 129 C H A P T E R 6 第 6 章 事 务 管 理 性的注入。程序中主要面向该接口编程,具体实现交给实现接口的事务管理器去完成。这 样,业务代码便和具体的事务 API 完全分离开。该接口在 Spring 中的定义如下。 package org.springframework.transaction; public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; } 接口中的 commit()方法和 rollback()方法分别用于事务的提交和回滚。 getTransaction()方法用于获取代表当前线程中一个事务的 TransactionStatus 对象,它 可能代表一个新的事务,也可能代表一个存在的事务。其方法参数中的 TransactionDefinition 是一个接口类型,作用是定义事务的传播行为、隔离级别、事务 只读和事务的超时属性。下面是 Transaction Status 接口的定义。 package org.springframework.transaction; public interface TransactionStatus extends SavepointManager { // 判断是不是新事务 boolean isNewTransaction(); boolean hasSavepoint(); // 设置事务回滚 void setRollbackOnly(); // 判断事务是否回滚 boolean isRollbackOnly(); // 判断事务是否结束,即事务已成功提交或失败回滚 boolean isCompleted(); } 它代表事务本身,并提供了事务的简单操作和状态查询的方法。 下面通过实例介绍如何面向 PlatformTransactionManager 接口编写编程式事务管理。 实例在 main()方法中创建了 ApplicationContext 上下文容器,从容器中获得了实例对象 的 JavaBean 并且调用其 transactionOperation()方法来执行事务操作,可以将 main()方 法放在一个单独的类中定义,为节省篇幅,本实例把它定义在了一个类中。 实例位置:.....mr..\.06..\.sl..\.01.. 具体的事务操作定义在 transactionOperation()方法中。在该方法中,首先定义了 Platform TransactionManager 接 口 中 getTransaction() 方 法 需 要 的 参 数 。 DefaultTransaction Definition 是 TransactionDefinition 接口的一个默认实现,实例 中 通 过 它 的 setPropagation Behavior() 方 法 定 义 了 事 务 的 传 播 行 为 属 性 为 “DefaultTransactionDefinition.PROPAGATION_ REQUIRED”,这是 TransactionDefinition 接口定义的属性值常量。DefaultTransaction Definition 类还定义了用于设置事务的其 他属性的方法,表 6.4 列举了这些方法中最常用的几个属性设置的方法。与之对应的还有 同名的 get()方法,该方法用于获取事务的属性。定义好事务的属性之后,将它传递给 getTransaction()方法获取 TransactionStatus 接口的实例,在事务提交时需要用到它。 130 第 2 篇 高 级 篇 P A R T 2 最后需要做的就是在 try-catch 块中完成数据库的操作。在操作正常结束时调用事务管理 器的 commit()方法提交事务;在抛出异常时调用事务管理器的 rollback()方法使事务回 滚。下面是实例中关键的程序代码。 package com.lzw; ⋯⋯省略import public class PlatformTransactionManagerExample{ DataSource dataSource; PlatformTransactionManager transactionManager; ⋯⋯省略get()/set()方法 public static void main(String[] args) { // 创建Spring容器 ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器中获得本实例,并且在配置文件中注入了依赖的DataSource和事务管理器的属性 PlatformTransactionManagerExample test = (PlatformTransactionManagerExample) context .getBean("transactionExample"); test.transactionOperation(); } public void transactionOperation(){ DefaultTransactionDefinition dtd=new DefaultTransactionDefinition(); dtd.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus ts=transactionManager.getTransaction(dtd); Connection conn=DataSourceUtils.getConnection(dataSource); try { Statement stmt=conn.createStatement(); stmt.execute("insert into tb_transaction values('飞龙','吉林省长春市')"); stmt.execute("insert into tb_transaction values('欣欣','吉林省长春市')"); transactionManager.commit(ts); System.out.println("事务成功完成"); } catch (SQLException e) { transactionManager.rollback(ts); System.out.println("事务执行失败"); System.out.println("原因:"+e.getMessage()); } } } 表 6.4 DefaultTransactionDefinition 类设置事务属性的常用方法 方 法 名 说 明 setIsolationLevel(int isolationLevel) 设置事务的隔离级别。IsolationLevel 参数由类的静态属性定义 setName(String name) 设置事务的名字,默认值是 NULL,没有名字 setPropagationBehavior(int behavior) 设置事务的传播行为。Behavior 参数由类的静态属性定义 setReadOnly(boolean readOnly) 设置事务的只读属性 这是最关键的事务 操作的程序代码 程序的 main()方法可 以定义在另一个类中 131 C H A P T E R 6 第 6 章 事 务 管 理 setTimeout(int timeout) 设置事务的超时属性 实例中的 DataSource 和事务管理器是以 IoC 注入到实例中的,在配置文件中需要定义 DataSource 和事务管理器。applicationContext.xml 配置文件代码如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=db_database06 sa 因为是面向 PlatformTransactionManager 接口编程,所以事务管理器可以使用实现该 接口的任何类的实例对象。运行本实例,将在数据库中插入两条记录。可以错误地修改插 入数据的两条 SQL 语句(例如:将表名修改为 tb_transaction1),查看事务是否回滚。程 序运行成功和失败的结果分别如图 6.1 和图 6.2 所示。 定义 DataSource 连 接 SQLServer 2000 数据库 定义 JDBC 的事物管理 器,可以定义为 6.8 节讲 解的任何事务器 132 第 2 篇 高 级 篇 P A R T 2 图 6.1 事务成功结果图 图 6.2 事务失败结果图 133 C H A P T E R 6 第 6 章 事 务 管 理 6.6.2 使用 TransactionTemplate 模板 TransactionTemplate 模板和 Spring 的其他模板一样,封装了资源的打开和关闭等常 用的重复代码,在编写程序时只须完成需要的业务代码即可。Spring 推荐使用 Transaction Template 模板,它继承并扩展了 DefaultTransactionDefinition 类,添加了 execute 方 法和事务管理器属性,其中事务管理器可以是任意实现 PlatformTransactionManager 接口 的类。execute 方法需要 TransactionCallback 接口的实现类作参数,其接口的定义中只 包含一个 doInTransaction()方法用于执行事务操作。本实例中以匿名类的方式定义 Transaction Callback 接口的实现,并且在 doInTransaction()方法中定义事务处理代码。 TransactionTemplate Example 程序的关键代码如下。 package com.lzw; ⋯⋯//省略import public class TransactionTemplateExample { DataSource dataSource; PlatformTransactionManager transactionManager; TransactionTemplate transactionTemplate; public static void main(String[] args) { // 创建Spring容器 ApplicationContext context = new ClassPathXmlApplicationContext( "transactionTemplateContext.xml"); // 从容器中获得本实例,并且在配置文件中注入了依赖的DataSource和事务管理器的属性 TransactionTemplateExample test = (TransactionTemplateExample) context .getBean("TransactionTemplateExample"); test.transactionOperation(); } public void transactionOperation(){ transactionTemplate.execute(new TransactionCallback(){ public Object doInTransaction(TransactionStatus status){ Connection conn = DataSourceUtils.getConnection(dataSource); try { Statement stmt = conn.createStatement(); stmt.execute("insert into tb_transaction values('飞龙','吉林省长春 市')"); stmt.execute("insert into tb_transaction values('欣欣','吉林省长春 市')"); System.out.println("事务成功完成"); } catch (SQLException e) { transactionManager.rollback(status); System.out.println("事务执行失败"); System.out.println("原因:"+e.getMessage()); } return null; } }); } 以匿名类的方式定义 Transaction Callback 接 口的实现来处理事务 134 第 2 篇 高 级 篇 P A R T 2 ⋯⋯//省略get()/set()方法 } 因为数据库连接和 6.6.1 节实例相同,所以在其applicationContext.xml 配置文件的 基础上,稍作修改定义本实例的配置文件。本实例中 TransactionTemplate 模板定义在 Spring 的配置文件中,然后注入到实例中。也可以在 TransactionTemplateExample 程序 的实例代码中创建模板的实例。transactionTemplateContext.xml 配置文件的关键代码如 下。 ⋯⋯//DataSource和事务管理器的定义不变 PROPAGATION_REQUIRED 运行本实例,将在数据库中插入两条记录。可以错误地修改插入数据的两条 SQL 语句, 查看事务是否回滚。程序运行成功和失败的结果分别如图 6.1 和图 6.2 所示。 6.7 声明式事务 在介绍 Spring 声明式事务管理之前,必须先了解 Spring 的事务属性。因为 Spring 中的事务是基于 AOP 实现的,而 Spring 的 AOP 是以方法为单位的,所以 Spring 的事务属 性就是对事务应用到方法上的策略描述。这些属性分为:传播行为、隔离级别、只读和超 时属性。如果读者对这些属性还不了解,请仔细阅读本章的 6.2~6.4 节。 Spring 的声明式事务不涉及组建依赖关系,它通过AOP 实现事务管理。Spring 本身就 是一个容器,相对 EJB 容器而言,Spring 显得更为轻便小巧。在使用 Spring 的声明式事 务时不须编写任何代码,便可通过实现基于容器的事务管理。Spring 提供了一些可供选择 135 C H A P T E R 6 第 6 章 事 务 管 理 的辅助类,这些辅助类简化了传统的数据库操作流程,在一定程度上节省了工作量,提高 了编码效率,所以推荐使用声明式事务。 6.7.1 优化 DataSource 在 6.5.1 节中已经简单地介绍了 DriverManagerDataSource 数据源,它在每次获取数 据库连接时都创建一个新的连接对象,完全没有缓冲的能力。DriverManagerDataSource 数据源关键配置代码可参见 6.5.1 节。 Spring 中 提 供 了 SingleConnectionDataSource 数 据 源 , 它 是 DriverManagerDataSource 的子类。在 SingleConnectionDataSource 数据源为多个操作保 持连接的开启(必须使用 DataSourceUitls 获取数据源的连接),直到数据源被销毁。其关 键配置代码如下。 ⋯⋯此处代码省略 Spring 没有提供连接池的实现,因为一些开源项目已经实现了带有连接池功能的数据 源 , 例 如 Apache Commons DBCP 的 BasicDataSource 数 据 源 。 与 SingleConnectionDataSource 和 DriverManagerDataSource 数据源相比,Apache Commons DBCP 的 BasicDataSource 数据源不仅提供了缓冲的连接池功能,而且它是一个完全轻量级 的数据源。它在 Spring 中配置方法如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=db_database06 sa 6.7.2 使用事务代理工厂 事务代理工厂 TransactionProxyFactoryBean 包含了事务拦截器、目标代理和事务的 属性设置,它配置方便、使用灵活,在声明式事务中广泛使用。 使用 TransactionProxyFactoryBean 需要注入它所依赖的事务管理器,设置代理的目 标对象、代理对象的生成方式和事务属性。代理对象是在目标对象上生成的包含事物和 AOP 切面的新的对象,这个新的对象用来替代目标对象以支持事物或 AOP 提供的切面功能。 其中对象的代理的生成方式可根据接口生成代理,也可以由开源工具 CGLIB 生成代理, 下面分别说明两种代理的生成方式。 136 第 2 篇 高 级 篇 P A R T 2 1.以接口生成代理 使用这种方式需要为目标对象定义并实现一个抽象的接口。TransactionProxyFactory Bean 可以自动的找到该接口,并根据该接口生成目标对象的代理对象;也可以设置 TransactionProxyFactoryBean 的 proxyInterfaces 代理接口属性,指定代理对象的接口。 interface ⋯⋯// 如有其他接口,可以继续添加 其中的interface指定了接口 interface。这只是个说明参数,读者 在实际编程中就替换为程序接口,本节以 interface 说明此处是接口定义。 2.由开源工具 CGLIB 生成代理 如果认为编写一个接口会增加额外的编程负担,可以让 TransactionProxyFactoryBean 使用开源工具 CGLIB 生成代理,但是必须设置 proxyTargetClass 属性为 true。关键属性配置如下。 最后再介绍一下 TransactionProxyFactoryBean 中如何设置事务的属性。Transaction ProxyFactoryBean 的 transactionAttributes 属性用于指定事务的属性,它是一个 Properties 类型的属性集合,以方法名和事务属性组成键值对,给不同方法指定不同的事 务属性。例如: PROPAGATION_REQUIRED,timeout_5,readOnly 方法名作键声明时,可以使用“*”通配符,上述代码定义对所有以“query”作前缀 的方法,并在定义的事务中完成操作。事务的不同属性之间以“,”分隔。 下面以一个简单的实例介绍如何使用 TransactionProxyFactoryBean 完成 Spring 的声 明式事务管理。 实例位置:.....mr..\.06..\.sl..\.02..首先在配置文件中定义数据源 DataSource 和事务管理器。这个事务管理器被注入到 TransactionProxyFactoryBean 中,设置代理对象和事务属性。这里的目标对象的定义是 以内部类的方式定义的。实例中的配置代码如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433;databasename=db_database06 137 C H A P T E R 6 第 6 章 事 务 管 理 sa PROPAGATION_REQUIRED,timeout_5,readOnly PROPAGATION_REQUIRED PROPAGATION_REQUIRED 然后编写操作数据库的 Dao 类。该类中的 insertAndQuery()方法是程序的关键,在方 法中执行了两次数据的插入操作,分别将数据插入到不同的数据表中。这个方法在配置 TransactionProxyFactoryBean 时被定义为事物性方法,并指定了事务属性,所以方法中 的所有数据库操作都被当作一个事务处理。Dao 类的程序代码如下。 package com.lzw; import java.util.List; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class Dao extends JdbcDaoSupport { public List insertAndQuery(String filed1, String filed2){ getJdbcTemplate().execute( "insert into tb_transaction values('" + filed1 + "','" + filed2 + "')"); 以内部类的方式定 义代理的目标对象 设置事务性的方法 的事务属性 定义操作数据的事 务性的方法 138 第 2 篇 高 级 篇 P A R T 2 getJdbcTemplate().execute( "insert into tb_transactionBak values('" + filed1 + "','" + filed2 + "')"); return query("select * from tb_transaction"); } public List query(String sql) { return getJdbcTemplate().queryForList(sql); } public int delete(String condition){ return getJdbcTemplate().update( "delete from tb_transaction where " + condition); } } 最后编写实例程序的主类 DeclareTransactionDemo。该类用于创建 Spring 容器,从 容器中获得 Dao 的代理对象,这个代理对象的方法被定义为事务的方法。因为Spring 的声 明式事务是基于 AOP 实现的,所以程序完全不知道 Dao 处于事务中,可以像操作 POJO(普 通 JAVA 对象)那样使用 Dao 中操作数据的方法。主类的关键代码如下。 package com.lzw; ⋯⋯//省略import public class DeclareTransactionDemo { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml"); Dao dao=(Dao) context.getBean("transactionProxy"); List list=dao.insertAndQuery("理想", "长春"); Iterator iterator =list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } } 运行程序,在数据库中的tb_transaction 表和 tb_transactionBak 表中会插入相同的 记录。如果其中任何一个表的数据插入失败,将导致事务回滚。程序运行结果如图 6.3 所 示。 图 6.3 实例运行结果 第 7 章 远程服务 远程服务让本地程序在使用服务器端的服务时,就如同使用本地 服务一样,客户端无须知道网络连接的细节。 在 Java/J2EE 应用领域中,提供了RMI、EJB 和 Web 服务等手段将 服务提供给远程客户端。无论使用这几种服务的哪一种开发者都需要 完成许多与业务逻辑无关的代码,例如借助 RMI Naming 注册服务对象、 JNDI 查找 EJB Home 对象、以标准 JAX-RPC 方式查找 Java 对象都需要 指定许多参数等,这些代码对于业务问题而言毫无益处。Spring 为解 决远程支持,借助于各种技术提供了有效的解决方案,并且能够消除 开发者编写与业务逻辑无关代码的必要性,本章将介绍在Spring 中如 何简单地实现远程服务。主要内容如下。  Spring 中提供的远程服务  RMI 服务的实现  Hessian 和 Burlap 服务的实现  HTTP Invoker 服务的实现  对 Web Service 的支持 7.1 Spring 中提供的远程服务 在 Spring 中提供的远程服务主要有 RMI、Hessian、Burlap、HTTP Invoker、JAX-RPC 以及企业 EJB 等,关于它们的具体功能介绍如表 7.1 所示。 表 7.1 Boolean 的设置值 远程服务名称 服务功能描述及适用地点 RMI 如果客户端和服务器端不受防火墙或者其他网络约束,则可 以使用 RMI 服务实现 Java 程序调用远程计算机上的 Java 方 法。Spring 提供了传统的 RMI 和 RMI 调用器 140 第 2 篇 高 级 篇 P A R T 2 续表 远程服务名称 服务功能描述及适用地点 Hessian、Burlap 借助于 HTTP 协议创建二进制协议和基于 XML 的协议实现远程服务。使用Spring 可以很简单的创建 Hessian 服务,同时也提供了代理支持 HTTP Invoker 这是 Spring 自身的过程框架,能够在 HTTP 协议上实现 POJO 远程服务的访问 JAX-RPC 基于 XML 的远程调用 API,可以发布和访问 SOAP Web Services 企业 EJB 访问 EJB 应用或者开发 EJB 应用 下面详细介绍这几个远程服务。  RMI RMI(Remote Method Invocation)是从 JDK1.1 开始就出现的 API,它为客户端提供 使用远程对象方法的能力。在 RMI 出现之前,要完成远程调用只能选择使用 CORBA 或编写 自己的 Socket 程序。 Spring 中使用 RmiProxyFactoryBean 和 RmiServiceExporter 支持传统的 RMI 与 RMI 调用器(可以使用任何 Java 接口)的透明远程调用。  Hessian、Burlap Caucho 公司发布了两个轻量级远程服务:二进制协议 Hessian 和基于 XML 的 Burlap 服务。两者都使用 HTTP 协议传输机制,并以简洁的规范定义。虽然两者都是面向 Java 的, 但是也允许用其他的语言来实现。 Hessian 和 Burlap 除 Java 运行库以外不需要任何其他类库,这使它们可以在资源受 限的环境下正常使用。  HTTP Invoker Spring 提供了 HTTP Invoker 远程处理服务,取代了 Hessian 和 Burlap 中建立定制序 列化的习惯。HTTP Invoker 使用和 RMI 相同 Java 的序列化机制,应用程序可以依赖于完 全的序列化能力,只要所有被传输的对象完全遵循 Java 序列化的原则即可。 因此,Spring 的 HTTP Invoker 是一个新的远程调用模型,作为 Spring 的一部分, 来执行基于 HTTP的远程调用并且使用 Java的序列化机制,它的使用方法同使用 Hessian 类似。  JAX-RPC JAX-RPC 为 Java 和 J2EE 平台提供了基于 XML 的协议,实现传统的客户/服务器的 RPC 机制。借助 JAX-RPC 开发者能够开发出基于 SOAP/HTTP 的 WEB Services。 WEB Services 是通过 WSDL(Web Service Definition Language,Web 服务定义语言) 和 SOAP(Simple Object Access Protocol 简单对象访问协议)))实现的,在 J2EE1.4 平 台中 Web 服务已经成了它的一个重要组成部分,无论是客户端还是服务器都要遵循 JAX-RPC 约定,才能够进行相互通信。  企业 EJB 企业 EJB(Java Beans)的组件结构是作为可重复使用的服务器端组件而设计的,它 使企业能够建立更安全、更可靠、可升级、可运行于多重平台和以商务为重点的应用程序。 EJB 结构是 Java TM 平台上的服务器端组件模型。设计 EJB 结构的目的,是使企业开发人 员能够将注意力集中于编写业务代码,而不是编写那些处理事务行为、安全、连接共享等 代码,因为 EJB 已经将这些任务委托给服务器厂商完成。 141 C H A P T E R 7 第 7 章 远 程 服 务 7.2 使用 Spring 的 RMI Spring 提供了一个 RMI 的代理工厂,通过它可以像使用本地 JavaBean 一样为 Spring 的应用程序提供 RMI 服务,这避免了程序开发者去编写那些沉闷的 RMI 开发和访问代码, 简化了 RMI 模式。Spring 还提供了管理 JavaBean 到 RMI 服务转换的远程输出器 (org.springframework.remoting.rmi.RmiServiceExporter)。 下面通过 Spring 框架中的远程输出器来简化 RMI 的开发过程,实现一个简单的 RMI 实例。 实例位置:.....mr..\.07..\.sl..\.01.. 1.创建 RMI 服务端 创建一个服务对象的接口 IhelloService,并在这个接口中声明两个方法,服务对象 的接口不用继承 java.rmi.Remote 接口。关键代码如下。 public interface IHelloService { public String doService(String msg); public int countService(int count); } 定义一个实现服务接口的服务类并且实现服务中的接口来创建 RMI 服务,该服务类无 须继承 java.rmi.UnicastRemoteObject 类。关键代码如下。 public class HelloImpl implements IHelloService { public int countService(int count) { return ++count; } public String doService(String msg) { return msg + " is runned !!!"; } } 创建 Spring 的配置文件 rmi-server.xml。通过它开发者无须直接使用 rmic 命令来生 成客户端的 stub 类和服务端的 skeleton 类,也不用将远程服务绑定到 RMI 的注册表中, 因为 Spring 的 RmiServiceExporter 类已经简化了这些操作。只需要配置它的服务对象、 名称与代理的接口即可。关键代码如下。 //提供的服务 helloservice//定义服务名称 142 第 2 篇 高 级 篇 P A R T 2 6335//定义服务端口 com.IHelloService//定义服务接口 编写实例的主类 RMIServer,它用于在服务启动时输出提示信息,关键代码如下。 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import org.springframework.context.support.FileSystemXmlApplicationContext; public class RMIServer { public static void main(String[] args) throws IOException { new FileSystemXmlApplicationContext("rmi-server.xml"); System.out.println("RMI Server服务正在启动中⋯.."); System.out.println("请输入 exit 关闭 Server: "); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true) { if (reader.readLine().equals("exit")){ System.exit(0); } } } } 服务启动以后会在控制台输出运行结果,如图 7.1 所示。可以在输出结果中看到启动 RMI 服务端口和主类编写的输出信息。 图 7.1 服务启动输出结果 2.创建 RMI 客户端 客户端访问 RMI 服务是通过 Spring 中工厂类 RmiProxyFactoryBean 来实现的。它实际 上是 RMI 远程服务的代理类,并且能够捕捉到 RemoteException 异常,通过在配置文件中 指定“serviceUrl”属性能够指定 RMI 的 URL,“serviceInterrface”属性用于指定业务 143 C H A P T E R 7 第 7 章 远 程 服 务 接口。因此借助于 RmiProxyFactoryBean 类可以建立对 RMI 服务的访问。 建立 Spring 配置文件 rmi-client.xmlRmiProxyFactoryBean 类的实例,并注入其依赖 属性。配置关键代码如下。 rmi://localhost:6335/helloservice com.IHelloService 建立客户端的主类 RMIClient.java 文件。它创建了 ApplicationContext 容器并从容 器中获取 IhelloService 的实例对象“someServiceProxy”,然后赋给“service”变量。 程序最终调用“someServiceProxy”变量(即 RMI 代理对象)中的 RMI 远程方法,并将返 回结果输出到控制台上,代码如下。 import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class RMIClient { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "rmi-client.xml"); IHelloService service = (IHelloService) context.getBean("someServiceProxy"); String result1 = service.doService("Some request"); System.out.println(result1); int result2 = service.countService(1); System.out.println(result2); } } 程序在控制台上输出远程方法的返回结果,如图 7.2 所示。 图 7.2 RMI 客户端的运行结果 144 第 2 篇 高 级 篇 P A R T 2 7.3 Hessian 和 Burlap 服务的实现 RMI 是 Java 平台中实施分布式计算的重要技术。它提供了一流的编程模型,但是技术 本身都有特定的适应场合,RMI 也不例外。它对网络中的防火墙约束无能为力,同时 RMI 的程序必须采用 Java 语言才能使用 RMI 技术。针对 RMI 技术的这些缺点,Resin 应用服务 器厂商 Caucho Technology 公司开发了 Hessian 和 Burlap,克服了 RMI 的局限性。 Hessian 和 Burlap 是基于 HTTP 的轻量级远程服务,其中 Hessian 借助于二进制消息 实现客户与服务器的交互,它能够在不同编程语言之间进行移植。而 Burlap 是借助于 XML 来实现客户与服务器端的交互,并用 XML 文件格式传送对象数据。XML 文件具有较高的可 读性,只要应用程序可以解析 XML 文件就可以解读所接收的消息。 在 Spring 中使用这两个远程服务的方法是类似的,由于它们是基于 HTTP 协议传送的, 因此在使用时必须搭配 Spring Web 框架,也就是使用 DespatcherServlet。 现在以 7.2 节实例为基础,通过 Hessian 来编写一个新的实例。 实例位置:.....mr..\.07..\.sl..\.02.. 在原实例的基础上修改相应的配置文件就可以实现 Hessian 服务。为了使用 Hessian 服务,本实例配置了一个 service-config.xml 文件,用于给适当的JavaBean 分发 Hessian 服务的 URL,关键配置代码如下。 //设置服务 bean的类型 com.IHelloService 在 web.xml 配置文件中定义 url 的请求后缀为*.service,同时引入上面的 service- config.xml 文件,其关键配置代码如下。 dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/service-config.xml 1 145 C H A P T E R 7 第 7 章 远 程 服 务 dispatcherServlet *.service 完成这两个配置之后,将 Web 工程目录发布到 Tomcat 服务器中,当服务器启动以后, Hessian 服务将随之启动运行。为了能正确启动 Hessian 服务,需要将 hessian-3.0.20.jar 的文件包放入 WEB-INF 文件夹的 lib 子文件夹中。 现在改写客户端的配置文件,将 Rmi-client.xml 的配置文件中“someServiceProxy” 定义的 class 类型设置为 HessianProxyFactoryBean 代理类,然后设置它的“serviceUrl” 和“serviceInterface”属性,这里需要注意的是,属性“serviceUrl”必须是标准的 HTTP 请求地址,本例中的“HessianService”是 Web 工程的名称。其关键代码如下。 http://localhost:8080/HessianService/helloservice.service com.IHelloService 运行 RMIClient 测试程序,会得到与图7.2 相同的结果。关于使用Burlap 服务的例子 只须将 HessianProxyFactoryBean 更改成 BurlapProxyFactoryBean 代理类,同时放入对应 的 jar 文件包即可,这里不再赘述。 7.4 HTTP Invoker 服务的实现 Spring 所提供的 HTTP Invoker 不仅基于 HTTP 协议实现远程服务,而且还能够使用 Java 所提供的序列化机制。它结合了RMI 和 Hessian 的优点,弥补了它们之间的空白。由于HTTP Invoker 服务也是基于 HTTP 协议来运行的,因此使用的方式和 Hessian 服务类似。 修改上一节的实例,来看一下 HTTP Invoker 远程服务。 实例位置:.....mr..\.07..\.sl..\.03.. 为了使用 HTTP invoker 服务,需要配置一个 JavaBean 并且使用 HttpInvokerService Exporter 来代理它,关键代码如下。 com.IHelloService 146 第 2 篇 高 级 篇 P A R T 2 这个配置文件与上一节几乎没有多大区别,客户端的rmi-client.xml 配置文件也同样 进行修改,关键配置代码如下。 http://localhost:8080/HTTPInvokerService/helloservice.service com.IHelloService 修改完成之后,在控制台中输出,其运行结果与图 7.2 相同。 7.5 Spring 实现 Web Service 为了能够访问并且实现基于 WSDL 和 SOAP 的 Web 服务,Spring 集成了标准的 JAX-RPC API。Spring 提供的两个支持的类分别是:用来与 Web 服务进行通信的 Spring 代理工厂 JaxRpcPortProxyFactoryBean 类和用户 Web 服务端的基类 ServletEndpointSupport。下面 通过实例来讲解如何使用这两个类实现 Spring 中的 Web Service。 实例位置:.....mr..\.07..\.sl..\.04.. 编写 IhelloWorld 接口服务类,在该接口中定义一个 getMessage()方法用于获取消息, 关键代码如下。 public interface IHelloWorld { public String getMessage(String name); } 定义实现 IHelloWorld 接口的服务类 HelloWorldImpl。该类实现接口中的 getMessage() 方法,定义属性 helloStr,该属性在 Spring 的配置文件中注入属性值。关键代码如下。 private String helloStr; public String getHelloStr(){ return helloStr; } public void setHelloStr(String helloStr){ this.helloStr = helloStr; } public String getMessage(String name) { return helloStr+":"+name; } 定义 JaxRpcHelloWorld 类。它集成了 Spring 中的 ServletEndpointSupport 代理类, 147 C H A P T E R 7 第 7 章 远 程 服 务 实现 IhelloWorld 接口,其关键代码如下。 import javax.xml.rpc.ServiceException; import org.springframework.remoting.jaxrpc.ServletEndpointSupport; public class JaxRpcHelloWorld extends ServletEndpointSupport implements IHelloWorld { private IHelloWorld helloWorld; protected void onInit() throws ServiceException { helloWorld = (IHelloWorld) getApplicationContext().getBean("helloWorld"); } } 在 web.xml 配置文件中配置访问的 service 服务,关键代码如下。 contextConfigLocation /WEB-INF/applicationContext.xml context org.springframework.web.context.ContextLoaderServlet 1 axis org.apache.axis.transport.http.AxisServlet 2 axis /services/* 定义 Spring 的配置文件 applicationContext.xml。在这个配置文件中配置 HelloWorldImpl 实例对象“helloWorld”,其关键代码如下。 Say Hello to : 定义一个服务 server-config.wsdd 文件,通过它将Web 服务提供给客户端,其关键代 148 第 2 篇 高 级 篇 P A R T 2 码如下。 至此,Web Service 服务的创建已经完成。将工程发布到Tomcat 服务器中,启动Tomcat 服务器,在浏览器输入正确的 URL 地址 http://192.168.1.111:8080/SpringWebservice/services,运行结果如图 7.3 所示。 图 7.3 Web Service 服务页面 在图 7.3 所示的 Web Service 服务页面中单击“wsdl”链接查看 Web Service 中的服 务,其效果如图 7.4 所示。 图 7.4 Web Service 中的服务信息 定义一个 WebService 客户端的访问类 testWebServiceClient。在类中引用相应的类 文件,这里使用 apache 的 axis 开源包,关键代码如下。 import java.net.URL; import javax.xml.namespace.QName; 149 C H A P T E R 7 第 7 章 远 程 服 务 import org.apache.axis.client.Call; import org.apache.axis.client.Service; import org.apache.axis.client.ServiceFactory; 在这个类中的 main 方法中编写访问 Web Service 的代码,关键代码如下。 try { String wsdlUrl = "http://127.0.0.1:8080/SpringWebservice/services/HelloWorld?wsdl"; String nameSpaceUri = "http://127.0.0.1:8080/SpringWebservice/services/HelloWorld"; // 创建调用对象 Service service = new Service(); Call call = null; call = (Call) service.createCall(); // 调用sayHello System.out.println(">>>getMessage"); call.setOperationName(new QName(nameSpaceUri, "getMessage")); call.setTargetEndpointAddress(new java.net.URL(wsdlUrl)); String ret = (String) call.invoke(new Object[] { "LHT" }); System.out.println("return value is " + ret); } catch (Exception e) { e.printStackTrace(); } 编译并且运行客户端程序,在控制器输出如图 7.5 所示的结果。 图 7.5 实例客户端运行结果 第 8 章 企业级服务 目前很多的 J2EE 应用都需要在重量级的平台上开发,这对开发 者的开发效率设置了很多的障碍,而 Spring 的轻量级的特性很好地解 决了这类问题。本章重点内容如下。  JNDI 服务  JMS 服务  JavaMail 服务  Timer 与 Quartz 任务调度 8.1 Java 命名服务 8.1.1 JN D I 简介 JNDI(Java Naming and Directory Interface)是一组在 Java 应用中 访问命名和目录服务的 API,它为 Java 应用程序提供了一个访问命名 和目录服务的标准接口。在 JNDI 中,通过预先绑定的名字能够找到 目标服务或者资源,这是一个在 J2EE 上下文(Context)中定位(lookup) 服务的传统方式。命名服务将名称和对象联系起来,可以用名称访问 对象。目录服务是一种命名服务,在这种服务里,对象不但有名称还 有属性。使用命名或目录服务可以集中存储共有信息,使得应用更协 调、更容易管理,在网络应用中是很重要的。 在现实生活中,经常会不知不觉地接触到命名服务。例如在浏览 Web 网页 http://www.mingrisoft.com 时,DNS(Domain Name System, 域名系统)会将这个 URL 转换成通信标识即 IP 地址,这就是一个命 名服务的过程。 从 JNDI 获取资源的传统方法是通过 InitialContext.lookup()方法来 151 C H A P T E R 8 第 8 章 企 业 级 服 务 查找资源,关键代码如下。 private void lookupMessage(){ try{ Context context = new InitialContext(); message = (String)context.lookup(JndiServlet.JNDI_NAME); }catch(NamingException ex){ message = ex.getMessage(); } } 这种传统的从 JNDI 获取资源的方式没有用到 IoC 依赖注入原则,使程序与 JNDI 获取 资源的代码耦合在一起。这意味着,想从 JNDI 获取资源就必须在程序中硬编码,如何才 能既使用 JNDI 的优势,又能得到依赖注入的好处呢? 8.1.2 从 JN D I 获取 D ataSource Spring 框架专门提供了 org.springframework.jndi 包,以简化对 JNDI 的使用,使开发者 能够将 JNDI 查找操作定义在配置文件中 JndiObjectFactoryBean 类能够把从 JNDI 中获取的 资源以依赖注入方式注入到需要它的 JavaBean 中,它同时使用了 JNDI 和 IoC 两种技术的 优势。 下面通过一个简单的实例演示 JndiObjectFactoryBean 如何获取 JNDI 资源并完成 Java Bean 的依赖注入。 实例从 JNDI 中获取了 DataSource 的实例。它是在 Tomcat 中定义的数据源,使程序可 以利用服务器的数据连接池获取数据库连接。在编写程序之前,必须在 Tomcat 服务器中定 义好实例需要的资源。 1.配置 Tomcat 数据连接池 数据库连接池是当今 Java 程序控制开发中常用的技术,主要用于解决高负载数据库访 问造成的性能问题,提高数据库的使用效率。 现在以 Tomcat5.5 为例,分别介绍在 Tomcat 服务器中使用服务器管理工具和编写连接 池代码两种方法来建立数据连接池。  使用服务器管理工具建立数据连接池 因为 Tomcat 5.5 在发布时没有把服务器管理工具一同发布,所以要想在 Tomcat 5.5 中 使用服务器管理工具的页面管理和配置 Tomcat 数据连接池,必须下载 Tomcat 的管理工具 包,例如“apache-tomcat-5.5.16-admin.zip”。 将 Tomcat 的管理工具包解压并覆盖 Tomcat 的安装目录,重新启动服务器,Tomcat 就 拥有了服务器管理工具。可以在页面中管理和配置 Tomcat 服务器的各种设置,例如数据连 接池、服务器端口号、用户管理、会话管理、Web 应用的装载方式和各种设置。下面介绍 服务器管理工具的具体使用方法。 (1)打开 IE 浏览器,在地址栏中输入 Tomcat 的访问路径,例如“http://localhost:8080/”, 在 IE 浏览器中将出现如图 8.1 所示的 Tomcat 默认的管理首页。 (2)单击图 8.1 中的“Tomcat Administration”,启动 Tomcat 服务器管理工具,在弹出 的登录页面中输入 Tomcat 的管理员名称和密码,单击【Login】按钮进入如图 8.2 所示的 服务器管理工具页面。 152 第 2 篇 高 级 篇 P A R T 2 图 8.1 Tomcat 默认的管理首页 图 8.2 服务器管理工具页面 (3)在服务器管理工具页面可以看到很多有关服务器的设置。展开“Tomcat Server”/ “Service(Catalina)”/“Host(localhost)”节点,其中包括了服务器中的所有 Web 应用上下文。 本实例在服务器中的 Web 名称是“testJNDI”,展开其上下文的节点“Context(/testJNDI)”, 其中包含“Resources”节点,它管理着 Tomcat 服务器为 Web 应用提供的所有资源,单击 它的“Data Sources”子节点,会出现数据源的管理页面,单击“Data Source Actions”下拉 153 C H A P T E R 8 第 8 章 企 业 级 服 务 组合框选择“Create New Data Source”选项创建新的数据源,出现如图 8.3 所示的页面。 图 8.3 创建新数据源页面 (4)在创建新数据源页面中填写相应的数据库连接和驱动信息,单击【Save】按钮创 建数据源,最后单击图 8.2 中的【Commit Changes】按钮提交创建数据源的数据。  编写连接池代码 在了解了如何使用 Tomcat 的服务器管理工具创建数据库连接池以后,下面介绍如何通 过编写配置代码创建数据库连接池。它可以把连接池的配置代码放在程序中,程序发布到 Tomcat 服务器时会自动根据配置代码创建连接池。 使用配置代码创建数据库连接池,需要在和 WEB-INF 文件夹同级的 META-INF 文件 夹中创建 context.xml 文件。如果没有 META-INF 文件夹可以手动创建一个,在 context.xml 文件中编写如下代码。 代码中定义了数据源的相关信息,它包括数据库的连接信息和连接池的最大活动连接 数目、最大空闲连接数目、数据源对象的类型等信息。代码中的属性说明如下。 name:DataSource 的名称。 154 第 2 篇 高 级 篇 P A R T 2 type:数据源对应的 Java 类型,一般设计为“javax.sql.DataSource”类。 driverClassName:指定数据库的 JDBC 驱动程序。 maxActive:连接池处于活动状态的数据库连接的最大数目,取 0 表示不受限制。 maxIdle:连接池处于空闲状态的数据库连接的最大数目,取 0 表示不受限制。 maxWait:连接池中数据库连接处于空闲状态的最长时间(以毫秒为单位),取 0 表示 无限期等待时间。 username:数据库登录名。 password:数据库登录密码。 url:指定连接数据库 URL,db_database08 是数据库名称。 将 META-INF 文件夹和 context.xml 文件一同发布到 Tomcat5.5 服务器中,启动 Tomcat 服务器,服务器在加裁本实例的应用时会根据 context.xml 文件的配置信息自动创建数据库 连接池。 2.实例创建过程 读者可以根据自己的需要,选择一种方式在 Tomcat 服务器中创建数据库连接池。然后 根据本节的讲解完成实例的创建过程,了解如何使用 JndiObjectFactoryBean 获取 JNDI 资 源并依赖注入到需要它的 JavaBean 中。 实例位置:.....mr..\.08..\.sl..\.01.. (1)本实例是一个 Spring 框架的 Web 工程,所以必须配置 Spring 的前端控制器,以 完成客户请求的转发。在 web.xml 文件中配置 Spring 的前端控制器并命名为“dispatcher”, 设置 Spring 配置文件的名称和位置,设置所有以“.lzw”为后缀的请求都交给前端控制器 负责处理。关键代码如下。 dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation WEB-INF/applicationContext.xml dispatcher *.lzw (2)根据在 web.xml 文件中定义的 Spring 配置文件的名称和位置,在 WEB-INF 文件 夹中创建 applicationContext.xml 文件。在配置文件中分别配置数据源“dataSource”、数据 155 C H A P T E R 8 第 8 章 企 业 级 服 务 库操作类“dao”、视图解析器“viewResolver”和首页控制器“/index.lzw”,其中数据源使 用 JndiObjectFactoryBean 类从 JNDI 中获取 DataSource 的实例。在配置文件中定义它的 “jndiName”属性为 JNDI 名称“java:comp/env/myDS”,通过这个属性可以取得 Tomcat 中 定义的“myDS”数据源,然后将它注入到数据库操作类的“dataSource”属性中。关键代 码如下。 java:comp/env/myDS true WEB-INF/jsp/ 这个步骤已经使用 JndiObjectFactoryBean 类从 JNDI 中获取了 DataSource 资源并注入 到数据库操作对象中,可以看到它只需几行配置代码就能够从 JNDI 中获取资源,并把获 取的资源像使用其他 JavaBean 一样作为属性注入到需要它的 JavaBean 中。 现在继续完成这个实例,让它能够运行。 (3)从 JNDI 中获取的数据源最终被注入到数据库操作类 DAO 中。这个类定义了 getMessage()方法从数据表中查询所有公告信息。关键代码如下。 package com.lzw.dao; import java.util.List; 从 JNDI 中获取 Tomcat 的数据库 连接池 为数据库操作对 象注入从 JNDI 获 取 的 DataSource 数据源 156 第 2 篇 高 级 篇 P A R T 2 import org.springframework.jdbc.core.support.JdbcDaoSupport; public class DAO extends JdbcDaoSupport { public List getMessage() { return getJdbcTemplate().queryForList("select title from tb_BBS"); } } (4)首页控制器 indexController 类中调用 DAO 类的 getMessage()方法并把公告信息传 递给视图层,在视图层将显示这此公告。首页控制器的关键代码如下。 public class DAO extends JdbcDaoSupport { public List getMessage() { return getJdbcTemplate().queryForList("select title from tb_BBS"); } } 视图 success.jsp 的关键代码如下。 ${rec.title } 最后将 SQL Server 2000 的数据库驱动包(mssqlserver.jar、msutil.jar、msbase.jar)复制 到%Tomcat_HOME %\common\lib 文件夹下,并把程序发布到 Tomcat 中。在 IE 浏览器中 浏览程序运行结果如图 8.4 所示。 图 8.4 程序运行结果 8.2 Java 电子邮件 8.2.1 JavaM ail简介 JavaMail 是 sun 公司制定的独立于传输协议的通信解决方案,也就是说 JavaMail API 与传输协议无关。它支持多种传输协议,如 POP3、SMTP、IMAP 和 MIME 等。如果让 JavaMail 与服务器通信,则需要给它提供传输协议。例如,要实现 E-mail 收发邮件的功能,就要给 JavaMail 提供 POP3 和 SMTP 协议。JavaMail 不光有收发邮件的用途,除此之外,还有一 个重要的用途是与应用程序交流。传统的应用里,需要用户坐在系统旁边与应用程序对话, 157 C H A P T E R 8 第 8 章 企 业 级 服 务 如果用户离开了系统便无法与系统交流,而通过 E-mail,在用户离开后,依然能保持与系 统的联系。例如用户通过 E-mail 发送图书的反馈信息,服务系统回自动处理反馈信息等。 简单了解了 JavaMail 的一些内容后,还需要了解一些协议,例如 SMTP、POP3 等, 理解这些协议的基本原理有助于理解如何使用 JavaMail。 1.SMTP 协议 SMTP 被用于在英特网上传递电子邮件,它是基于文本的传输协议。它的全称是 Simple Mail Transaction Protocol。基于 JavaMail 的程序将与公司或 Internet 服务提供商(ISP)的 SMTP 服务器通信。该 SMTP 服务器将会把消息转发给用作接收消息的 SMTP 服务器,最 后用户可通过 POP 或 IMAP 协议获取该消息。由于支持身份验证,所以不需要 SMTP 服务 器是一种开放的转发器,但需要确保 SMTP 服务器配置正确。但 JavaMail API 中没有集成 用于处理诸如配置服务器以转发消息或添加/删除电子邮件帐户这一类任务的功能。 2.POP3 协议 POP 是一种传输协议,它的全称是 Post Office Protocol,含义是邮局协议。当前的版本 为 3,也称作 POP3,该协议是在 RFC 1939 中定义的。POP 是 Internet 上的大多数人用于 接收邮件的机制。它为每个用户的每个邮箱定义支持,这是它所做的全部工作,也是大多 数问题的根源。在使用 POP 协议时,人们熟悉的很多功能,如查看收到了多少新邮件消息 的功能,POP 根本不支持。这些功能都内置到诸如 Eudora 或 Microsoft Outlook 之类的邮件 程序中,能记住接收的上一封邮件,以及计算有多少新邮件这类信息。因此,使用 JavaMail API 时,如果想获取这类信息,需要自己进行计算。 3.MIME 协议 MIME 的含义是“多用途的网际邮件扩充协议”。它不是一种邮件传输协议,相反,它 定义传输的内容,如消息的格式、附件等。许多文档都定义了 MIME 协议,包含:RFC 822、 RFC 2045、RFC 2046 和 RFC 2047。 介绍了 JavaMail 的相关内容后,下面介绍在 Spring 中对 JavaMail 的支持。 8.2.2 Spring 的邮件发送器 Spring 提供了发送电子邮件的 org.springframework.mail.MailSender 接口,通过一套简 化了的 API 提供了对发送电子邮件的全面支持,它主要由两个实现类。  org.springframework.mail.javamail.JavaMailSenderImpl 类 提供了对 JavaMail API 的实现,支持高级的邮件功能,它不但可以发送普通的文本邮 件,还可以发送非 SMTP 和内嵌图片并包含其他附件文件的 MIME 邮件。由于它功能强大, 在大多数的应用中都采用 JavaMailSenderImpl 来发送邮件。它也是本节讲解的重点,在接 下来的所有实例中都采用这种方式发送邮件。  org.springframework.mail.cos.CosMailSenderImpl 类 CosMailSenderImpl 是以 COS(com.oreilly.servlet)为基础实现简单的 SMTP 邮件发 送器,它可以实现简单的文本邮件的发送,但是不能发送内嵌图片或包含其它附件文件 的邮件。 使用 JavaMailSenderImpl 类实现邮件发送,需要设置一些常用的属性信息,例如 SMTP 158 第 2 篇 高 级 篇 P A R T 2 服务器、MailSession 和服务器的验证等信息,这些邮件发送器常用的属性如表 8.1 所示。 表 8.1 邮件发送器的常用属性 属 性 描 述 host 设置邮件的 SMTP 服务器 session 设置 MailSession 对象,它和“host”属性都可以指定邮件的 SMTP 服务器 javaMailProperties 设置 JavaMail 属性,可以分别通过 JavaMail 的“mail.smtp.timeout”属性和 “mail.smtp.auth”属性设置发送邮件的超时时间并确定 SMTP 服务器是否需要验证 username 设置认证用户名 password 设置认证密码 port 设置 SMTP 服务器的端口号,服务器一般采用默认的端口 25 8.2.3 发送简单的文本邮件 下面将在一个发送简单文本邮件实例的创建过程中讲述如何在 Spring 中发送电子邮 件。在创建实例之前,先要准备支持邮件发送和接收的 SMTP 和 POP3 服务器,在 Windows 2003 操作系统中提供了这两个服务器,下面分别介绍它们的安装和配置。 1.安装 SMTP 和 POP3 服务器 视频录像:.....mr..\.08..\.lx..\.01..\.安装..SMTP....和.POP3....服务器....exe.... 打开操作系统的“控制面板”,运行“添加或删除程序”,在出现的窗口中单击“添加/ 删除 Windows 组件”,会出现如图 8.5 所示的“Windows 组件向导”对话框。 图 8.5 Windows 组件向导 在“Windows 组件向导”对话框中选择“应用程序服务器”选项,单击【详细信息】 按钮,在弹出的对话中选择“Internet 信息服务(IIS)”选项,单击该对话框的【详细信息】 按钮会出现如图 8.6 所示的“Internet 信息服务(IIS)”对话框,选择“SMTP Service”选 项,单击【确定】按钮关闭对话框。 159 C H A P T E R 8 第 8 章 企 业 级 服 务 图 8.6 Internet 信息服务(IIS)对话框 回到如图 8.5 所示的“Windows 组件向导”对话框,选择“电子邮件服务”选项,单 击【详细信息】按钮,在弹出的对话框中选择所有组件,单击【确定】按钮回到“Windows 组件向导”对话框,单击【下一步】按钮开始安装 SMTP 和 POP3 服务器。 2.配置 POP3 服务器 在安装完 POP3 服务器以后,必须创建邮箱才能接收对方发送的邮件。 视频录像:.....mr..\.08..\.lx..\.02..\.配置..POP3....服务器....exe.... 在操作系统的“控制面板”中选择“性能和维护”/“管理工具”/“POP3 服务” 运行 POP3 服务程序,在如图 8.7 所示的界面中,选择“LZWSKY”节点,这是笔者的 主机名。在不同的计算机上会以各自的主机名作为服务器的名称,单击“新域”连接, 出现“添加域”对话框,输入域名例如“lzw.com”后单击【确定】按钮创建 POP3 服 务的域名。 图 8.7 POP3 服务程序界面 创建域名后的界面如图 8.8 所示。单击域名“lzw.com”会显示域名的操作连接,单击 “添加邮箱”连接将出现如图 8.9 所示的“添加邮箱”对话框,输入邮箱名(例如 lzw)和 密码,单击【确定】按钮,提示邮箱创建成功。 160 第 2 篇 高 级 篇 P A R T 2 图 8.8 创建过域名的 POP3 服务程序界面 图 8.9 添加邮箱对话框 3.实例创建过程 在安装并配置好 SMTP 和 POP3 服务器以后,就已经具备了实例所需要的运行环境。 下面介绍程序的创建过程。 实例位置:.....mr..\.08..\.sl..\.02.. (1)实例从 JNDI 中获取了 MailSession 实例对象。这个实例对象定义了 SMTP 服务器 信息,它将被注入到 JavaMailSenderImpl 类的实例对象中。为了能够从 JNDI 中获取 MailSession 对象,必须在 Tomcat 服务器中定义它的 JNDI 服务。 在和 WEB-INF 文件夹同级的 META-INF 文件夹中创建 context.xml 文件,如果没有 META-INF 文件夹就创建一个。这个文件在 Tomcat 装载本实例的 Web 应用时为其创建 MailSession 对象的 JNDI 服务。编写 context.xml 文件代码如下。 (2)编写 web.xml 文件配置 Spring 的前台控制器,并将所有以“.lzw”为后缀的 HTML 161 C H A P T E R 8 第 8 章 企 业 级 服 务 请求都交给这个前台控制器转发到相应的后台控制器。另外还要定义 Spring 配置文件的名 称和位置,关键代码如下。 dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation WEB-INF/applicationContext.xml dispatcher *.lzw (3)创建 index.jsp 首页文件,在首页文件中定义输入发送邮件信息的文本框和提交请 求的【发送按钮】。首页文件中的重要表单元素如表 8.2 所示。 表 8.2 首页文件中的重要表单元素 名 称 类 型 含 义 重 要 属 性 sendemail form 表单 action="sendMail.lzw" method="post" to text 收件人 type="text" class="txt_grey" from text 发件人 type="text" class="txt_grey" subject text 主题 type="text" class="txt_grey" style="width:240" text textarea 邮件内容 cols="37" rows="6" class="wenbenkuang" submit submit 【发送】按钮 class="btn_grey" value="发送" button reset 【重置】按钮 class="btn_grey" value="重置" (4)编写对应首页中 Form 表单的表单类 MailForm,其中定义了对应表单元素的 to、 from、subject、text 属性和相应的 get()/set()方法,关键代码如下: package com.lzw; public class MailForm { private String to; private String from; private String subject; private String text; public String getFrom() { return from; 162 第 2 篇 高 级 篇 P A R T 2 } public void setFrom(String from) { this.from = from; } ⋯⋯ //此处代码省略 } (5)编写处理首页 Form 表单请求的控制器 TestMailSender 类,它定义了邮件发送器属 性 mailSender 和邮件模板属性 mailMessage。在 TestMailSender()构造方法中调用 setCommand Class 方法设置表单类为 MailForm。onSubmit()方法用于处理表单请求,它从表单类中获取 发送邮件的表单信息并执行邮件发送器的 send()方法,然后判断邮件发送是否成功,并转 到相应的页面中显示提示信息。关键代码如下。 package com.lzw; import java.util.Date; import org.springframework.mail.MailException; import org.springframework.mail.MailSender; import org.springframework.mail.SimpleMailMessage; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.SimpleFormController; public class TestMailSender extends SimpleFormController { private MailSender mailSender; private SimpleMailMessage mailMessage; public TestMailSender() { setCommandClass(MailForm.class); } protected ModelAndView onSubmit(Object form, BindException errors) throws Exception { MailForm mailForm = (MailForm) form; SimpleMailMessage newMailMessage; newMailMessage = new SimpleMailMessage(mailMessage); String text = mailForm.getText() + "\n" + new Date().toLocaleString(); newMailMessage.setText(text); if (!mailForm.getFrom().equals("")) newMailMessage.setFrom(mailForm.getFrom()); if (!mailForm.getTo().equals("")) newMailMessage.setTo(mailForm.getTo()); if (!mailForm.getSubject().equals("")) newMailMessage.setSubject(mailForm.getSubject()); try { mailSender.send(newMailMessage); System.out.println("邮件发送成功!"); } catch (MailException e) { e.printStackTrace(); errors.reject("error"); System.out.println("邮件发送失败\n" + e.getLocalizedMessage()); String error = e.getMessage(); 163 C H A P T E R 8 第 8 章 企 业 级 服 务 if (error.indexOf("Invalid Addresses") >= 0) error = "无效的邮件地址"; return new ModelAndView("failed", "error", error); } return new ModelAndView(getSuccessView()); } public void setMailMessage(SimpleMailMessage mailMessage) { this.mailMessage = mailMessage; } public void setMailSender(MailSender mailSender) { this.mailSender = mailSender; } } (6)编写 Spring 的配置文件 applicationContext.xml 文件。在文件中定义 mailSession 实 例,它使用 org.springframework.jndi.JndiObjectFactoryBean 类从 JNDI 中获取 MailSession 的实例对象,然后把这个 mailSession 实例注入到 mailSender 邮件发送器中。这个邮件发送 器是 org.springframework.mail.javamail.JavaMailSenderImpl 类的实例,也就是本节所讲解的 Spring 对 JavaMail 的抽象。另外还定义了邮件模板 mailMessage 和视图解析器与处理发送 邮件请求的控制器。关键代码如下。 164 第 2 篇 高 级 篇 P A R T 2 (7)最后将 activation.jar 和 mail.jar 两个程序资源包复制到 Tomcat5.5 的 common 文 件夹的 lib 子文件夹中,使 Tomcat 支持 JavaMail 服务,这样才能创建 MailSession 对象 的 JNDI 服务。把程序发布到 Tomcat 服务器中,启动服务器,浏览程序首页如图 8.10 所示。 图 8.10 程序运行首页 在首页面中输入发件人、发送人、邮件的主题和邮件内容信息,单击【发送】按钮, 提交发送邮件请求。TestMailSender 控制器在发送邮件的同时,会根据发送结果判断转发到 如图 8.11 和图 8.12 所示的成功页面或是失败页面。 图 8.11 邮件发送成功页面 165 C H A P T E R 8 第 8 章 企 业 级 服 务 图 8.12 邮件失败页面 8.3 任 务 调 度 在企业应用程序中,大多数程序都需要响应用户的触发事件,例如在 Web 应用程序中 按钮的单击或者是表单的提交。但是应用程序还有一些功能不需要用户触发,而是程序在 规定的时间间隔自动运行,例如在每天规定时间自动对数库中的数据备份或者是让程序在 指定时间自动提交所有的数据,这样的程序就需要使用任务调度。Spring 提供了两种任务 调度的抽象,它们分别是 Java 的 Timer 类和 OpenSymphony 的 Quartz 调度器。 8.3.1 Spring 的 Java T im er 调度器 Java 在 JDK1.3 或其更高的版本中提供了 java.util.Timer 任务调度器,它可以定义任务 调度的频率,即两次任务调度的间隔时间。基于 Timer 的任务调度简单易用,可以满足简 单的调度任务。 Spring 框架提供了对 Timer 类的支持,可以把所有任务和触发配置以及对 Timer 的控 制都交给 Spring 处理。ScheduledTimerTask 和 TimerFactoryBean 类是 Spring 框架提供的对 调度支持的两个核心类。ScheduledTimerTask 是 TimerTask 的包装器,可以通过它对任务定 义触发信息。TimerFactoryBean 用于配置触发器,并创建 Timer 的实例。 下面介绍一个使用 Spring 开发的任务调度的实例。该实例所演示的是在项目第一次运 行前有 1 秒钟的延迟时间,在之后的每 3 秒钟间隔打印一次“定时任务第 N 次执行”,其 中 N 是当前的执行次数。 实例位置:.....mr..\.08..\.sl..\.03.. (1)首先,需要创建一个任务 TestTask 类,它将被调度器按指定时间或固定频率来调 用。创建调度任务必须继承 TimerTask 并重写 run()方法,关键代码如下。 import java.util.TimerTask; public class TestTask extends TimerTask { int counter = 1; public void run() { 166 第 2 篇 高 级 篇 P A R T 2 System.out.println("定时任务第" + counter++ + "次执行"); } } (2)然后编写 Spring 的配置文件 applicationContext.xml。在配置文件中定义 Scheduled TimerTask 类的实例来完成任务调度,它指定的任务调度的 3 个属性分别如下。  TimerTask 此属性用于指定调度的任务类,实例中指定的是刚刚创建的 TestTask 类。  period 这个属性定义了任务调度的间隔时间,它以毫秒为单位,实例定义的间隔时间为 2s。  delay 这个属性定义任务第一次执行的延迟时间,它以毫秒为单位,实例中定义的延迟时间 为 1000ms 即 1s。 配置文件中还需要配置 TimerFactoryBean 类的实例来启动调度器,Spring 配置文件的 程序代码如下。 2000 1000 (3)最后,创建实例的主类。在主类的 main()方法中创建 ApplicationContext 容器的实 例对象。因为容器在创建后会自动开始任务调度,不需要程序做任何事情,所以主类只创 建了匿名的容器对象。程序代码如下。 import org.springframework.context.support.ClassPathXmlApplicationContext; public class ScheduledTest { 167 C H A P T E R 8 第 8 章 企 业 级 服 务 public static void main(String[] args) { new ClassPathXmlApplicationContext("applicationContext.xml"); } } 运行程序主类 ScheduledTest,在控制台将输出如图 8.13 所示的结果。 图 8.13 实例在控制台输出结果 8.3.2 Spring 的 Q uartz调度器 Quartz 是 OpenSymphony(www.opensymphon.com/quartz)提供的开源的任务调度解决 方案,它除了提供和 Java Timer 调度器相同的功能之外,还提供了按指定时间或日期完成 任务调度的功能。Quartz 的核心由两个接口和两个类组成:JobDetail 和 Trigger 类。 下面将通过一个 Quartz 实现的调度器实例创建过程讲解如何在 Spring 中使用 Quartz 调度器。 实例位置:.....mr..\.08..\.sl..\.04.. 使用 Quartz 实现调度器,需要创建完成调度任务的工作类。在本实例中将创建 QuartzJob 类,它必须实现 org.quartz.Job 接口或继承 org.springframework.scheduling.quartz 包中的 QuartzJobBean 类。实例的工作类中定义了 firstDate 记录任务调度的启动时间,它 将在 Spring 的配置文件中注入属性值。QuartzJob 类的程序代码如下。 import java.util.Date; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class QuartzJob extends QuartzJobBean { private Date firstDate; static int counter = 1; protected void executeInternal(JobExecutionContext context) throws JobExecutionException { Date date = new Date(); String now = String.format("%tT", date); System.out.println("任务调度启动于" + firstDate.toLocaleString()); System.out.println("现在时间是" + now + "。任务第" + counter++ + "次被执行\n"); } public void setFirstDate(Date firstDate) { this.firstDate = firstDate; } } 168 第 2 篇 高 级 篇 P A R T 2 Quartz 调度器将使用指定的频率或在指定的时间和日期调度任务工作类去完成指定任 务。在 Spring 中关于这些调度的定义都是在配置文件中进行的,本实例定义的 Spring 配置 文件为 applicationContext.xml。它定义了 JobDetailBean 的实例,其中属性 jobClass 指定了 工作类的类型,jobDataAsMap 属性提供了工作类需要的所有属性。本实例为工作类提供了 firstDate 属性,记录任务调度的启动时间。 Spring 中有两种方式调度工作任务,分别如下。  org.springframework.scheduling.quartz.SimpleTriggerBean 它可以完成简单的任务调度工作,类似于 ScheduledTimerTask 类,可以指定任务以怎 样的频率来运行,同时它也能够设置任务的延迟等待时间。  org.springframework.scheduling.quartz.CronTriggerBean 除 SimpleTriggerBean 调度类之外,还可以使用 CronTriggerBean 类完成更高级的调度 工作。它定义任务在指定的时间或日期运行任务完成特定的工作,例如在指定日期的某个 时间完成数据库的备份、在考勤系统的指定时间内查看员工的出勤情况等。 本实例在 Spring 的配置文件中分别定义了这两种调度类的实例 simpleTrigger 和 cronTrigger。其中 cronTrigger 调度的 cronExpression 属性的设置格式有些特别,它使用一 个特定格式的字符串定义任务的触发时间和日期,它甚至可以指定任务在一周内的任何一 天被触发。 下面介绍 cronExpression 属性的表达方式。 cronExpression 属性值以空格符分割 7 个时间元素,这 7 个时间元素位置按从左到右的 顺序定义如表 8.3 所示。 表 8.3 cronE xpression 属性值的定义 元 素 位 置 描 述 取 值 范 围 1 时间元素中的秒单位 0~59 2 时间元素中的分钟单位 0~59 3 时间元素中的小时单位 0~23 4 时间元素中的日期单位 1~31 5 日期元素中的月份单位 1~12 或 JAN~DEC 6 日期元素中的星期 1~7 或 SUN~SAT 7 日期元素中的年份单位 1970~2099 每一个元素除表 8.3 中的取值范围之外,还可以指定取值区间(例如月份可以指定区 间为 1~10,定义每个月的前 10 天)和通配符“*”和“?”。 通配符“*”代表一个元素的所有取值范围。 通配符“?”代表不必设置的元素。 分析本实例中定义的“12 13 9 * * ?”表达式,它定义了在任何一天的 9 点 13 分 12 秒 执行任务调度。其中的“?”省略了星期元素,最后的年份元素没有赋值。 在 Spring 的配置文件中定义了 SchedulerFactoryBean 类的实例用于启动调度工作。实 例定义了,启动“simpleTrigger”调度,如果将注释中的“cronTrigger”调度打开,实例将 在指定的时间启动调度。applicationContext.xml 文件的配置代码如下。 QuartzJob 1000 2000 12 13 9 * * ? 创建实例的主类 TestQuartz。它不需要处理任何业务,只需创建容器的实例对象,容 器会自动开始任务调度。程序代码如下。 170 第 2 篇 高 级 篇 P A R T 2 import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestQuartz { public static void main(String[] args) { new ClassPathXmlApplicationContext("applicationContext.xml"); } } 运行主类,调度任务将在一秒钟延迟后,每间隔两秒钟执行一次任务调度。实例运行 结果如图 8.14 所示。 图 8.14 实例启动 SimpleTriggerBean 调度运行结果 如 果 把 Spring 配 置 文件 中 quartzFactoryBean 实 例 的 “triggers” 属 性改 为 使 用 “cronTrigger”调度,它将在指定的时间启动调度任务。运行结果如图 8.15 所示。 图 8.15 实例启动 CronTriggerBean 调度运行结果 第 9 章 Spring 的 Web 框架 MVC 开发模式几乎无人不知,特别在 Web 开发中,它被频繁使用。 现在已经出现很多 Web MVC 框架,例如 Struts、WebWork 等,而 Spring 也提供了自己 Web MVC 框架,它可以应用到 Spring 的所有功能。 本章将讲述 Spring 如何处理 Web MVC 应用、以及在 Spring 中如何 使用 Tile 布局视图、Velocity 和 FreeMarker 模板视图。主要内容如下。  Spring 的 MVC 框架  处理器映射  视图解析器  控制器讲解  国际化信息  Spring 标签  Tile 布局 9.1 Spring 的 M V C 框架 9.1.1 Spring M V C 概述 Spring 提供了自己的非常灵活和可扩展的 Web MVC 框架,它在 依赖注入和 AOP 等方面更加优秀。它将对象划分成不同的角色:控制 器、可选的命令对象(Command Object)或表单对象(Form Object)、 以及视图和传递给视图的模型(Model)。Spring 的视图也相当灵活, 一个控制器甚至可以直接输出一个视图作为响应。 172 第 2 篇 高 级 篇 P A R T 2 9.1.2 Spring W eb 入门实例 本节将通过实现一个简单的登录功能来介绍 Spring 的 MVC 框架, 在这个示例中只使用到了 Spring MVC 的部分初级功能。 实例位置:.....mr..\.09..\.sl..\.01.. (1)要使用 Spring Web MVC,首先要在 Web 应用中的 web.xml 配置文件中定义 DispatcherServlet。web.xml 配置文件内容如下。 dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation WEB-INF/beans-config.xml 1 dispatcherServlet *.do (2)建立登陆页面 index.html 文件。在页面中建立两个用于输入用户名和密码的文本 框和一个登录按钮。index.html 文件内容如下。 Welcome 欢迎来到本页面,这里包含一个登录信息。请填写用户名和密码进行登录。

用户名:

密  码: 173 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架
(3)建立处理登录业务逻辑的控制器 LoginAction 类。它实现 Spring 的 org.springframe work.web.servlet.mvc.Controller 接口,并实现接口包含的处理请求的 handleRequest()方法。 当控制器 LoginAction 收到分发器分配的请求时,会执行控制器的 handleRequest()方法完成 登录的业务逻辑,判断用于登录的用户名和密码是否正确,如果登录信息正确,就转到登 录成功页面,否则转到失败页面。控制器 LoginAction 类程序代码如下。 package action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class LoginAction implements Controller { private String fail_view; private String success_view; private String userName; private String password; public String getFail_view() { return fail_view; } public String getSuccess_view() { return success_view; } public void setFail_view(String string) { fail_view = string; } public void setSuccess_view(String string) { success_view = string; } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { userName = request.getParameter("userName"); password = request.getParameter("password"); if (userName.equals("lzw") && password.equals("123")) return new ModelAndView(success_view); else return new ModelAndView(fail_view); } } (4)在 WEB-INF 中,建立 web.xml 中指定的 Spring 使用的 Bean 配置文件 beans-config. xml。在配置文件中定义一个 Bean 为控制器 LoginAction 类和 viewResolver 用于视图解析 的 Bean。关于视图解析将在后续章节讲解。beans-config.xml 文件内容如下。 174 第 2 篇 高 级 篇 P A R T 2 /WEB-INF/ .html failure success (5)建立用于显示登录成功的页面文件 success.html 文件和显示登录失败页面的 failure. html 文件。 success.html 文件内容如下。 登录成功页面 恭喜您,登录成功。 failure.html 文件内容如下。 登录失败页面 对不起,您输入的用户名或密码不正确。 将本实例放在 SpringMVCLoginExample 文件夹中,并发布到 Tomcate 服务器的 webapps 文件夹下,启动 Tomcate 服务器。在 IE 地址栏中输入“http://localhost:8080/ Spring MVCLogin Example/index.html”,出现登录页面。运行效果如图 9.1 所示。 175 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 图 9.1 登录页面 在页面中输入用户名“lzw”,输入密码“123”,单击登录按钮向控制器“/login.do”发 送请求。控制器会判断登录的用户名和密码,然后返回登录成功页面 success.html。如图 9.2 所示。 图 9.2 登录成功页面 如果输入了错误的用户名或密码,控制器将返回登录失败页面 failure.html。如图 9.3 所示。 图 9.3 登录失败页面 在本章的控制器讲解中会使用更方便、有效的各种控制器和搭配的验证器来完成登录 的验证功能。 9.1.3 配置 D ispatcherServlet(分发器) Spring 在 Web MVC 中使用 DispatcherServlet 作请求分发控制器,它在 Spring 中的包资 源路径为 org.springframework.web.servlet.DispatcherServlet,所有的客户请求都会经过它来 判断分发给哪个控制器(Controller),再由被分配的控制器处理客户请求的业务逻辑。 以入门实例中的 web.xml 配置文件为例,定义分发器的 Servlet 时,初始化参数“context ConfigLocation”以指定 Spring 的 Bean 定义文件的存放位置与文件名。上述代码中定义了 176 第 2 篇 高 级 篇 P A R T 2 DispatcherServlet 的参数“contextConfigLocation”为“WEB-INF/beans-config.xml”,Dispatcher Servlet 在初始化时会到“WEB-INF”中读取“beans-config.xml”文件。如果没有定义“context ConfigLocation”初始化参数,那么 DispatcherServlet 采用默认方式,以 Servlet 名称为前缀, 读取“Servlet 名称-servlet.xml”文件作为 Spring 的 Bean 定义文件。例如 dispatcherServlet- servlet.xml。 上述配置信息将所有以.do 后缀结尾的访问请求都映射到 DispatcherServlet 来处理,也 可以修改后缀为.html 等任何文件格式,例如“.jsp”、“.txt”等,可以采用任何符合命名规 则的字符串来作后缀。 9.1.4 处理器映射与拦截器 HandlerMapping 是 Spring 提供的客户请求与控制器映射的对象。当接收到客户的请求 时,DispatcherServlet 会根据 HandlerMapping 搜索到的正确的控制器来处理业务逻辑。下 面分别介绍 HandlerMapping 的两种实现以及如何使用处理器的拦截器。 1.BeanNameUrlHandlerMapping 如果没有定义 HanderMapping 对象,DispatcherServlet 会默认使用 HandlerMapping 对 象:org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping。它会根据请求信 息调用与 URL 同名的控制器对象。简单地说,BeanNameUrlHandlerMapping 将客户的 HTTP 请求映射到 Web 上下文中定义的 Bean 的名字上,而这个 Bean 就是为处理 HTTP 请求而定 制的控制器之一。如果使用这种处理器映射对象,在 Spring 的配置文件中配置控制器时, 标签必须使用“name”属性,而不能使用“id”属性,因为在控制器命名时,必须 以“/”为前缀才能和 URL 对应,而“id”属性不可以使用“/”前缀命名。例如: “” 2.SimpleUrlHandlerMapping 使用 SimpleUrlHandlerMapping 可以使 URL 访问指定的控制器,而不会将 URL 与控制 器名称绑定在一起。SimpleUrlHandlerMapping 与默认的 BeanNameUrlHandlerMapping 处在 同一个包路径中。SimpleUrlHandlerMapping 可以使用 Properties 和 Map 两种集合类型设置 URL 与 Bean 的映射,这两种方法的使用分别如下。 (1)使用 Properties welcomeControl loginControl 因为 mappings 属性类型为 java.util.Properties,所以使用标签为其赋值。在上述代 码中标签的“key”是客户请求的 URL,它的值就是对应的处理业务逻辑的控制器类。 177 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 (2)使用 Map 在上述代码中,属性“urlMap”类型为 java.util.Map 集合类。标签的“key”是 客户请求的 URL,因为 Map 集合的键/值可以应用 String 之外的对象类型,所以可以使用 标签赋值为其他 JavaBean 的引用,而无须重新生成 JavaBean。 3.定制多个处理器映射 在 Spring 中所有的 HandlerMapping 类都实现了 Ordered 接口,在定制 HandlerMapping 时可以指定 order 属性安排 HandlerMapping 的访问顺序。因此可以定制处理器映射,然后 指定其访问顺序以定义多个处理器映射。以前面讲解的处理器为例,定制 SimpleUrl Handler Mapping 和 BeanNameUrlHandlerMapping 两个处理器映射的示例代码如下。 0 1 名称为“simpleHandlerMapping”的 Bean 定义 order 属性值为 0,而名称为“beanName HandlerMapping”的 Bean 定义 order 属性值为 1,它们都是处理器映射对象,但是 simple Handler Mapping 的优先级要高于 beanNameHandlerMapping。只有在 simpleHandlerMapping 没有相 应的 URL 映射时才去访问 beanNameHandlerMapping 处理映射关系。 4.添加拦截器 HandlerInterceptor HandlerMapping 对象中包含 Interceptors 属性,它是 HandlerInterceptor[]类型的数组, 用于存放处理器的拦截器。拦截器可以在控制器处理请求之前和处理请求之后、或者显示 178 第 2 篇 高 级 篇 P A R T 2 视图之后进行拦截。要使用拦截器就必须实现 Spring 的“org.springframework. web.servlet. HandlerInterceptor”接口,HandlerInterceptor 接口的程序源代码如下。 package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface HandlerInterceptor{ boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; } HandlerInterceptor 接口定制了 3 个方法。 (1)preHandle()方法在执行控制器之前被调用,用于拦截控制器的执行。容器会根据方 法的 boolean 类型返回值判断是否继续执行之后的工作(包括控制器处理请求的业务逻辑) (2)postHandle()方法在执行控制器之后被调用,可用于控制器之后的收尾工作。 (3)afterCompletion()方法在控制器处理完请求并且视图 View 显示之后被调用。 除了实现 HandlerInterceptor 接口编写自己的拦截器之外,Spring 还提供了 org.springframe work.web.servlet.handler.HandlerInterceptorAdapter 适配器类,它本身已经实现了 HandlerInterceptor 接口,如果自己编写的拦截器只需要 HandlerInterceptor 接口的个别方法, 可以直接继承 HandlerInterceptorAdapter 类,之后重写需要的方法。最后把这个拦截器赋值 给 HandlerMapping 的 Interceptor 属性即可。 9.1.5 视图解析器 通过前面的讲解,读者已经了解了如何将 HTML 请求指向特定控制器。控制器在处理 获得的请求之后会返回 ModelAndView 类的对象,它包含着数据模型和显示处理数据的视 图信息。视图解析的任务就是解析 ModelAndView 对象的视图信息并将数据交由视图层处 理,其中视图有很多种表现形式,例如:HTML、JSP、PDF、EXCEL、Velocity 和 FreeMarker 模板和 Tiles 布局等。本节主要以 JSP 试图为例讲解视图解析,其他视图以及 Velocity 和 FreeMarker 模板将在本章的剩余章节中讲解。 Spring 中包含很多视图解析器,其中最常用到的是 InternalResourceViewResolver 解析 器,它可以从控制器返回的 ModelAndView 对象中解析出 JSP(或 Velocity 和 FreeMarker 模板)文件的位置,然后将数据交由解析出的视图处理。 本节主要讲解 InternalResourceViewResolver 解析器,其他解析器将在以后讲解。下面 先介绍 InternalResourceViewResolver 解析器的配置信息,关键代码如下。 org.springframework.web.servlet.view.JstlView /WEB-INF/JSP/ 179 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 .jsp 这段配置代码以“viewResolver”名称定义了 InternalResourceViewResolver 视图解析器, 这个解析器中包含很多属性,其中最常用的是“prefix”、“suffix”和“viewClass”属性, 假设控制器返回如下 ModelAndView 对象: new ModelAndView("save","data",model); 其中“save”参数是视图信息,“model”是控制器返回的数据,而“data”就是“model” 在视图层的属性名。那么配置“viewResolver”解析器的具体属性意义如下。 prefix:添加视图的前缀信息。例如代码中定义该属性值为“/WEB-INF/JSP/”,表明在 返回的视图信息前面加上这个路径信息,定义的结果为“/WEB-INF/JSP/save” suffix:这个属性的意义与 prefix 属性相反,它在返回视图信息的后面追加后缀信息。 例如代码中定义的 suffix 属性值为“.jsp”,表明解析器将指定视图信息的后缀以“.jsp”为 文件的扩展名。和代码中定义的 prefix 结合在一起,最终解析出的视图信息为: “/WEB-INF/JSP/save.jsp”,解析器将控制器返回的数据模型交由这个 JSP 视图来处理。 viewClass:它是解析器使用的视图对象。如果省略此属性,解析器将采用默认的 “InternalResourceView”类做视图对象。它可以处理 HTML 和 JSP 视图,但是如果 JSP 页 面中使用了 JSTL 标签就需要使用 Spring 的另一个视图对象,那就是代码中定义的 JstlView 视图,它以模板的形式支持 JSTL 标签。另外还有用于其他视图的视图解析器对象,例如 BeanNameViewResolver,将在 9.5 节“其他视图解析器”中讲解。 9.1.6 异常解析器 如今的 Web 网站的页面越来越漂亮、并且拥有更高的交互性,如此费尽心血设计的页面 当然不想被突如其来的服务器异常错误页面所打乱。Spring 定义的异常解析器可以在服务器 抛出异常时显示对应的异常处理页面。要使用异常解析器就必须在配置文件中定义如下代码。 loginError 代码中使用 org.springframework.web.servlet.handler.SimpleMappingExceptionResolver 类 定义了异常解析器,它通过设置 “exceptionMappings”属性,使程序出现异常时,能够将 其转向到指定的视图中,以定义好的视图来显示异常信息,这样可以防止出现服务器的异 常页面。上述代码中将异常的超类“java.lang.Exception”转向“loginError”视图,这意味 着它会把所有的异常都交由这个视图处理。 9.1.7 控制器简介 处理器映射将客户请求和控制器(Controller)映射在一起,由控制器(Controller)负 责处理客户请求。如图 9.4 所示,除 ThrowawayController 之外,所有的控制器都实现了 180 第 2 篇 高 级 篇 P A R T 2 Controller 接口,接口中包含 handleRequest()方法用于处理客户请求。实现 Controller 接口 可以定制自己的控制器。Spring 定制了很多不同用途的控制器,它们都实现了 Controller 接口,重要的是它们针对不同的业务需求扩展了相应的功能。最理想的选择就是针对相应 的业务需求去继承某个控制器来处理客户请求,Spring MVC 中的控制器如图 9.4 所示。 图 9.4 Spring 的控制器 (1)ThrowawayController 是一个接口,它只包含一个 execute()方法。此类控制器使用 后立刻销毁,不再使用(即一次性控制器)。 (2)Controller 是控制器的顶层接口和所有控制器的起源,它包含 handleRequest()方法 用于处理客户请求。所有控制器都必须实现这个接口才能接收到 DispatcherServlet 分配的 客户请求。 (3)AbstractController 此类是所有控制器的父类,它实现了 Controller 接口并扩展了 WebContentGenerator 类, 为控制器添加了 Session 会话的读取和同步化、生成 HTTP 应答的缓存指令、指定处理的请 求类型等,为控制器提供了良好的基础功能。继承 AbstractController 类可以编写处理简单 的控制器,要处理复杂的业务最好继承其他对应此业务的控制器。 (4)MultiActionController 这是一个多动作控制器,它提供了处理多个请求的能力,可以将不同的客户端请求映射到 自身不同的方法上。它需要一个方法名解析器把 URL 访问路径对应到 MultiActionController 控制器的指定方法上。 (5)BaseCommandController 与 AbstractCommandController AbstractCommandController 控制器继承自 BaseCommandController,它们是命令控制 器,可以将请求中的一个或多个参数帮定到一个实体对象中并且提供验证功能,在处理请 求时,可以从这个实体对象中读取请求参数。 (6)AbstractFormController 与 SimpleFormController SimpleFormController 是简单的表单控制器,继承自 AbstractFormController,用于处理用 户提交的表单信息。它们也同样提供了实体类来存储表单信息,并且提供了验证器的功能。 表单控制器在接收到 GET 请求时会显示表单页面,在接收到 POST 请求时处理表单数据。 181 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 (7)AbstractWizardFormController 这是一个向导控制器,如果单个表单中的信息太多,会使操作过于复杂并影响交互性。 Spring 的向导控制器可以将单个表单分解成多个表单页面,以向导的交互方式分别接收表 单,然后汇总所有表单信息在控制器中集中处理。 9.1.8 模型与视图 在 9.1.5 节的“视图解析器”中已经提到:控制器在处理获得的请求之后会返回 ModelAndView 类的对象,它包含着数据模型和显示数据模型的视图信息。ModelAndView 类封装了 Model 和 View,方便控制器同时返回这两个对象。下面介绍如何创建 ModelAnd View 对象。 ModelAndView 类有 7 个构造方法,分别如下。 (1)空构造方法 ModelAndView() 构造一个空的 ModelAndView 对象。 (2)以 View 对象为参数的构造方法 ModelAndView(View view) 包含 view 对象的构造方法。此方法会构造一个包含视图而没有数据模型的实例对象。 它只能指定视图而不能包含数据模型。 (3)以 View 对象和 Map 对象为参数的构造方法 ModelAndView(View view, Map model) 此方法在 ModelAndView(View view)构造方法之上添加了 Map 类型的数据模型 model。 ModelAndView 类采用 java.util.Map 集合类承载数据,在指定视图的同时也包含了这个视图 所需要的数据信息。 (4)以 View 对象、Map 对象和 Object 对象为参数的构造方法 ModelAndView(View view, String modelName, Object modelObject) 构造一个包含指定视图和模型的实例对象。它可以把数据模型提供给视图,并且可以 通过 modelName 参数指定数据属性在视图中的名称。多用于要传递的数据是单一的对象而 不是 Map 集合的情况。 (5)以 String 对象为参数的构造方法 ModelAndView(String viewName) 包含视图名称的构造方法。此方法会根据传入的 String 类型视图名称构造对象。它和 ModelAndView(View view)构造方法类似,但是指定的参数是视图的名称而不是视图类型的 实例对象。 (6)以 String 对象和 Map 对象为参数的构造方法 ModelAndView(String viewName, Map model) 此方法在 ModelAndView(String viewName)构造方法之上添加了 Map 类型的数据模型 model。在视图中可以访问这些数据。 (7)以 String 对象、Map 对象和 Object 对象为参数的构造方法 ModelAndView(String viewName, String modelName, Object modelObject) 182 第 2 篇 高 级 篇 P A R T 2 此方法构造一个包含 viewName 视图名称、modelObject 数据模型和访问数据模型的名 称 modelName 属性的实例对象。它和 ModelAndView(View view, String modelName, Object modelObject)构造方法类似,只是提供的是视图名称而不是视图类型对象。 其中最常使用的是后 3 个构造方法。它们将构造控制器的返回值。 9.1.9 命令控制器 在开发 Web 应用时,经常以超连接的方式传递单个或多个参数到控制器。在处理请求 的业务代码中需要逐个获取这些参数,再通过获取的参数决定返回结果。继承 Abstract CommandController 类来编写命令控制器,可以自动将参数都绑定到一个命令对象中,并提 供验证器功能以确保参数的正确性。命令控制器不必从请求中逐一获取参数,只需操作命 令对象获取命令参数并根据参数确定返回结果。 第 15 章的分页处理采用命令控制器可以完成分页功能,具体实例可参见该章。 9.1.10 表单控制器与验证器 1.表单控制器 继承 SimpleFormController 类编写的表单控制器,会自动将页面提交的表单内容绑定 到 JavaBean 中,这个 JavaBean 类似于 Struts 的 ActionForm 类.。表单控制器可以非常方便 地处理表单的显示和表单内容。它在接收到 GET 请求时,将显示 formView 属性指定的表 单页面,在接收到 POST 请求时处理表单内容,然后在没有返回 ModelAndView 类型的对 象时转到 successView 属性指定的视图中。通常通过地址栏或超链接直接访问控制器的都 是 GET 请求,所以直接显示表单页面,而通过表单提交请求时都使用 POST 方法提交以隐 藏表单的参数,这样控制器便会处理表单内容。例如下面的实例,它是应聘某公司的职位 时,所填写的个人简介信息,实例中表单参数很多,正适合使用表单控制器。 实例位置:.....mr..\.09..\.sl..\.02.. 实例中表单控制器的实现非常简单,它继承了 SimpleFormController 类并重写了 onSubmit()和 handleRequestInternal()方法,为使控制器支持中文必须在 handleRequest Internal()方法中(既控制器自动将表单参数绑定到 JavaBean 之前)将 request 参数的编码 格式设置为“GBK”中文编码。具体的表单处理则交给 onSubmit()方法处理。onSubmit() 方法有很多同名的重载方法,它们有不同的参数,为介绍控制器如何处理表单数据并将 数据结果绑定到页面中,本实例在包含 Object 类型参数的 onSubmit()方法的中简单地更 改了邮箱的未填写状态,并返回了 ModelAndView 类的实例对象。读者也可以在其他 onSubmit()的重载方法中处理表单数据,但是就本实例而言,只用到了 Object 类型的参数。 另外如果没有必要将表单的处理结果显示到页面中,也可以在 doSubmitAction()方法中处 理表单数据,它只包含一个 Object 类型的参数并且不用返回 ModelAndView 类型对象, 控制器在处理完表单数据之后会自动转到 successView 属性指定的视图中。在介绍实例中 的控制器之前,先介绍 setCommandClass()方法。它必须在初始化阶段调用,以绑定表单 类。其语法格式如下。 setCommandClass(Class commandClass) 功能:绑定表单类。 183 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 参数:commandClass,此参数时表单类的 Class 类型。 实例中的控制器 FormController 类通过 setCommandClass()方法设置 FormBean 类作表 单的实体类,并且在未填写邮箱信息时将实体类中的邮箱信息更改为“没有填写邮箱”。程 序代码如下。 package com.lzw; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.SimpleFormController; public class FormControl extends SimpleFormController { public FormControl() { setCommandClass(FormBean.class); } protected ModelAndView onSubmit(Object command) throws Exception { FormBean fb=(FormBean)command; if(fb.getMail()==null||fb.getMail().equals("")){ fb.setMail("没有填写邮箱"); } return new ModelAndView(getSuccessView(),"info",fb); } protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GBK"); return super.handleRequestInternal(request, response); } } 在 Spring 的 配 置 文 件 中 定 义 FormControl 控制器并映射访问路径为“ /form Controller.lzw”。本实例的配置文件为 applicationContext.xml,其中定义了视图解析器“view Resolver”、处理器映射“urlHandleMappings”和本实例的表单控制器“formController”, 其配置代码如下。 WEB-INF/jsp/ .jsp 在构造方法中 设置表单类 更改表单类中邮 箱的空值信息 在绑定表单参数之 前设置请求编码 定义视图解析器 184 第 2 篇 高 级 篇 P A R T 2 formController view success 像其他 Spring 的 Web 项目一样配置 web.xml 信息,其配置代码基本相同,然后运行程 序,如图 9.5 所示。 图 9.5 表单控制器实例首页 定义控制 器映射 定义控制器的配置信息,其中 “formView”属性是控制器的表单 视图,“success View”是控制器 的默认的结果视图。 185 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 在填写了所有标注为“必填”的字段信息之后单击“提交”按钮,将会看到如图 9.6 所示的运行结果。从运行结果中可以看出,在没有填写邮箱信息的状态下会显示“没有填 写邮箱”信息,而不再是空白信息。 图 9.6 表单控制器实例运行结果 2.验证器 在 Spring 中使用表单控制器可以自动绑定表单内容到 JavaBean,使代码整洁易懂、方 便维护。但是表单中经常需要对个别字段进行验证,例如日期格式、Email 格式、电话位 数和身份证位数等,以防止错误信息存储到数据库中。 Spring MVC 提供了 org.springframework.validation.Validator 接口可以实现验证器功能, 其接口的定义如下。 package org.springframework.validation; public interface Validator { boolean supports(Class clazz); void validate(Object obj, Errors errors); } 接口中包含两个方法,如下。 (1)supports()方法 supports()方法将返回值 boolean 类型,以确定被验证的表单类。在实现此方法时可以 简单地返回如下代码,以确定验证对象。其中的 FormBean 参数是自定义的表单类。 return clazz.equals(FormBean.class) (2)validate()方法 validate()方法负责验证表单类的属性信息,然后使用 Errors 对象驳回任何非法对象并 返回表单页面。其中的 Errors 对象包含 rejectValue()和 reject()方法用于加入错误信息,同时 186 第 2 篇 高 级 篇 P A R T 2 也可以使用 org.springframework.validation.ValidationUtils 类的静态方法实现简单的数据验 证。例如 rejectIfEmpty()、rejectIfEmptyOrWhitespace()等。 接下来将演示如何在上一实例中加入验证器。 首先实现 Validator 接口定义验证器类 FormValidator,其程序代码如下。 package com.lzw; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; public class FormValidator implements org.springframework.validation.Validator { public boolean supports(Class clazz) { return clazz.equals(FormBean.class); } public void validate(Object obj, Errors errors) { FormBean form = (FormBean) obj; ValidationUtils.rejectIfEmpty(errors, "name", null, "名字不能为空"); ValidationUtils.rejectIfEmpty(errors, "num", null, "身份证不能为空"); ValidationUtils.rejectIfEmpty(errors, "xueli", null, "学历不能为空"); ValidationUtils.rejectIfEmpty(errors, "phone", null, "电话不能为空"); ValidationUtils.rejectIfEmpty(errors, "address", null, "联系地址不能为空"); ValidationUtils.rejectIfEmpty(errors, "postalcode", null, "邮编不能为空"); if (form.getMail() != null && !form.getMail().equals("") && form.getMail().indexOf("@") < 0) //使用Errors对象的rejectValue()方法验证参数格式 errors.rejectValue("mail", null, "电子信箱格式错误"); } } 修改 Spring 配置文件的 formController 控制器定义信息如下。 view success 在首页面中使用标签绑定表单元素并显示验证信息。表单的提交方法使用 “POST”方法,因为表单控制器在接到“POST”请求时才会处理表单数据。关键代码如下。 <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> 使用 ValidationUtils 的 静态方法验证空参数 定义验证器为Form Validator 定义 Sring 标签 187 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 表单控制器实例
本公司招聘专业人才,有志者请填下表,我们会与你联系。
您的个人简介
⋯⋯ //省略其他表单元素的定义
您的名字:
(必填) ${status.errorMessage }
${nameError }
© 2006-2010 lzwsky
定义表单的提交方法 为 POST,这样控制器 才能处理表单数据 使用 Spring 标签 绑定表单元素并 显示验证信息 188 第 2 篇 高 级 篇 P A R T 2 服务电话:(0431)84978981 84978982
Email:mailto:mingrisoft@mingrisoft.com
加入验证器之后,再次运行程序,在首页面不填写任何信息单击“发送”按钮,将出 现如图 9.7 所示的验证页面。 图 9.7 验证器实例运行结果 9.1.11 多动作控制器 在对命令控制器的讲解中,介绍了命令控制器可以将命令参数绑定到命令对象中,控 制器根据命令对象的属性来完成业务控制,确定返回结果。例如使用“action”参数传递动 作指令,控制器根据这个参数的不同值完成不同的业务处理。这样做可以避免直接操作 request 请求对象,使代码简洁并提高可读性。但是如果每个命令的业务代码过于庞大或命 令过多,则会导致所有命令的业务代码都堆积在命令控制器的 handle()方法中,使代码过多 189 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 而且难以维护。 Spring 的另一个控制器以类似的方式处理命令参数,不同的是它将不同的命令参数或 不同的访问路径映射到控制器中的不同方法中,这样就解决了代码堆积问题。它就是多动 作控制器。 多动作控制器需要指定方法名解析器属性,Spring 有 ParameterMethodNameResolver 和 PropertiesMethodNameResolver 两种常用的方法名解析器,它们告诉控制器应该执行哪 个方法来处理 HTTP 请求。下面分别以两种方法解析器来介绍多动作控制器。 实例位置:.....mr..\.09..\.sl..\.03.. 在开始介绍控制器和方法解析器之前,先完成一个 index.jsp 页面为接下来的讲解 做一个铺垫。该页面中包含 3 个按钮,用于提交不同的参数,完成不同的任务,程序代 码如下。 <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> 多动作控制器实例
编号 商品名称 单价 数量 入库日期
00001 单人办公桌 350.00 52006-12-1
   
${message } 
页面的运行效果如图 9.8 所示。 定义 3 个提交按钮, 设置不同参数。 显示控制器返 回的数据 190 第 2 篇 高 级 篇 P A R T 2 图 9.8 首页运行效果 1.编写多动作控制器 继承 org.springframework.web.servlet.mvc.multiaction.MultiActionController 类可以编写 自己的多动作控制器,并且控制器内部的方法可以随意定义,但是必须定义方法的返回类 型为 ModelAndView。本实例中的多动作控制器 MAController 的程序代码如下。 package com.lzw; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; public class MAController extends MultiActionController { public ModelAndView add(HttpServletRequest requesst,HttpServletResponse response){ String message="正在加载页面—— >商品添加"; return new ModelAndView("index","message",message); } public ModelAndView update(HttpServletRequest requesst,HttpServletResponse response){ String message="正在加载页面—— >商品修改"; return new ModelAndView("index","message",message); } public ModelAndView delete(HttpServletRequest requesst,HttpServletResponse response){ String message="正在加载页面—— >商品删除"; return new ModelAndView("index","message",message); } } 2.ParameterMethodNameResolver 这个方法名解析器可以根据 HTTP 请求中的参数解析处理此请求的方法名。使用这个 解析器时,需要指定参数名属性“paramName”,通过解析该属性指定的参数的值确定执行 控制器的方法。无论使用哪种方法名解析器,都需要将它注入到多方法控制器的 methodNameResolver 属性中。本实例的 Spring 配置文件定义方法名解析器的程序代码如下。 / 定义 3 个处理请求的 方法,可以把它们看 作 3 个普通控制器。 191 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 .jsp multiController action 配置 web.xml 信息然后运行程序,如图 9.9~9.11 所示。 图 9.9 单击“add”按钮运行结果 图 9.10 单击“update”按钮运行结果 图 9.11 单击“delete”按钮运行结果 3.PropertiesMethodNameResolver 此方法名解析器不同于 ParameterMethodNameResolver,它不再暴露参数信息,而是直 接将访问路径通过 key/value 键值列表映射到多动作控制器的方法名上。使用该解析器需要 改写多方法控制器的映射路径为“/product*.lzw”,并且在方法名解析器的“mappings”属 定义方法名解析器并指 定参数名属性为 action 把定义的方法名解析器注入 到多动作控制器的 method NameResolver 属性中 192 第 2 篇 高 级 篇 P A R T 2 性中设置映射列表,这样设置后所有以“/product”前缀开头和“.lzw”后缀结尾的访问请 求都会交由多方法控制器处理。首先修改实例中配置文件的方法名解析器定义信息如下。 add update delete 然后把 index.jsp 首页的 3 个按钮改成超链接,连接到控制器的方法映射上。这样做可 以使用不同的请求访问多动作控制器中的不同方法。在第 18 章校园管理系统中的图书馆管 理模块完全使用了多动作控制器,读者可以参考多动作控制器在这个模块中的用法。 9.1.12 向导控制器 继承 org.springframework.web.servlet.mvc.AbstractWizardFormController 类可以编写向 导控制器。它是 Spring 中最强大的控制器,在表单控制器的基础之上扩展了跨越多个页面 处理表单的能力,类似于应用程序的安装向导。向导控制器多用于内容较多、单页表单难 以显示或需要按资料级别分类输入(例如“必填”和“选填”内容)的情况。下面通过开 发一个博客用户注册实例,介绍向导控制器的实现和验证方法。 实例位置:.....mr..\.09..\.sl..\.04.. 1.编写向导控制器 编写向导控制器除继承 AbstractWizardFormController 类之外,还需要实现该类的 processFinish()抽象方法,处理提交后的全部表单数据。另外还有 validatePage()方法,验证 每一个表单内容,processCancel()方法取消表单处理等。 (1)processFinish()方法 当向导控制器控制的所有表单都提交数据之后,将调用控制器的 processFinish()方法处 理所有的表单数据。因为它是一个抽象方法,所以在编写向导控制器时必须实现它。该方 法中有 4 个参数,分别是请求(request)、应答(response)、表单对象(command)和绑定 异常(errors)对象。请求和应答是 JSP 基础内容,本书不再多作介绍。表单类绑定了所有 表单的参数信息,如果某个表单还未提交,那么在表单对象中对应的属性就不会被初始化, 绑定异常是绑定表单参数时的错误对象。因为它接收到了向导中所有表单数据,在这个方 法中可以完成表单的业务处理,通常是将数据保存到数据库中。 (2)validatePage()方法 validatePage()方法用于验证所有表单的数据,它有 4 个 参 数 分 别 是 表 单 对 象 (command)、绑定异常(errors)、表单索引(page)和判断是否完成表单提交的参数(finish)。 表单索引参数用于获取当前提交的表单编号,它从下标 0 开始对应第一个表单,通常根据 该参数获取当前表单的索引,然后调用验证器来验证该表单的数据完整性。 193 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架  说明 有关该方法其他参数的介绍可参考 9.1.12 节。 (3)processCancel()方法 processCancel()方法在用户取消表单提交时完成向导的收尾工作,它将返回用于显示取 消表单提交的 ModelAndView 视图对象,以友好的页面结束向导。 下面是实例中向导控制器的程序代码。 package com.lzw; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractWizardFormController; public class WDController extends AbstractWizardFormController{ public WDController() { setCommandClass(FormBean.class);//设置表单类 } protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { FormBean fb=(FormBean) command; /* 插入数据库操作代码 */ return new ModelAndView("success","regInfo",fb); } protected ModelAndView processCancel(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { // 取消操作 return new ModelAndView("cancel"); } protected void validatePage(Object command, Errors errors, int page, boolean finish) { // 验证方法 WDValidator validator=(WDValidator)getValidator(); switch (page) { case 1 : validator.validateStep1(command, errors); break; case 2 : validator.validateStep2(command, errors); break; } } protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GBK"); return super.handleRequestInternal(request, response); } } 处 理 所 有 表 单 数 据,可以将表单信 息存储到数据库。 分别对每个表单中 的数据进行验证 在绑定表单数据之前,将 请求编码转换为“GBK” 194 第 2 篇 高 级 篇 P A R T 2 2.编写验证器 在上面的向导控制器代码中,validatePage()方法用于验证向导中的每个表单数据,其 中的 4 个参数的说明如表 9.1 所示。 表 9.1 validatePage()方法的参数说明 参 数 名 参 数 类 型 说 明 command Object 在初始化控制器时注入的验证器属性的引用 errors Errors 验证的错误信息 page int 表单的索引值,用于区分不同表单 finish boolean 表明是否所有表单都以提交完毕 validatePage() 方 法 的 “ command ” 参 数 的 类 型 是 “ Object ”,但 实 际 上 它 是 org.springframework.validation.Validator 接口的实现类,这个类在向导控制器初始化时被注 入到控制器的“validator”属性中。本实例中的 WDValidator 验证器实现了 Validator 接口, 并且扩展了接口,定义了 validateStep1()和 validateStep2()方法,分别用于验证必填表单和 选填表单。下面是实例中验证器的程序代码。 package com.lzw; import java.sql.Date; import org.springframework.validation.Errors; import org.springframework.validation.Validator; public class WDValidator implements Validator { public boolean supports(Class clazz) { return clazz.equals(FormBean.class); } public void validate(Object obj, Errors errors) { } public void validateStep1(Object obj, Errors errors) { //验证必填表单 FormBean fb = (FormBean) obj; if (fb.getTxt_regname() == null || fb.getTxt_regname().equals("")) errors.rejectValue("txt_regname", null, "用户名不能为空"); if (fb.getTxt_regrealname() == null || fb.getTxt_regrealname().equals("")) errors.rejectValue("txt_regrealname", null, "请输入真实姓名"); if (fb.getTxt_regpwd() == null || fb.getTxt_regpwd().equals("")) errors.rejectValue("txt_regpwd", null, "请输入密码"); else if (fb.getTxt_regpwd2() == null || fb.getTxt_regpwd2().equals("")) errors.rejectValue("txt_regpwd2", null, "请输入确认密码"); else if (!fb.getTxt_regpwd().equals(fb.getTxt_regpwd2())) errors.rejectValue("txt_regpwd2", null, "两次密码不一样,请重新输入"); if (fb.getTxt_birthday() == null || fb.getTxt_birthday().equals("")) errors.rejectValue("txt_birthday", null, "请输入出生日期"); else { try { 195 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 Date.valueOf(fb.getTxt_birthday()); } catch (Exception e) { errors.rejectValue("txt_birthday", null, "出生日期格式不对"); } } if (fb.getTxt_regemail() == null || fb.getTxt_regemail().equals("")) errors.rejectValue("txt_regemail", null, "请输入Email"); else if (fb.getTxt_regemail().indexOf("@") < 0 || fb.getTxt_regemail().indexOf(".") < 0) errors.rejectValue("txt_regemail", null, "Email格式不对"); if (fb.getTxt_city() == null || fb.getTxt_city().equals("")) errors.rejectValue("txt_city", null, "请选择所在城市"); } public void validateStep2(Object obj, Errors errors) { //验证选填表单 FormBean fb = (FormBean) obj; if (fb.getTxt_regoicq()!=null&&!fb.getTxt_regoicq().equals("")) { String str = "0123456789"; char[] cs = fb.getTxt_regoicq().toCharArray(); for (char c : cs) { if (str.indexOf(c) <= 0) { errors.rejectValue("txt_regoicq", null, "OICQ号码只能使用数字"); break; } } } if (fb.getTxt_reghomepage()!=null&&!fb.getTxt_reghomepage().equals("") && !fb.getTxt_reghomepage().startsWith("http://")) errors.rejectValue("txt_reghomepage", null, "主页必须以http://开头"); } } 3.编写配置文件 在配置文件 applicationContext.xml 中定义向导控制器时,需要指定“pages”属性,此 属性是 String 类型数组。可以使用标签为其指定表单列表,其值是向导控制器所控制 的所有表单视图名。这些值最终被解析器解析成视图,在访问控制器时,控制器首先会显 示列表中的第一个视图。实例中还指定了“validator”属性为“WDValidator”验证器。程 序代码如下。 196 第 2 篇 高 级 篇 P A R T 2 step1 step2 step3 WEB-INF/jsp/ .jsp wizard 4.定义表单分步 在完成控制器和验证器的编写并在配置文件中定义好以后,就实现了向导控制器并且 添加了验证功能。但是如何让表单之间能够连贯起来呢?向导控制器中定义了 getTargetPage()方法,该方法会解析请求中以“_target”为前缀、以数字为后缀的参数,并 且会以后缀的数字作为表单列表的索引值。在本实例中“_target1”是必填表单视图、 “_target2”是选填表单视图,而控制器默认显示的“_target0”是说明性的注册协议信息。 表单视图是一个 String 类型数组,所以它的索引下标同样是以“0”开始,当直接访问向导 控制器时将返回索引下标为“0”的表单视图。 为使表单能以正确的顺序提交,需要在表单中定义控制参数(_target*)。下面给出实 例中表单的按钮定义信息,读者可参考并举一反三,开辟更好的定义方法。 (1)step1.jsp 这是实例的首页面,在访问控制器时它是第一个被显示的视图,其运行结果如图 9.12 所示。 首页面中值包含“同意以上条款”和“不同意”两个按钮元素,其中“同意以上条款” 按钮的定义如下。 指定向导控制器 管理的表单列表 注入验证器属性 197 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 图 9.12 向导控制器实例首页 按钮的“name”属性名定义为“_target1”,这个参数提交后,按照数组下标计算,向 导控制器将显示 step2 视图。 “不同意”按钮的定义信息如下。 其中按钮的“name”属性名定义为“_cancel”,这个参数提交后,控制器会执行 processCancel()方法,默认情况下,此方法会抛出异常以取消向导控制器,但是本实例重写 了该方法并使它转向 cancel 视图。cancel 视图的运行结果如图 9.13 所示。 图 9.13 cancel 视图运行结果 (2)step2.jsp 这个页面是必填表单内容,其中不但定义了控制器需要的控制参数,而且还使用了 Spring 的绑定标签显示验证器的错误信息,Spring 标签绑定将在 9.2 节中讲解。当不填写表 单内容而直接提交时,控制器会返回验证错误信息。运行效果如图 9.14 所示。 198 第 2 篇 高 级 篇 P A R T 2 图 9.14 step2 视图运行结果 (3)step3.jsp 如图 9.15 所示,这是用户注册所填写的最后一个表单,其内容是选填的,用户可以不 填写信息。但是如果用户填写了表单内容,验证器还是会对其“OICQ”和“个人主页”信 息进行验证。当“OICQ”信息包含非数字字符时会返回“OICQ 号码只能使用数字”的验 证错误信息;当“个人主页”信息缺少“http://”前缀时也会提示验证错误信息。因为这是 向导中的最后一步,所以它的参数定义有别于其他视图,在视图中需要定义“_finish”参 数,控制器在接收到此参数时会执行 processFinish()方法处理提交后的全部表单数据。下面 是视图中的关键代码。
使 用 标签绑定参数,并输 出验证错误信息 199 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 ⋯⋯
选填内容
性别
OICQ ${status.errorMessage }
个人主页 ${status.errorMessage }

 
图 9.15 step3 视图运行结果 最后将实例发布到 Tomcat 服务器中,运行本实例。 9.1.13 Param eterizableV iew C ontroller 控制器 这个控制器利用“viewName”属性将视图页面封装在控制器内,在控制器接收到请求 时,只是简单地将请求指向封装的视图。此控制器多用于封装视图文件,避免直接对 JSP 文件提交请求。例如将所有 JSP 文件都放到“WEB-INF”文件夹中,这个文件夹是不能被 客户端访问的,可是控制器却可以访问它,这样便利用控制器和“WEB-INF”文件夹的结 合实现了 JSP 视图的封装。下面的实例演示了 ParameterizableViewController 控制器的使用 方法。 实例位置:.....mr..\.09..\.sl..\.05.. 定义 Spring 的配置文件 applicationContext.xml 文件的内容如下。 定义控制参数 200 第 2 篇 高 级 篇 P A R T 2 WEB-INF/jsp/ .jsp agreement 实例中重复利用了 9.1.12 节向导控制器的视图文件内容,所以实例运行结果也相同, 只是实现方式不同而已。实例运行结果如图 9.12 所示。 9.1.14 U rlFilenam eV iew C ontroller 控制器 此控制器与 ParameterizableViewController 控制器的区别在于 UrlFilenameViewController 控 制器以“前缀+请求名称+后缀”来解析视图名称的 URL 路径,通过此路径直接访问视图 文件,而不用在定义处理器映射。例如下面的实例。 实例位置:.....mr..\.09..\.sl..\.06.. 定义 applicationContext.xml 配置文件内容如下。 WEB-INF/jsp/ .txt WEB-INF/jsp/ .jsp 定 义 ParameterizableViewController 控制器,并注入“viewName”视图属 性,控制器用此属性名确定视图对象 201 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 其中的“com.lzw.simpleController”控制器只是简单地返回视图对象,此视图对象指向 home.lzw 控制器,再由此控制器去访问 JSP 文件。com.lzw.simpleController 控制器程序代 码如下。 package com.lzw; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; public class simpleController extends AbstractController{ protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GBK");//转换请求编码 return new ModelAndView("home.lzw");//返回视图对象 } } 此实例将 9.1.12 节向导控制器的视图文件内容稍作修改,将注册协议保存在 “agreement.txt”文件中,视图文件中以“” 语句引用指向“agreement.txt”文件的 UrlFilenameViewController 控制器,进而实现了注册 协议文本和程序代码的分离。程序运行结果如图 9.12 所示。 9.1.15 使用 W eb 应用上下文 WebApplicationContext 是 Spring 的 Web 应用容器,有两种方法可以在 Servlet 中使用 WebApplicationContext。 第 一 种 方 法 是 在 Servlet 的 web.xml 文 件 中 配 置 Spring 的 ContextLoaderListener 监听器;第二种方法同样要修改 web.xml 配置文件,在配置文件中添 加一个 Servlet,定义使用 Spring 的 org.springframework.web.context.ContextLoaderServlet 类。 现在分别介绍两种方法的使用环境和使用方法。 1.使用监听器装载 Web 应用上下文 要使普通的自定义 Servlet 可以使用 Spring 的 Web 容器,可以在 web.xml 配置文件中使 用标签定义spring 的org.springframework.web.context.ContextLoaderListener类为Web 服务的监听器,再用 WebApplicationContextUtils 获得 Web 应用上下文。具体配置代码如下。 org.springframework.web.context.ContextLoaderListener contextConfigLocation 202 第 2 篇 高 级 篇 P A R T 2 /WEB-INF/beans-config.xml 标签定义了参数“contextConfigLocation”,该参数定义了 Spring 配置 文件的名称和路径,监听器会根据参数去读取 Spring 的 Bean 配置文件(这里为 beans-config.xml),可以使用“,”分隔多个 Bean 的配置文件。例如: contextConfigLocation /WEB-INF/beans-config.xml, /WEB-INF/connection-config.xml 如果没有定义参数“contextConfigLocation”,那么监听器 ContextLoaderListener 会以默 认的方式读取 applicationContext.xml 文件。 2.定义 Servlet 装载 Web 应用上下文 使用监听器的方法需要 Web 服务器的支持。但是在早期版本的服务器和 Servlet 规范 (例如 Servlet2.2 之前的版本)中并不支持监听器方法,这时可以在 web.xml 配置文件中使 用 Spring 的 org.springframework.web.context.ContextLoaderServlet 类来新定义一个 Servlet, 并且设置加载顺序为第一位。例如: contextConfigLocation /WEB-INF/beans-config.xml webcontext org.springframework.web.context.ContextLoaderServlet 1 使用上述两种方法的任何一种都可以装载 Spring 的 Web 应用上下文,之后在普通的 Servlet 中使用 Spring 的 WebApplicationContextUtils 类可以获得 Web 应用上下文的实例, 并且可以使用这个实例来获取 Spring 管理的 JavaBean。 WebApplicationContextUtils 类有两个方法,分别是 getRequiredWebApplicationContext() 方法和 getWebApplicationContext()方法,它们都可以通过 ServletContext 来获得 Spring 的 Web 应用上下文。例如: WebApplicationContext context; context = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 9.1.16 国际化信息 1.本地化信息 在第 3 章的 3.4.4 节中已经讲解了 Spring 的国际化功能,本节将在此基础上讲解如何 利用 Spring 的国际化功能在 Web 视图中显示本地语言信息。 203 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 实例位置:.....mr..\.09..\.sl..\.07.. 在 Web 视图应用 Spring 的国际化功能需要在 JSP 视图中使用 JSTL 的标 签,这与 3.4.4 节的实例相比只是视图层不同而已,因此本节将重复利用 3.4.4 节的实例资 源完成 Web 视图的国际化。在开始实例讲解之前,先介绍标签的语法格式, 如下所示。 这是 JSTL 的国际化标签,它负责显示国际化信息,其中的参数“key”对应着资源文 件中的 key。 因为使用到了 JSTL 标签,所以必须配置 Spring 的解析器使用 JstlView 视图类,这样 才能解析视图的 JSTL 标签。另外 JSP 视图必须通过 Spring 的控制器中转才能访问 Spring 的国际化信息(也就是说:“直接访问 JSP 视图文件无法获取 Spring 的国际化信息文本”)。 在 applicationContext.xml 文件中配置关键代码如下。 org.springframework.web.servlet.view.JstlView WEB-INF/jsp/ .jsp login 最后编写一个显示国际化信息的 JSP 视图文件。它装载了 JSTL 的 fmt 标签库,并使用 fmt 标签库的标签显示消息文本。程序代码如下。 <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> <fmt:message key="LoginFrame.locale"/>
装载 JSTL 的 fmt 标签库 使 用 标 签 显 示 国际化信息 配 置 解 析 器 的 JstlView 视图类 使 用 控 制 器 封 装 JSP 视图 204 第 2 篇 高 级 篇 P A R T 2
 
 
  ">      ">
本实例的运行结果如图 9.16 所示。 图 9.16 国际化 WEB 视图的运行结果 1 读者可以改变当前系统的“区域”来查看不同地区的本地化界面。以 Windows 2000 操作系统为例,在“控制面板”中单击“区域选项”然后在“您的区域位置”下拉组合框 中选择“英语(美国)”并单击“应用”按钮改变当前系统设置,刷新如图 9.16 所示的页 205 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 面,再次查看结果,如图 9.17 所示。 图 9.17 国际化 WEB 视图的运行结果 2 2.根据参数国际化信息 从实例的运行结果中,读者会发现本实例的一个缺陷,那就是无法通过参数指定国际 消息文本。假设访问网站的是身在美国的中国留学生,如果他在浏览网站时想使用中文浏 览,那么在本实例的运行界面中就要提供各种语言的超链接。 org.springframework.web.servlet.i18n.LocaleChangeInterceptor 类实现了 Spring 的 Handler Interceptor 接口定义了(Local)区域拦截器,它在 preHandle()方法中(即控制器被调用之 前)从请求中获取“paramName”属性指定的参数,此参数中包含地域信息(例如“zh_CN” 是中国地域)。下面是这个拦截器的 preHandle()方法的关键代码定义。 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException { String newLocale = request.getParameter(this.paramName);// 获取区域参数值 if (newLocale != null) { // 如果获取的区域参数不为空 // 获取实现LocaleResolver接口的类的对象 LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); // 实例化区域属性编辑器LocaleEditor LocaleEditor localeEditor = new LocaleEditor(); // 将获取的区域参数值传递给区域属性编辑器LocaleEditor localeEditor.setAsText(newLocale); // 通过区域属性编辑器LocaleEditor把String类型的区域信息转换为java.util.Locale类的对 象,然后以它为参数调用实现了LocaleResolver接口的类的setLocale()方法,设置Locale区域信息 localeResolver.setLocale(request, response, (Locale) localeEditor.getValue()); } 206 第 2 篇 高 级 篇 P A R T 2 return true; } 在实例的applicationContext.xml配置文件中编写LocaleChangeInterceptor拦截器的定义 信息,并注入其“paramName”属性(即 request 请求中判断区域信息的参数名)为 “languageParam”,其默认值是“locale”。这样就完成了 LocaleChangeInterceptor 拦截器的 定义,但是它现在根本不能工作。分析这个拦截器的 preHandle()方法的代码片段,它在方 法中依赖 LocaleResolver 接口的 setLocale()方法完成 Locale 区域信息的设置,所以需要在 applicationContext.xml 配置文件中编写 LocaleResolver 接口实现类的定义,它的名称必须为 “localeResolver”。它在实例中的关键配置代码如下。 代码中定义的是 LocaleResolver 接口的实现类之一,其接口的一些实现类(信息解析 器)说明如下。  SessionLocaleResolver 将区域信息存储在 Session 会话范围内。它首先在 Session 会话范围内解析区域信息, 如果会话中没有区域信息则使用 request.getLocale()方法解析区域信息。  CookieLocaleResolver 将客户端 Cookie 中存取区域信息。它继承了 CookieGenerator 类,在使用此信息解析 器时,必须指定 Cookie 的名字和最大生存时间。 cookieName:此参数指定 Cookie 的名字。 cookieMaxAge:此参数是 Cookie 的最大生存时间,其默认值是 Integer.MAX_VALUE  FixedLocaleResolver 固定的本地化信息解析器,始终解析本地区域信息即 java.util.Locale.getDefault(),并 且不支持改变区域信息。  AcceptHeaderLocaleResolver 调用 request.getLocale()方法获取客户端 accept-language 头中的区域信息。它和 FixedLocaleResolve 区域解析器一样,不支持改变区域信息。 通过以上的讲解,读者应该对 LocaleChangeInterceptor 拦截器和区域解析器有所了解, 现在将“本地化信息”中的 applicationContext.xml 文件代码,修改如下。 messages 定义 SessionLocaleResolver 区域解析器 定义拦截器信息 207 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 languageParam index ⋯⋯ //其余代码不变 重新发布本实例到服务器中,在浏览器的地址栏中输入访问路径加上 “ ?languageParam=en_US ”参数访问控制器,注意访问路径中包含的请求参数 “languageParam=en_US”指定了区域属性是美国地区。再次访问实例,控制器将呈现英文 界面(背景图片上的文字除外)。也可以在地址栏输入访问路径加上参数 “?languageParam=zh_TW”访问以繁体字显示的网页。 另一种实现可以使用 CookieLocaleResolver 解析器从 Cookie 中解析区域信息,同时它 也在客户端创建 Cookie 文件,存储上一次浏览的区域信息,实现了参数持久化。修改区域 解析器的定义如下。 lzwI18NInfo 10000 再次访问网页时,不需要指定参数信息也会显示上一次选择的区域文本。 9.1.17 文件上传 Spring 提供的 MultipartResolver 解析器可以实现文件上传功能,它可以分别支持 CommonsFileUpload 和 COS FileUpload 的实现,如果使用 CommonsFileUpload 实现文件上 传,在 Spring 的配置文件中配置 MultipartResolver 解析器代码如下。 1000000 208 第 2 篇 高 级 篇 P A R T 2 上述代码中配置了 MultipartResolver 解析器的“maxUploadSize”属性来限制上传的文 件大小。 在配置解析器之后可以继承表单控制器完成文件上传的业务代码。第 14 章的“文件上 传”就是通过 MultipartResolver 解析器实现的,读者可以参见该章内容。 9.2 使用 Spring 标签 结合 JSTL 和 Spring 标签可以使 JSP 视图拥有简洁、易读的页面,也可以更好地使用 Spring 的强大功能,例如:显示国际化信息、绑定表单数据、显示验证信息等。使用 JSTL 标签可以在定义视图解析器时配置“viewClass”属性为 JstlView 类。本节主要讲解 Spring 的 3 个常用标签。使用 Spring 标签时必须在 JSP 文件中定义如下语句装载 Spring 标签库。 <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> 也可以在 web.xml 文件中配置标签库如下。 /spring WEB-INF/spring.tld 9.2.1 标签 在向导控制器的实例中已经应用了此标签,用于为命令对象或命令对象的属性赋值, 它可以显示命令对象以及它们的属性和属性的验证错误信息。当然也可以使用 JSP2.0 的 EL 表达式“${expression }”显示命令对象和属性信息。这个标签的 path 属性是必须的, 它指明命令对象或对象属性的路径。例如下面的部分 JSP 代码。 <%--绑定name属性--%> value="${status.value}"> <%--将属性值绑定给表单元素--%> ${status.errorMessage} <%--显示验证的错误信息--%> 在标签范围内存在“status”属性,它是 org.springframework.web.servlet.support.BindStatus 类的对象,在 JSP 页面中可以使用它的 4 个属性,如表 9.2 所示。 表 9.2 status 属性说明 属 性 值 描 述 value 绑定的属性字符串值 expression 获得绑定的表达式,即 path 属性指定的命令对象的属性名,可以利用这个属性定义 Form 表单的元素名 errorMessages 获得绑定的属性的所有错误信息。它是一个数组对象 errorMessage 获得 errorMessages 数组的第一个错误信息 209 C H A P T E R 9 第 9 章 Sprin g 的 Web 框 架 在 9.1.12 节的向导控制器中使用标签绑定了数据并显示了验证的错误信 息,请读者参考向导控制器的实例中对标签的应用。 9.2.2 标签 此标签用于显示国际化消息文本。它和 JSTL 的标签类似,但是 标签可以结合 Spring 框架更好地显示国际化消息文本。它可以替换消息信 息中的参数。在 JSP 页面中可以使用它的常用参数,如表 9.3 所示。 表 9.3 标签的常用参数说明 属 性 值 描 述 code 消息代码。通过此参数获取消息文本中指定的消息资源 text 默认文本。如果指定的消息代码在消息文本中不存在,就使用此参数的值作为默 认值 arguments 用此参数值替换消息文本中的参数。例如在消息文本中定义了如下消息: welcomeInfo = 欢迎我们的贵宾,{0}经理和{1}经理光临本公司 可以这样定义标签: 在 JSP 页面将输出如下结果: 欢迎我们的贵宾,李经理和王经理光临本公司 argumentSeparator 参数分隔符,在指定 arguments 参数信息时,默认采用“,”分割不同的参数,在 argumentSeparator 中定义其他任意分隔符。 9.3 使用 Tile 布局 在 Spring 中有很多 JSP 视图之外的其它视图可以使用,例如 FreeMarker 和 Velocity 模 板视图或者 Tiles 模板视图等。本节将结合一个以 Tiles 模板布局的实例,讲解如何使用 Tiles 模板为 Web 页面布局。 实例位置:.....mr..\.09..\.sl..\.08.. 9.3.1 定义视图 Tiles 以模板为蓝本定义每个页面的布局和内容,所以在编写 Tiles 的 XML 定义文件时, 先确定一个视图模板 myLayout.jsp 文件。程序代码如下。 <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <%@ taglib prefix="tiles" uri="http://jakarta.apache.org/struts/tags-tiles"%> <%--从Tiles定义中获取title信息--%> <tiles:getAsString name="title"/> <%--使用标签插入header页的定义 --%> 210 第 2 篇 高 级 篇 P A R T 2 <%--使用标签插入left页的定义 --%> <%--使用标签插入context页的定义 --%> <%--使用标签插入footer页的定义 --%>
代码中的标签和标签需要从 Tiles 的定义文件中读取信 息和 Tiles 组建(视图文件),Tiles 的定义文件以 XML 标记语言格式存放。本实例中的 tiles-defs.xml 定义文件的工具代码如下。 这段代码定义了一个 Tiles 模板“.myLayout”,以“.”为前缀可以更清晰地区分模板和 其他 Tiles 定义。这个 Tiles 模板中定义了左侧、页头、页脚和内容视图,其中的任何定义 都可以在另一个基于此模板的视图定义中被替换。例如实例中还定义了如下代码: 此视图定义继承了“.myLayout”模板并替换了 title 组建的定义信息。当 Spring 的控制 器返回“welcome”视图时,视图解析器会根据此定义生成视图页面。 9.3.2 配置 T iles 在 Spring 中 使 用 Tiles 布 局 需 要 在 applicationContext.xml 配 置 文 件 中 编 写 org.springframework.web.servlet.view.tiles.TilesConfigurer 类的定义来指定 Tiles 的定义文件, 并且指定解析器的视图为 org.springframework.web.servlet.view.tiles.TilesView 视图。如果需 要 在 视 图 中使 用 JSTL 标签,可以指定同一个包中的 TilesJstlView 视 图 。 请 参 考 applicationContext.xml 文件的关键代码。 org.springframework.web.servlet.view.tiles.TilesView WEB-INF/tiles-defs.xml welcome 从配置文件中可以看出,“ParameterizableViewController”控制器可以返回 welcome 视 图,视图解析器会到 Tiels 的定义文件中去解析该视图。将本实例发布到 Web 服务器中, 在浏览器中输入“http://localhost:8080/TilesViewDemo/welcome.do”访问控制器,最终运行 结果如图 9.18 所示。 图 9.18 Tiles 布局实例 第 10 章 集成 Web 框架 在 Spring 中虽然提供了自己的 Web MVC 框架,但是可能会因为某 些原因使用不同的 Web 框架,例如 Struts、JSF 等,本章主要讲解如 何在 Spring 中使用这两个比较流行的 Web 框架。  Struts 集成  JSF 集成 10.1 Struts 集成 10.1.1 Struts 简介 1.Struts 框架简介 Struts 是 Apache 组织的一个项目,像其他的 Apache 组织的 项目一样,它也是开放源码项目。Struts 是一个比较好的 MVC 框 架,提供了对开发 MVC 系统的底层支持,它采用的主要技术是 Servlet , JSP 和 custom tag library 。 可 以 从 http://jakarta.apache.org 网站获取它的使用版本和具体信息。 Struts 框架的基本构成如图 10.1 所示。 在 Struts 设计模式中,模型由实现业务逻辑的 JavaBean 组件构 成,控制器由ActionServlet 和 Action 实现,视图由一组 JSP 文件与 Struts 标签库构成。图 10.2 显示了 Struts 实现的 MVC 设计模式。  视图 Struts 中的视图部分依然可以采用 JSP 来实现。在这些 JSP 文件 中没有业务逻辑,也没有模型信息,只有标签,这些标签可以是标准 的 JSP 标签或客户化标签,如 Struts 标签库中的标签。 213 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 图 10.1 Struts 框架的基本构成 图 10.2 Struts 实现的 MVC 设计模式 当用户通过视图向 Servlet 发送数据时使用了 Struts 中的 ActionForm 组件,该组件 通常也被归于视图。ActionForm 的作用就是将用户提交的数据编译成 Bean 对象,除了基 本的 get ()和 set ()方法外,它还提供了另外两种特殊的方法,用于对用户提交的数据进 行一些初始化以及验证。  模型 模型表示应用程序的状态和业务逻辑。对于大型应用,业务逻辑通常采用 EJB 或其他 对象关系映射工具(如 Hibernate、IBatis)来实现模型组件。  控制器 Struts 提供了一个控制器组件 ActionServlet,它继承自 HttpServlet,并重载了 HttpSerlvet 的 doGet()、doPost()方法,可以接受 HTTP 响应,并进行转发。同时还提供 了使用 XML 进行转发 Mapping(映射)的功能。  配置 struts-config.xml 用户请求是通过 ActionServlet 来处理和转发的。这就需要一些描述用户请求路径和 Action 映射关系的配置信息。在 Struts 中,这些配置映射信息都存储在特定的 XML 文件 214 第 2 篇 高 级 篇 P A R T 2 struts-config.xml 中。在该配置文件中,每一个 Action 的映射信息都通过一个 元素来配置。 这些配置信息在系统启动的时候,被读入内存,供Struts 在运行期间使用。在内存中, 每一个元素都对应一个 org.apache.struts.action.ActionMapping 类的实例。 2.Struts 工作流程 Struts 在 Web 应 用 启 动 时 会 加 载 并 初 始 化 ActionServlet , ActionServlet 从 struts-config. xml 文件中读取配置信息,把它们存放到各种配置对象中,例如 Action 的映射信息存放在 Action Mapping 对象中。当 Action Servlet 接收到一个客户请求时,将执 行如下流程。 (1)检索和用户请求匹配的 ActionMapping 实例,如果不存在,就返回用户请求路径 无效的信息。 (2)如果 ActionForm 实例不存在,就创建一个 ActionForm 对象,把客户提交的表单 数据保存到 ActionFrom 对象中。 (3)根据配置信息决定是否需要表单验证。如果需要验证,就调用 ActionForm 的 validate()方法。 (4)如果 ActionForm 的 validate()方法返回 null 或返回一个不包含 Action Message 的 ActionErrors 对象,就表示表单验证成功;如果 ActionForm 的 validate()方法返回一 个包含一个或多个 ActionMessage 的 ActionErrors 对象,就表示表单验证失败,此时 Action Servlet 将直接把请求转发给包含用户提交表单的 JSP 组件,在这种情况下,不 会再创建 Action 对象并调用 Action 的 execute 方法。 (5)ActionServlet 根据 ActionMapping 实例包含的映射信息决定将请求转发给哪个 Action,如果相应的 Action 实例不存在,就先创建这个实例,然后调用 Action 的 execute() 方法。 (6)Action 的 execute 方法返回一个 ActionForward 对象,ActionServlet 再把客户 请求转发给 ActionForward 对象指向的 JSP 组件。 (7)ActionForward 对象指向的 JSP 组件生成动态网页,返回给客户。 3.Struts 的基本组件包 整个 Struts 大约有 15 个包,近 200 个类所组成,而且数量还在不断扩展。在此不能 一一介绍,只列举几个主要的。表 10.1 列出了目前 Struts API 包中基本的几个组件,如 action,actions,config,util,taglib,validator。 表 10.1 struts api 包中基本的几个组件 类 包 名 称 描 述 org.apache.struts.action 基本上,控制整个 struts framework 的运行的核心类、组件都在这个 包中,比如我们上面提到 ActionServlet、Action、ActionForm、 ActionMapping 等。struts1.1 比 1.0 多了 DynaActionForm 类。增加了 动态扩展生成 FormBean 的功能 org.apache.struts.actions 这个包的主要作用是提供客户的 http 请求和业务逻辑处理之间的特定 适配器转换功能,而 1.0 版本中的部分动态增删 FromBean 的类,也在 struts1.1 中被 Action 包的 DynaActionForm 组件所取代 org.apache.struts.config 提供对配置文件 struts-config.xml 元素的映射。这也是sturts1.1 中 215 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 新增的功能 org.apache.struts.util Strtuts 为了更好支持 web application 的应用,体统了一个些常用服 务的支持,比如 Connection Pool 和 Message Source 续表 类 包 名 称 描 述 org.apache.struts.taglib 这不是一个包,而是是一个客户标签类的集合。下面包括 Bean Tags, HTML Tags,Logic Tags,Nested Tags,Template Tags 这几个用于 构建用户界面的标签类 org.apache.struts.validator Struts1.1 framework 中增加了 validator framework,用于动态的 配置 from 表单的验证 如图 10.3 所示是这几个组件包之间的关系,其中 action 是整个 Struts 的核心。 util taglib action (代码) actions validator config 图 10.3 Struts 组件包之间的关系 下面结合一个简单的实例,讲解如何使用 Struts 框架。 10.1.2 Struts 简单实例 这里给出一个 Struts 简单的入门示例让读者对 Struts 开发的流程有所掌握,熟悉整 个 Struts 框架结构了解其工作原理,为下一节的 Spring 集成 Struts 讲解作一个铺垫。 实例位置:.....mr..\.10..\.sl..\.01.. 在 Struts 中用于处理前端用户请求的控制器是通过 ActionServlet 类实现的,因此在 Web 工程中的 web.xml 文件中必须配置这个 Servlet,同时需要给出 Struts 的配置文件所 在的位置,关键代码如下。 action org.apache.struts.action.ActionServlet config /WEB-INF/struts-config.xml action *.do 在这个配置文件中属性“config”用于设定 Struts 配置文件的位置和名称,同时设置 216 第 2 篇 高 级 篇 P A R T 2 对所有*.do 的请求,都交由 ActionServlet 来分配给控制器对象处理。 当用户提交了 HTML 表单之后,Struts 框架会自动把表单中的数据组装到 ActionForm 中。ActionForm 中的属性和 HTML 表单中的字段是一一对应的,下面建立一个继承 ActionForm 类的 Form 表单类,在这个类中定义与表单中相同的字段名称,同时定义相应 的 get()/set()方法。关键代码如下。 import org.apache.struts.action.ActionForm; public class UserForm extends ActionForm { public String userid = null; public String username = null; public String password = null; public int age = 0; public String getUserid(){ return userid; } public void setUserid(String userid){ this.userid = userid; } ⋯//此处省略了部分代码 } 在 Struts 中所有的控制器对象都是通过继承 Action 类或者它的子类来实现的,在这 个类中重写它的 execute()方法以执行相应的业务逻辑。在 com 包中建立一个控制器类 UserAction 用于设置用户的登录信息,其关键代码如下。 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse reponse) throws Exception { UserForm userform = (UserForm)form; if (userform == null) return mapping.findForward("fail"); request.setAttribute("name", userform.getUsername()); request.setAttribute("password", userform.getPassword()); request.setAttribute("age", String.valueOf(userform.getAge())); return mapping.findForward("success"); } 正如前面所提到的,Struts 框架允许把应用分成多个组件,以提高开发速度。创建 Struts 的配置文件 struts-config.xml 可以把这些组件组装起来,决定它们如何使用。在 本实例的配置文件中标签的“path”属性设置为 hello.do,ActionServlet 会将 使用者的请求交给 UserAction 类处理,而属性设定了使用 ActioinMapping 对象 的 findForward()方法,查找名称为“surcess”所对应的返回页面,关键代码如下。 217 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 接下来建立两个 JSP 的页面视图组件 index.jsp 和 success.jsp 用于响应用户的输入。 其中,index.jsp 中建立一个 form 表单设置它的 action 数据请求为 login.do,与 struts-config.xml 中配置的请求一致,关键代码如下。
用户ID 用户姓名
登录口令 用户年龄
UserAction 控制器最终会返回到成功页面 success.jsp,在这个页面中通过 request 对象的 getAttruibte()方法来获取相应的数据并显示在成功页面中。关键代码如下。 <% String name = null; String password = null; 218 第 2 篇 高 级 篇 P A R T 2 int age = 0; name = (String)request.getAttribute("name"); password = (String)request.getAttribute("password"); age = new Integer((String)request.getAttribute("age")).intValue(); %> 数据提交成功
用户姓名 <%=name%>
用户密码 <%=password%>
用户年龄 <%=age%>
将实例部署到 Tomcat 服务器中,启动Tomcat 服务器,在IE 的地址栏中输入访问路径, 输入相应的表单数据,然后单击【提交】按钮。运行页面如图 10.4 和图 10.5 所示。 图 10.4 首页运行页面 图 10.5 程序运行结果 本节介绍了如何在一个 Struts 框架下开发简单的 Web 程序,通过它相信读者对于 Struts 的应用开发有所了解,接下来会介绍如何在 Spring 中集成 Struts 框架。 10.1.3 Spring 集成 Struts 在 Spring 中集成 Struts 的一种方式是访问 Spring 应用上下文的基类中派生所有的 StrutsAction 子类,该类是 org.springframework.web.struts.ActionSupport 公共类, 是 Struts 中对 Action 抽象的实现,这个类可以调用 getWebApplicationContext()方法来 获取 ApplicationContext 的实例,进而使用Spring 容器中所管理的 JavaBean 实例。例如 重写上面 UserAction 类的代码如下。 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse reponse) throws Exception { 219 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 ApplicationContext context = this.getWebApplicationContext(); context.getBean("OtherAction"); return mapping.findForward("sucess"); } 这种方式实现集成的好处在于它的直观性非常符合 Struts 的习惯,除了需要继承 ActionSupport 类以及需要从应用上下文中获取 JavaBean 之外,其他的都与 Struts 应用 (例如编写 Action 控制器类和 Struts-config.xml 配置文件)相似,但是Action 类将直接 使用 Spring 特定的类,这样就使Struts 集成代码和 Spring 中的 API 紧密耦合一起。其次 在 Struts 的 Action 类中查找 Spring 管理的 JavaBean,这一点违反了 Spring 中的反向控 制(IoC)的原则。 由于存在以上两点缺陷,因此 Spring 提供了另外一种与 Struts 集成的方式,允许使 用 Spirng 的 IoC 支持将服务的 JavaBean 注入到 Action 中。具体方法是在 Struts 中仍然 继承自己的 Action 类,在 Spring 中建立这个 Action 的代理,该代理通过 ContextLoaderPlugIn 获得,从而查找真正的 Action。这种做法的好处是,Struts 和 Spring 之间不在是紧密耦合而是相互分割的,它们之间通过一个 Action 代理来进行请求,要实现 这一点需要在 Struts-config.xml 的配置文件中使用 DelegatingActionProxy 类的实例来 作为代理,关键代码如下。 定义一个 Spring 配置文件 bean-config.xml,在这个文件中要使 UserAction 中的 name 属性与 Struts-config.xml 的 Action 中的 path 属性配置相同,代理类 DelegatingActionProxy 是通过这个配置来查找到 Action 实例的,其配置代码如下。 220 第 2 篇 高 级 篇 P A R T 2 10.1.4 Spring 集成 Struts 实例 通过上一节的讲解,很轻松地就完成了 Spring 中集成 Struts 的工作,接下来将完成 一个 Spring 集成 Struts 的实例。 实例位置:.....mr..\.10..\.sl..\.02.. 本实例主要讲解集成 Spring 和 Struts 以完成数据存盘,该实例完善10.1.2 节中的程 序。 建立用户实体类 UserForm,它继承 Struts 中的 ActionForm 类,其中定义了字段属性 以及对应的 get()/set()方法,关键代码如下。 public class UserForm extends ActionForm { public String userid = null; public String username = null; public String password = null; public int age = 0; public String getUserid(){ return userid; } public void setUserid(String userid){ this.userid = userid; } public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; } ⋯//此处省略了部分代码 } 定义用户操作数据库的 UserDao 类,该类主要完成数据存盘操作,在类中定义了 Spring 的 JDBC 模板 JdbcTemplate 的实例对象“jtl”来执行数据操作,它在 Spring 的配置文件 中被注入属性值。另外,定义 executeSql()方法,其包含的参数为“insertSql”,这个参 数是 String 类型的 SQL 语句。关键代码如下。 import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; public class UserDao { private JdbcTemplate jtl = null; public JdbcTemplate getJtl(){ return jtl; } public void setJtl(JdbcTemplate jtl) { this.jtl = jtl; } public void executeSql(String insertSql){ 221 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 jtl.execute(insertSql); } } 定义 UserAction 控制器,它继承Struts 中的 Action 类,在类中定义了 UserDao 类的 实例对象“dao”。在 Spring 的配置文件中将注入其属性值,在 execute()方法中定义一个 String 类型的“addSql”属性来生成存盘语句,然后调用 UserDao 类的 execute()方法完 成数据添加,关键代码如下。 public class UserAction extends Action{ private UserDao dao = null; public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse reponse) throws Exception { UserForm formuser = (UserForm)form; String addSql = "insert into tb_userman(userid,username,password,age) values ("; addSql = addSql + "'" + formuser.getUserid().trim() + "','" + formuser.getUsername().trim() + "','" + formuser.getPassword().trim() + "'," + formuser.getAge() + ")"; dao.executeSql(addSql); return mapping.findForward("success"); } public UserDao getDao(){ return dao; } public void setDao(UserDao dao) { this.dao = dao; } } 在 Struts-config.xml 文件中完成 UserForm、context 等组件之间的配置。关键代码 如下。 222 第 2 篇 高 级 篇 P A R T 2 编写 Spring 的 beans-config.xml 配置文件,在这个配置文件中首先定义 Spring 的 JDBC 模板的实例,然后定义数据源、UserDao 等类。关键代码如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=db_spring05 sa 成功页面 success.jsp 的文件只是一个请求定向的操作,关键代码如下。 将本实例部署到 Tomcat 上,浏览实例页面,效果如图 10.4 和图 10.5 所示。 223 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 10.2 JSF 集成 10.2.1 JSF 简介及入门实例 1.JSF 框架简介 Java Server Faces (JSF)是一种应用程序框架,用于创建基于 Web 的用户界面。如 果熟悉 Struts(一种流行的开放源代码的基于 JSP 的 Web 应用程序框架)和 Swing(针对 桌面应用程序的标准 Java 用户界面),就可以将Java Server Faces 想像成这两种框架的 组合。 JSF 通过一个 Servlet 控制器来管理 Web 应用程序的生命周期,拥有带有事件处理和 组件呈现(rendering)的丰富组件模型,能够减轻开发基于 Web 应用程序的工作量。 除了在概念上组合 Struts 和 Swing 之外,JSF 还是 Microsoft WebForms 的直接竞争者。 这两个框架在概念上和实现上都非常相似。因为 JSF 代表了基于 Java 的 Web 应用程序框架 的标准,所以工具开发商可以集中精力使用 JSF 开发 IDE,而不必针对现有的任何基于 Java 的 Web 框架开发 IDE。 2.JSF 简单入门 现在来看一个简单的 JSF 示例是如何工作的。 实例位置:.....mr..\.10..\.sl..\.03.. 本实例将 10.1 节中实例的基础上使用 JSF 技术来完成相同的功能。 建立 UserBean 类,它是一个纯 JavaBean 文件,只包含几个属性变量和相应的 get()/set()方法,其关键代码如下。 public class UserBean { public String userid = null; public String username = null; public String password = null; public int age = 20; public String getUserid(){ return userid; } public void setUserid(String userid){ this.userid = userid; } public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; } ⋯//此处省略了部分代码 } JSF 同样采用了 Web MVC 框架。使用JSF 的时候,前端控制器由FacesServlet 类完成。 224 第 2 篇 高 级 篇 P A R T 2 在配置文件中定义将所有以“.faces”为后缀的请求都交由 FaceServlet 来处理。web.xml 文件的关键代码如下。 javax.faces.CONFIG_FILES /WEB-INF/faces-config.xml Faces Servlet javax.faces.webapp.FacesServlet 3 Faces Servlet *.faces 配置 faces-config.xml 文件。其中标签定义了页面的流程操作, 标签用于表示页面来源,设置标签属性值为“login”,这 样对 login 的请求将跳转到标签配置的 success.jsp 页面上,关键代码如下。 /index.jsp login /success.jsp 标签中设置管理 UserBean 的实例,同时设定实例对象的存活范围在 session 会话中,关键代码如下。 userbean com.UserBean session 接下来设计用户视图 index.jsp 文件,其中使用了 JSF 的 core 与 html 标签库。核心 标签库 core 是有关 UI 组件的处理,而 html 标签库则是关于 HTML 的高级标签,在使用这 两个标签库的标签之前,必须在页面中引入标签库,关键代码如下。 <%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %> JSF 组件必须定义在标签之间,html 标签库中几乎都是与 HTML 相关的高级标签。为提高代码编写速度,index.jsp 文件中定义 core 标签库的前缀为“f”, 定义 html 标签库的前缀为“h”,其中会产生一个表单,使用会显 示配置文件中 UserBean 的相关属性,标签会产生一个提交按钮,它的 “action”属性中指定了 faces-config.xml 配置文件中定义的“login”流程。关键代码如 225 C H A P T E R 1 0 第 10 章 集 成 Web 框 架 下。
用户ID 用户姓名
登录口令 用户年龄
surcess.jsp 是请求成功页面,代码与 index.jsp 文件类似,只是所使用的标签是 。关键代码如下。 226 第 2 篇 高 级 篇 P A R T 2
用户姓名
用户密码
用户年龄
将本实例发布到 Tomcat 服务器中,浏览 index.faces,其运行结果如图 10.5 所示。 10.2.2 Spring 如何集成 JSF JSF 框架本身对 JavaBean 的管理有依赖注入的功能,但是 Spring 的 IoC 容器更加强 大。当 JSF 与 Spring 结合的时候,主要是让 Spring 管理的 JavaBean 可以被 JSF 作为 标签的属性使用。看下面的实例是如何完成 Spring 与 JSF 集成的。 实例位置:.....mr..\.10..\.sl..\.04..使用 Spring 框架中的 JSF 代理类 DelegatioinVariableResolver,该类可以让 Spring 管理的 JavaBean 在 JSF 中使用,这一点与集合 Struts 框架十分类似。使用代理类时候需 要在 face-config.xml 文件中配置如下代码。 org.springframework.web.jsf.DelegatingVariableResolver 配置完 face-config.xml 文件后,JSF 会使用 DelegatingVariableResolver 代理类在 Spring 的 bean-config.xml 配置文件中寻找所需要的 Java Bean。关键代码如下。 为了让代理类 DelegationVariableResolver 知道定义 JavaBean 的位置和名称,还要 在 web.xml 文件中使用 Spring 的 contextLoaderListener 类,同时使用 标签指定 Spring 配置文件的位置,关键代码如下。 org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-INF/beans-config.xml 将实例发布到 Tomcat 服务器中,浏览 index.faces,其运行效果如图 10.5 所示。 3 第 3 篇 典型实例 第 1 1 章 用户信息维护 第 1 2 章 生成 Excel文件 第 1 3 章 留言本 第 1 4 章 文件上传 第 1 5 章 数据分页 第 1 6 章 企业通信软件 第 1 7 章 在线投票系统 主要内容  应用 S pring 实现 C R U D 操作  在 S pring 中应用非 H TM L 视图  复杂条件查询  S pring 应用 H ibernate 操作关系表  实现分页处理数据  生成 3D 投票结果  S pring 对 Java S w ing 的应用  实现文件的上传  开发应用程序的用户维护模块 工欲善其事 必先利其器 第 11 章 用户信息维护 信息是一个网站的基本组成元素。网站里记录了大量的信息,而对信 息进行处理与用户进行交互,也是制作动态网站基本要求。本章将以用户 信息维护为例,介绍用户信息的注册、更改和删除等基本模块的制作过程。  用户信息注册:用于用户的注册  用户信息修改:用于对已注册用户信息的修改  用户信息删除:用于删除已经注册的用户  用户信息查询:用于显示系统中已注册用户的信息 服务器端  操作系统:Windows 2003 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5 以上  数据库:SQL Server 2000  浏览器:IE6.0  分辨率:最佳效果 1024×768 像素 客户端  浏览器:IE5.0,推荐使用 IE6.0  分辨率:最佳效果 1024×768 像素 11.1 实例运行结果 在浏览器的地址栏中输入实例的测试网址。例如将用户信息维护 概 述 开发环境 228 第 3 篇 典 型 实 例 P A R T 3 的实例程序放置在 Tomcat 的 webapps 文件夹下,并将其重命名为“logineclipse”,输入 “http://127.0.0.1:8080/logineclipse”,即可进入到用户信息维护的首页,如图 11.1 所示”。 图 11.1 用户信息维护首页运行结果 11.2 设计与分析 11.2.1 系统分析 在设计该信息系统时,需要明确以下问题。 (1)用户的信息注册和修改是发生在前台由用户完成。 (2)用户的信息查询和删除是发生在后台由管理员完成。 11.2.2 文件夹及文件架构 用户信息维护的 Web 文件夹及文件架构,如图 11.2 所示。 图 11.2 用户信息维护的 Web 文件夹及文件架构图 229 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护 11.3 技术要点 11.3.1 配置视图解析器 Spring 可以支持多种视图,通过配置视图解析器来指定使用的视图技术。本实例使用 的视图解析器为 org.springframework.web.servlet.view.InternalResourceViewResolver,解析的视图类 是 org.springframework.web.servlet.view.JstlView。这个视图支持 jstl 标签,前缀为 “/”,后缀“.jsp”,表示解析的是 JSP 技术的视图。关键配置如下。 org.springframework.web.servlet.view.JstlView / .jsp 11.3.2 解决中文乱码 如果在程序中不做对中文任何的处理,那么在程序执行过程中遇到中文时,将会出现 乱码。解决办法是在控制器里添加设置字符集代码:如果使用的 SimpleFormController 控制器中,可以添加如下代码。 protected Object formBackingObject(HttpServletRequest request) throws Exception { request.setCharacterEncoding("gb2312"); // 在这里设置 字符集 return super.formBackingObject(request); } 如果使用的是 Controller 则只需要在 handleRequest(HttpServletRequest arg0, HttpServlet Response arg1)方法内添加一行如下所示代码。 arg0.setCharacterEncoding("gb2312"); 11.4 开发过程 11.4.1 数据表结构 本实例使用的数据库为 SQL Server 2000,数据库名称为 DB_User11,该数据库中的 tab_userinfo 表用于保存用户信息的数据,其结构如表 11.1 所示。 230 第 3 篇 典 型 实 例 P A R T 3 表 11.1 tab_userinfo 表 字段名称 数据类型 字段大小 允许为空 是否主键 说 明 id int 4 否 是 用户的 ID 号 username varchar 50 否 否 用户名 password varchar 50 否 否 密码 realname varchar 50 是 否 真实姓名 age int 4 是 否 年龄 tel varchar 50 是 否 电话 11.4.2 使用表单控制器实现用户注册 当填写注册信息并提交以后,数据会自动绑定到 UserForm 类的属性里保存,并且在表 单控制器中将数据取出,插入数据库。 1.创建简单 UserForm 类 这个类和 Structs 的 FormBean 有些类似,用户提交的注册数据会自动绑定在 UserForm 类里的。关键代码如下。 例程 11-1:光盘\mr\11\Users\src\com\UserForm.java package com; public class UserForm { private String username;//用户名 private String password;//密码 ⋯⋯//此处代码省略,详细内容可参见光盘 public void setUsername(String username){ this.username = username; } public String getUsername(){ return username; } ⋯⋯//此处代码省略,详细内容可参见光盘 2.配置使用表单控制器 创建 UserForm 类以后,通过 Bean 配置来指定在表单控制器中使用。关键配置如下。 //配置 RegisterUserAction处理器 com.UserForm //指定使用UserForm类  注意 name 属性必须是 commandClass。 231 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护 11.4.3 数据访问对象的设计 数据访问对象 userDao 类是操作数据库的核心类。本实例主要包括 4 个模块,分别是 注册模块、修改模块、删除模块和查询模块。这 4 个模块均有访问数据库的需求,例如, 注册模块需要把注册数据插入数据库;删除模块需要从数据库中的数据删除等等。整个应 用跟数据库息息相关。为此,可以抽象出一个数据访问层 userDao 类,由它来完成对数据 库的访问。 可以把 userDao 类配置在 Spring 的配置文件中,供IoC 容器管理。如果某个模块需要 访问数据库,可以在模块注册 Bean 容器时指定依赖(userDao 类)。之后,在程序中调用 userDao 的方法,完成对数据库的访问。 1.创建数据访问对象 (1)配置数据源 把数据库的连接信息写在配置文件里,包括注册数据库驱动、数据库路径、用户名和 密码,供 Spring IoC 容器管理。关键配置如下。 com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=DB_User11 sa 配置userDao类 (2)给 JdbcTemplate 类指定数据源 JdbcTemplate 在第 5 章中已经讲述过。如果要使用它来操作数据库,首先要给它指定 数据源,可以通过 Spring 配置文件来指定。关键配置如下。 (3)注册 Bean 容器 可以将数据库访问对象写在 Spring 配置文件中,供 IoC 容器管理。在配置 userDao 232 第 3 篇 典 型 实 例 P A R T 3 类时要给它指定依赖 JdbcTemplate。关键配置如下。 (4)编写数据访问对象代码 数据访问对象 userDao 类主要是对 JdbcTemplate 做了一层封装并抽象成单独的一个 类,让 userDao 类来提供读写数据库的方法,使应用程序可以透明地调用数据访问对象提 供的方法来访问数据库。这样,应用程序可以不必暴露读写数据库的细节,实现模块间的 解耦。设计 userDao 类的主要工作是编写读写数据库的方法。关键代码如下。 public class UserDao { private JdbcTemplate jtl; public JdbcTemplate getJtl(){ return jtl; } public void setJtl(JdbcTemplate jtl) { this.jtl = jtl; } public void execute(String sql) { //可以向数据库写数据,例如,增、删和改 jtl.execute(sql); } public List queryObject(String sql) { //可以读数据库数据,例如,查询操作 List rs = jtl.queryForList(sql); return rs; } }  注意 jtl 是已经拥有数据源的 JdbcTemplate 类的对象,在程序真正运行的时候由 IoC 容 器动态地将其注入到程序中。 2.配置数据访问对象 读者是否还记得在前面提到的 4 个模块?在实例的设计过程中,每一个模块都要访问 数据库的,现在将创建好的数据库访问对象供模块使用。由于每一模块都是单独的控器, 所以在控制器注册 Bean 容器时要为模块指定数据访问对象。关键配置如下。 例程 11-2:光盘\mr\11\Users\WebRoot\WEB-INF\bean_cogfig.xml //控制器注册Bean容器 //指定依赖 使用数据访问对象 注册模块 233 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护 //控制器注册Bean容器 //指定依赖 //控制器注册Bean容器 //指定依赖 //控制器注册Bean容器 / 11.4.4 注册模块的设计 该模块主要完成注册的功能。用户填写注册信息并提交数据后,由表单控制器的 onSubmit()方法接收前台数据,并通过 obj 对象把数据绑定在 UserForm 类中,并调用 UserForm 对象的 getxxx()方法取出数据,保存在相应的变量中。然后,使用数据访问对象 将数据插入数据库,完成注册功能。 (1)编写设置器注入方式 在上一小节中,已经给注册模块指定了依赖(userDao 类),下面将编写依赖的注入方 式(设置器注入)。关键代码如下。 public class UserRegisterAction extends SimpleFormController { private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao = userDao; } } (2)使用数据访问对象实现数据的插入 注册功能的核心是把数据插入数据库。通过调用 userDao.execute(insertSQL)把数据 插入数据库,关键代码如下。 例程 11-3:光盘\mr\11\Users\src\com\RegisterUserAction.java protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object obj, BindException bind) throws Exception { UserForm user = (UserForm) obj; //转型为UserForm类型 String username = user.getUsername(); //取出用户名 ⋯⋯ //编写插入用户名、密码、真实姓名、年龄、电话的SQL语句 String insertSQL = "insert into userinfo (username,password, 使用数据访问对象 使用数据访问对象 使用数据访问对象 更新模块 查询模块 删除模块 设置器注入 234 第 3 篇 典 型 实 例 P A R T 3 realname,age,tel) values('"+username ⋯⋯"')"; userDao.execute(insertSQL); // userDao执行SQL语句 Map msg = new HashMap(); msg.put("msg", "注册成功!"); return new ModelAndView("UserReg", msg); } 11.4.5 查询模块的设计 该模块主要完成查询的功能,支持模糊查询和多种条件查询。例如在用户名中输入 “l”,如图 11.3 所示,单击“查询”,会将查询用户名中包含“l”的用户都查出来,如 图 11.4 所示。 图 11.3 查询用户 如果没有符合查询条件数据,则提示:“没有此用户”。如果不输入任何查询条件,则 会将数据库中的所有用户列出来。 用户填写查询条件并单击“查询”时,条件数据会提交给抽象控制器的 handleRequest() 方法处理。下面介绍实现该功能的关键步骤。 (1)编写设置器代码 在 11.4.3 节中,已经给查询模块指定了依赖(userDao 类),下面将编写依赖的注入 方式(设置器注入)。关键代码如下。 将注册数据插 入数据库 235 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护图 11.4 查询结果 public class ConditionQueryAction implements Controller { private UserDao userDao; public UserDao getUserDao(){ return userDao; } public void setUserDao(UserDao userDao){ this.userDao = userDao; } (2)编写条件组合模块 如果要根据条件查询结果,首先应该判断用户输入了哪些条件,然后,把输入的若干 条件组合成一个字符串,再把这个字符串当作条件来查询。通过若干个 if 语句,实现查询 条件的组合。关键代码如下。 String Cxtj = ""; int lable = 0; if (!username.equals("")){ Cxtj = "username like '%" + username + "%'"; lable = 1; } if (!realname.equals("") && lable == 1) { Cxtj = Cxtj + " and realname like '" + realname + "%'"; } if (!realname.equals("") && lable == 0) { Cxtj = "realname like '" + realname + "%'"; 使用设置器 注入方式 236 第 3 篇 典 型 实 例 P A R T 3 lable = 1; } ⋯⋯//此处代码省略,详细内容可参见光盘 代码说明如下。 Cxtj:所有输入条件组合成的最终字符串,初使条件为“空”。 lable:表示条件的状态。lable 为 1,表示已经填写了此条件;lable 为 0,表示没有 填写此条件。 设计思路是,首先获取表单中的数据,然后对数据(username)进行判断,如果数据 不为 null,说明用户填写了此条件,并且把此条件添加在 Cxtj 中,lable 置 1;如果数据 为空,说明用户没有填写此条件(username)。接着判断下一个数据(realname)是否为 null,如果不为 null 并且 lable 为 1(说明用户填写了前一个条件“username”),在 Cxtj 尾追加一个条件(realname);如果数据(realname)不为 null 并且 lable 为 0(说明用 户没有填写前一个条件),只把当前的条件添加到 Cxtj 即可。以后,依此类推,逐次判断 age、tel⋯⋯,最终组合字符串 Cxtj 就包含各个查询条件了。 (3)使用数据访问对象实现查询功能 该模块实现了查询功能。仍然使用数据访问对象(uaerDao)访问数据库实现查询功能。 关键代码如下。 例程 11-4:光盘\mr\11\Users\src\com\QueryUserAction.java public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { arg0.setCharacterEncoding("gb2312"); String username = arg0.getParameter("username"); ⋯⋯//此处代码省略,详细内容可参见光盘 String Cxtj = ""; int lable = 0; if (!username.equals("")){ Cxtj = "username like '%" + username + "%'"; lable = 1; } ⋯⋯//此处代码省略,详细内容可参见光盘 if (lable == 0) { String querySQL = "select * from tab_userinfo"; java.util.List aList = userDao.queryObject(querySQL); return new ModelAndView("ListUser", "list", aList); } else { String querySQL = "select * from tab_userinfo where " + Cxtj + " "; java.util.List rlist = userDao.queryObject(querySQL); if (rlist.isEmpty()){ Map msg = new HashMap(); msg.put("msg", "没这个用户!"); return new ModelAndView("ConditionQuery", msg); } return new ModelAndView("ListUser", "list", rlist); } } } 使用数据访问对象 (userDao)条件查询 使用数据访问对象 (userDao)查询所有 237 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护 代码说明如下。 经过条件组合后,要对 lable 进行判断,如果等于 0,说明用户没有填写任何查询条 件,程序将查询所有的用户并返回给客户端;如果不等于 0,说明用户已经填写了查询条 件,具体有哪些条件由组合的 Cxtj 字符串决定,并把该字符串当作条件编写 SQL 语句: select * from tab_userinfo where " + Cxtj + "。然后,使用 userDao 的 queryObject() 方法向数据库发送 SQL 语句并执行查询,返回结果给客户端。 11.4.6 修改模块的设计 该模块主要实现修改用户的功能。用户修改数据提交后,由 Controller 控制器的 handleRequest()方法接收修改后的数据,并编写修改的 SQL 语句,这里使用 userDao 的.execute(updateSQL)方法更新数据。 (1)编写设置器代码 在 11.4.3 节中,已经给修改模块指定了依赖(userDao 类),下面将编写依赖的注入 方式(设置器注入)。关键代码如下。 public class UpdateUserAction implements Controller { private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao = userDao; } (2)编写修改代码 修改功能的代码和注册功能的代码差不多。不同的是,注册是把数据插入到数据库, 而修改是将修改后的数据更新到数据库。两者在 SQL 语句的编写上不同。关键代码如下。 例程 11-5:光盘\mr\11\Users\src\com\UpdateUserAction.java public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { arg0.setCharacterEncoding("gb2312"); //解决乱码 String id = arg0.getParameter("userid"); //获得修改 信息的ID号 String username = arg0.getParameter("username"); //获得已修 改的用户名 ⋯⋯ //已修改的其他数据 //编写更新到数据库的SQL语句 String updateSQL = "update tab_userinfo set username='" + username + "', password='" + password + "',realname='" + realname + "',age='" + age + "',tel='" + tel + "' where id='" + id + "'"; //使用userDao实现更新数据 userDao.execute(updateSQL); //说明:此时userDao通过设置器动态地注入 进来 Map msg = new HashMap(); msg.put("msg", "更改成功!"); return new ModelAndView("ModifiSuccee", msg); } } 238 第 3 篇 典 型 实 例 P A R T 3 11.4.7 删除模块的设计 该模块主要完成删除的功能。运行结果如图 11.5 所示。 图 11.5 删除功能 当用户单击“删除”按钮时,会将用户的 ID 号传递给控制器。控制器得到 ID 号就可 以根据 ID 将用户删除。程序代码如下。 public class DeleteUserAction implements Controller { private UserDao userDao; public UserDao getUserDao(){ return userDao; } public void setUserDao(UserDao userDao){ this.userDao = userDao; } public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { String ID = arg0.getParameter("id"); //获得删除 的ID号 String deleteSQL = "delete from tab_userinfo where id='" + ID + "' "; //编写 删除的SQL语句 userDao.execute(deleteSQL); //使用userDao实 现删除 return new ModelAndView("DeleteSuccee"); //返回删除成功页 面 } } 删除模块的代码比较简单,这里不再详细说明。 239 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护 11.4.8 将请求映射到处理器 用户提交表单时会发出处理表的请求。例如,用户在注册时会发出“注册”的请求, 希望找到有“注册”功能的处理器;查询时会发出“查询”的请求,希望找到有“查询” 功能的处理器。每一请求都对应着不同的处理器,可以将请求映射到处理器,可以在Spring 配置的文件中指定这种映射关系。关键配置如下。 例程 11-6:光盘\mr\11\Users\WebRoot\WEB-INF\bean_cogfig.xml RegisterUserAction QueryUserAction UpdateUserAction DeleteUserAction 11.4.9 配置 XML 文件 web.xml 文件主要用于配置前端控制器,以及加载 bean_cogfig.xml 文件的路径。关 于它们的详细介绍,读者可参考第 17 章的 17.5.1 节下面只给出主要代码。 例程 11-7:光盘\mr\11\Users\WebRoot\WEB-INF\web.xml WebModule1 dispatcherServlet 加载Spring控制器类 org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/bean_cogfig.xml //加载 bean_config.xml dispatcherServlet *.do 240 第 3 篇 典 型 实 例 P A R T 3 11.4.10 视图组件的实现 1.index.jsp 该页是“用户注册”的页面。在 action 中发出 save.do(“注册”)请求。关键代码如 下。 例程 11-8:光盘\mr\11\Users\WebRoot\index.jsp
用户信息注册
用户名:
密码:
2.ListUser.jsp 该页面主要用到了 jstl 标签库中标签的迭 代功能,循环遍历 list 集合中的元素并显示在网页上,关键代码如下。 例程 11-9:光盘\mr\11\Users\WebRoot\ListUser.jsp //在控制器中返回一个list集合对象,user是list集合对象中的成员变量
${user.username}//用户名 ${user.password}//密码 241 C H A P T E R 1 1 第 11 章 用 户 信 息 维 护 ${user.realname}//真实姓名 ${user.age}//年龄 ${user.tel}//电话 修改 删除
3.ConditionQuery.jsp 该页面主要使用 jstl标签的设置功能,把当前表单中数据 生命周期设置为 request,供处理器取得,关键代码如下。 例程 11-10:光盘\mr\11\Users\WebRoot\ConditionQuery.jsp ⋯⋯ ⋯⋯ 11.5 发布与运行 将该工程所在的文件夹下的 web 文件拷贝到 Tomcat 安装路径下的“webapps”文件夹 中,并将其重命名为“logineclipse”,然后重新启动 Tomcat,在浏览器中键入 “http://localhost: 8080/logineclipse”即可。 第 12 章 生成 Excel 文件 在开发 Web 应用程序时,通常会使用 JSP 技术作为客户端 界面显示,它是 HTML 页面格式。不是所有应用程序都使用 JSP 作为视图技术,还可以使用 Excel 作为视图输出数据。本章将利 用 Spring 框架,将数据生成 Excel 视图并显示出来,然后将生成 的视图文件保存到本地以便日后使用。该实例具有以下功能。  生成 Excel 视图:将数据库中的数据以 Excel 文档显示  保存 Excel 文件:将网页上显示的 Excel 视图存盘  读取数据:将 Excel 中的数据读取到控制台显示 服务器端  操作系统:Windows 2003 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5.0  数据库:SQL Server  浏览器:IE6.0  分辨率:最佳效果 1024×768 像素  浏览器:IE5.0,推荐使用 IE6.0  分辨率:最佳效果 1024×768 像素 概 述 开发环境 243 C H A P T E R 1 2 第 12 章 生 成 Excel 文 件 12.1 实例运行结果 在浏览器的地址栏中输入实例的测试网址(例如,将留言簿的实 例程序放置在 Tomcat 的 webapps 文件夹下,就可以输入 http://127.0.0.1:8080/springviewexcel/ index.jsp),即可进入到本实例的首页,如图 12.1 所示。 图 12.1 主页运行结果 单击“查看”,初一班级的学生信息显示结果如图 12.2 所示。 图 12.2 Excel 运行结果 244 第 3 篇 典 型 实 例 P A R T 3 12.2 技 术 要 点 12.2.1 PO I 简介 POI 是 apache 免费的开源插件,它主要提供对 Microsoft 产品编程的 API。Jakarta POI 中最成熟的 API 就是 HSSF。通过 HSSF 可以用 Java 代码来读取、写入和修改 Excel 文件。 HSSF(Horrible Spreadsheet Format) 是读写 Microsoft Excel 的 API。 HDF(Horrible Document Format) 是读写 Microsoft Word 97 的 API。 本实例将使用 POI 3.0 的 HSSF 类包中的 API 来操作 Excel 文件,关于包中相关类的详 细用法,读者可以参考帮助文档。本章将主要讲述 Java 对 Excel 文挡的读、写的实现方法。 12.2.2 数据写入的创建过程 把数据写入 Excel 中必须经过以下步骤。 (1)创建 workbook 对象 HSSFWorkbook workbook =new HSSFWorkbook(); (2)通过 workbook 对象创建工作区对象并命名为 test excel HSSFSheet sheet = workbook.createSheet(“test excel”); (3)由工作区对象创建行对象 HSSFRow row= sheet.createRow(0); (4)由行对象创建单元格对象 HSSFCell cell = title.createCell((short)1); (5)把数据写入将单元格里 cell.setCellValue(“this is title”); (6)保存 Excel 文档 FileOutputStream out=new FileOutputStream("C:\\test.xls "); workbook.write(out); //将Excel文档保存到C盘根目录下test.xls out.close(); 经过上面创建后,数据在 Excel 文档显示的结果如图 12.3 所示。 由第(5)步创建得到 由第(2)步创建得到 245 C H A P T E R 1 2 第 12 章 生 成 Excel 文 件 图 12.3 创建后运行结果 12.2.3 如何设置字体和单元格样式 通过 HSSFFont 和 HSSFCellStyle 类设置数据在 Excel 中显示的字体、颜色、大小和单 元格样式。设置过程如下。 (1)通过 HSSFFont 类创建字体对象 HSSFFont font = workbook.createFont(); //由workbook创建字体 (2)通过 font 来设置字体属性 font.setFontHeightInPoints((short)8); //设置字体属性 font.setFontHeight((short)HSSFFont.BOLDWEIGHT_NORMAL) ; //设置字体属性 font.setColor((short)(HSSFFont.COLOR_RED)); //设置字体属性 (3)通过 HSSFCellStyle 类创建单元格样式对象 HSSFCellStyle cellstyle = workbook.createCellStyle(); //由workbook创建单元格样式 (4)通过 cellstyle 来设置样式属性 cellstyle.setFont(font); //设置样式属性 (5)通过 HSSFCell 类创建单元格对象 HSSFCell cell = row.createCell((short)1); //创建单元格 (6)将样式应用于单元格对象 cell.setCellStyle(cellstyle); //使用已创建的样式 通过上述设置后显示效果如图 12.4 所示。 图 12.4 字体和样式的显示效果 12.2.4 读取 E xcel单元格中的数据 要读取单元格中的数据必须经过以下步骤。 (1)创建对 Excel 文档的引用对象 获取 C 盘根目录下的 test 电子表格文件的引用对象 workbook。 HSSFWorkbook workbook= new HSSFWorkbook(new FileInputStream("C:\\test.xls")); 246 第 3 篇 典 型 实 例 P A R T 3 (2)获取工作区表对象 通过 workbook 对象获得工作区 test 表对象。 HSSFSheet sheet = workbook.getSheet("test"); 如果工作区表未知,可以通过工作区的缺省标识"0",来获得表对象。 HSSFSheet sheet = workbook.getSheetAt(0); (3)获取行对象 通过工作区表对象 sheet 来获取行对象。 HSSFRow row = sheet.getRow(0); (4)获取单元格对象 通过行对象 row 来获得单元格对象。 HSSFCell cell = row.getCell((short)0); (5)读取单元格中的数据 通过单元格对象 cell 的 getStringCellValue()来读取单元格中的数据。 cell.getStringCellValue(); 12.2.5 如何在 E xcel表格中显示数据 Excel 文档由行元素和列元素构成,把数据按照整行整列显示,可以通过循环控制实现。 关键代码如下。 int i=0; while (table.next()){ HSSFRow dataRow = sheet.createRow((short) (i + 2)); //创建一行 for(int j=0;j<9;j++){ HSSFCell cell = dataRow.createCell((short) j); //创建单元格 cell.setCellStyle(datestyle);//设置单元格样式 String data=table.getString(j+2); //取出一条记录的记录项 cell.setCellValue(data); //把数据写到单元格里 } i++; } 代码讲解如下。 table:是数据的集合,显示的原始记录的集合。 table.next():指向集合中的下一条记录,可作为循环结束的判断条件。 table.getString(x):取得当前记录的第 x 个记录项。 x:范围 1~9,因为一条记录共 9 个记录项。 下面介绍将 table 集合里原始记录显示在 Excel 中的算法。 用 while 循环遍历 table 集合中的每条数据记录,在 while 循环中定义 for 循环将数据记 录的每个字段值写到 Excel 表格的对应字段中。这是一个典型的遍历数组的算法。 247 C H A P T E R 1 2 第 12 章 生 成 Excel 文 件 12.3 开 发 过 程 12.3.1 创建实现类 当用户单击“查看”时,会将所有人员信息显示到 Excel 中。这一过程分两步完成, 第一步,从数据库中将数据查出来并保存在 SqlRowSet 集合中;第二步,将集合中的数据 显示在 Excel 电子表格中。 (1)从数据库取出数据 取数据由 UserXslController 类完成。在 handleRequest 方法中,先创建连接,然后向数 据库发送 SQL 语句查询出结果,这里的返回结果类型为 SqlRowSet,它可以类似于 ResultSet 那样遍历结果集。将集合放在 Map 键值对里,传给 UserXslView 类。代码如下。 例程 12-1:光盘\mr\12\springviewexcel\src\com\UserXslController.java public class UserXslController implements Controller { private Map msg = new HashMap(); public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse reponse) throws Exception { String name = request.getParameter("selectname"); String classname = new String(name.getBytes("ISO8859_1"), "gb2312"); msg.put("title", classname + " 学生详细信息"); DriverManagerDataSource dmds = new DriverManagerDataSource(); //获得数据源 dmds.setDriverClassName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); //获得url dmds.setUrl("jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=DB_Excel12");//获得驱动 dmds.setUsername("sa"); //数据库用户名 dmds.setPassword(""); //数据库密码 JdbcTemplate jtl = new JdbcTemplate(dmds); //将数据源给JdbcTemplate模板 //使用queryForRowSet方法查询返回结果为SqlRowSet类型。 SqlRowSet list = jtl.queryForRowSet("SELECT stu_id, name, sex, age,sfzhm,csrq,zzmm,jtdh,jtdz,jkzk FROM tab_stuinfo where classid = ’"+classname +"’"); msg.put("table", list); //将结果集存到map中 return new ModelAndView("customxslview", msg); } } (2)在 Excel 中显示数据 首先继承 Spring 提供的 AbstractExcelView 类,并且在 buildExcelDocument()方法中编写 显示的代码。参数 model 是 Map 类型,用于接收 UserXslController 类携带的数据。获得数 据后,先设置标题、表头字体和样式,然后将 model 里的数据循环写入 Excel 文档显示。 程序代码如下。 例程 12-2:光盘\mr\12\springviewexcel\src\com\UserXslView.java public class UserXslView extends AbstractExcelView { protected void buildExcelDocument(Map model, HSSFWorkbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { SqlRowSet table = (SqlRowSet) model.get("table"); //取得数据集合 248 第 3 篇 典 型 实 例 P A R T 3 String title = model.get("title").toString(); //取得标题 HSSFSheet sheet = workbook.createSheet(title); //创建工作区 HSSFRow row_title = sheet.createRow(0); //创建行对象 HSSFFont title_font = workbook.createFont(); //创建标题的字体 title_font.setFontHeightInPoints((short)8); //设置标题字体属性 title_font.setFontHeight((short)HSSFFont.BOLDWEIGHT_NORMAL); //同上 title_font.setColor((short)(HSSFFont.COLOR_RED)); //同上 HSSFCellStyle title_style = workbook.createCellStyle(); //创建样式 title_style.setFont(title_font); //设置标题样式属性 HSSFCell cell_title = row_title.createCell((short)1); //创建单元格对象 cell_title.setCellStyle(title_style); //设置单元格样式 cell_title.setCellValue(title); //将标题写到Excel表格里 //以上为标题的字体和样式的属性设置 String titles[] = {"学生姓名","性别","年龄","身份证号","出生日期","政治面貌","家庭电 话","家庭地址","健康状况"}; HSSFRow row = sheet.createRow((short) 1); //创建行 HSSFCellStyle items_style = workbook.createCellStyle(); //创建样式 items_style.setAlignment((short)HSSFCellStyle.ALIGN_CENTER); //设置表头样式 HSSFFont celltbnamefont = workbook.createFont(); // 创建字体 celltbnamefont.setFontHeightInPoints((short)10); //设置表头字体属性 celltbnamefont.setColor((short)(HSSFFont.COLOR_RED)); //设置表头字体属性 items_style.setFont(celltbnamefont); //设置表头字体属性 items_style.setWrapText(true); for (int i = 0; i < titles.length; i++) { HSSFCell cell = row.createCell((short) i); if (i == 3 || i == 6 || i == 2) { sheet.setColumnWidth((short) i, (short) 5335); } else { sheet.setColumnWidth((short) i, (short) 3335); } cell.setCellStyle(items_style); //设置到此结束 cell.setCellValue(titles[i]); //将数据表头数据写到单元格里 } HSSFCellStyle datestyle = workbook.createCellStyle(); HSSFDataFormat df = workbook.createDataFormat(); datestyle.setDataFormat(df.getFormat("yyyy-mm-dd")); //设置日期在Excel中的显示格式 int i=0; while (table.next()){ HSSFRow dataRow = sheet.createRow((short) (i + 2)); for(int j=0;j<9;j++){ HSSFCell cell = dataRow.createCell((short) j); String data=table.getString(j+2); cell.setCellStyle(datestyle); cell.setCellValue(data); } i++; 249 C H A P T E R 1 2 第 12 章 生 成 Excel 文 件 } } } 12.3.2 创建 R eadX lsC ontroller 该控制器的功能是读取 Excel 文档中的数据并把它显示在控制台上。代码如下。 例程 12-3:光盘\mr\12\springviewexcel\src\com\ReadXlsController.java public class ReadXlsController implements Controller { private String text[][]; private short columns; private int rows; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse reponse) throws Exception { String pp = request.getParameter("path"); String path = new String(pp.getBytes("iso-8859-1"), "gb2312"); HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(path)); HSSFSheet sheet = workbook.getSheetAt(0); HSSFRow row = sheet.getRow(sheet.getLastRowNum()); rows = sheet.getLastRowNum()+1; columns = row.getLastCellNum(); text=new String[rows][columns]; for (int i = 0; i < rows; i++) { row = sheet.getRow(i); for (short j = 0; j < columns; j++) { if(row==null){ text[i][j]=null; continue; } HSSFCell cell; int type = 0; cell = row.getCell(j); if (cell != null) { type = cell.getCellType(); if (type == 0) { Double num = cell.getNumericCellValue(); DecimalFormat df = new DecimalFormat("0.00"); String size = df.format(num); text[i][j] = size; } else if (type == 1) { String string = cell.getRichStringCellValue() .toString(); text[i][j] = string; } } else { text[i][j] = null; 250 第 3 篇 典 型 实 例 P A R T 3 } } } return new ModelAndView("display","list",text); } } 查看一下运行效果。先单击“浏览”,选择 c 盘跟目录下的“初三班”的 Excel 文件, 如图 12.5 所示,然后单击“读取 Excel 文件”,结果如图 12.6 所示。 图 12.5 读取 Excel 文档 图 12.6 显示结果 12.3.3 建立 bean_config.xm l文件 定义生成用户的控制器请求,其关键代码如下。 例程 12-4:光盘\mr\12\springviewexcel\WebRoot\WEB-INF\bean_config.xml 251 C H A P T E R 1 2 第 12 章 生 成 Excel 文 件 /Display.jsp 12.3.4 配置 w eb.xm l 设置 servlet 完成用户的响应操作,其关键代码如下。 例程 12-5:光盘\mr\12\springviewexcel\WebRoot\WEB-INF\web.xml dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/bean_config.xml 1 dispatcherServlet *.do 定义一个用于显示供用户进行班级选择的 index.jsp 文件,当用户单击提交按钮时即可 完成表单数据的提交,其代码如下。 例程 12-6:光盘\mr\12\springviewexcel\WebRoot\index.jsp 252 第 3 篇 典 型 实 例 P A R T 3
请选择Excel文件
初中一班 查看
初中二班 查看
初中三班 查看
12.4 发布与运行 将该实例所在的文件夹下的 defaultroot 文件夹拷贝到 Tomcat 安装路径下的“webapps” 文件夹中,并将其重命名为“Excel”,然后重新启动 Tomcat,在浏览器中键入“http://local host:8080/Excel”。 第 13 章 留言本 留言本可以说是每个网站的重要组成部分。它是网站访问者发表 意见的场所,也是网友们相互沟通的桥梁。目前网站留言本的形式多 种多样,有只记录用户留言内容的简易留言本,也有带管理员回复功 能的复杂留言本。本章介绍的留言本功能比较简单,该留言本具有以 下功能。  显示当日的留言:主要用于显示系统中当天的全部留言 信息  查看留言信息:主要用于查看已发布的留言信息  用户登录:主要用于用户登录  回复留言:主要用于对用户的留言信息进行回复  发表留言:用于发布自己的主题  用户注册:用于注册系统  查看本周留言:用于查看系统在本周内的所有留言 服务器端  操作系统:Windows 2003 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5.0  数据库:SQL Server 2000  浏览器:IE6.0  分辨率:最佳效果 1024×768 像素 客户端  浏览器:IE5.0,推荐使用 IE6.0  分辨率:最佳效果 1024×768 像素 概 述 开发环境 254 第 3 篇 典 型 实 例 P A R T 3 13.1 实例运行结果 在浏览器的地址栏中输入实例的测试网址(例如,将留言本的实例程序放置在 Tomcat 的 webapps 文件夹下,就可以输入 http://localhost:8080/MessageBook),即可进入到留 言本的首页,如图 13.1 所示。 图 13.1 留言本运行结果 13.2 设计与分析 13.2.1 系统分析 在设计该留言本时,需要明确以下问题。 (1)对于没有登录的用户,只能查看留言内容。 (2)对于已经登录用户,除了可以查看留言外,还具有回复留言和发表留言的功能。 13.2.2 系统流程 网站留言本的系统流程如图 13.2 所示。 图 13.2 系统流程图 255 C H A P T E R 1 3 第 13 章 留 言 本 13.2.3 文件夹及文件架构 留言本的 Web 文件夹及文件架构如图 13.3 所示。 中文和英文资源 文件的位置 图 13.3 Web 文件夹及文件架构图 13.3 技 术 要 点 13.3.1 如何获取日期 在留言本中需要显示当天的所有留言信息,所以程序需要根据日期来查询留言。这 时可以设计一个 Daily 类,如果想获取当天的日期,只要调用 getToday()即可。关键 代码如下。 public class Daily { private Calendar now=Calendar.getInstance(); private int year = now.get(Calendar.YEAR); private int month = now.get(Calendar.MONTH) + 1; private int day = now.get(Calendar.DAY_OF_MONTH); public String getToday(){ return "%"+this.month+"%"+this.day+"%"+this.year+"%"; return today; } } 13.3.2 如何从数据库查询出无重复标题 在发表主题时,会有很多人对主题回复,这样数据库里就有了多个相同的标题。为了 使浏览者根据标题来查看留言,在留言本主页上显示的标题要求惟一,可以使用聚合函数 和分组来过滤重复的标题。查询方法如下。 select message_title,min(message_time) as message_time from tab_message where message_time 256 第 3 篇 典 型 实 例 P A R T 3 like '%"+ date+"%' group by message_title 上面的 SQL 语句可以根据日期查询出无重复的标题,并且是最早留言的那个标题。 13.3.3 如何自动录入日期和时间 在留言的时候,需要记录留言的时间。一般的做法是在留言那一刻用程序生成时间, 并同标题和留言一同插入到数据库,这样做有点繁琐。本例将介绍一种快捷的时间录入方 法,即在数据库设置默认时间为系统当前时间,如图 13.4 所示。 图 13.4 建立默认日期函数 上图是 SQL Server 2000 在建立数据库表时的界面,阴影部分日期字段设置为 datetime 类型,被圈住的“默认值”是一个获取当前系统时间的函数 getdate()。这样建成的表保 存后,当有数据录入时,会自动记录数据录入的日期和时间。 13.3.4 保存用户登录信息 在用户登录以后,其他页面要确认用户是否登录,可以把用户名和密码保存到 session 范围中,这样其他页面只要判定 session 中是否有值即可。关键代码如下。 HttpSession session = request.getSession(); userVo.setUsername(request.getParameter("username")); //将获得用户名并设置在 userVo的属性中 userVo.setUserPassword(request.getParameter("password")); //将获得的密码并设置在 userVo属性中 session.setAttribute("sessionUser", userVo); //将值对象保存到session中 13.3.5 如何判断用户是否登录 上面代码将数据保存在 session 中,本页面就要判断 session 中是否有值。如果有值 说明用户已经登录,可以留言;如果没有值说明没有用户登录系统,拒绝留言。判断的关 键代码如下。 257 C H A P T E R 1 3 第 13 章 留 言 本 发表留言 对不起!您还没有登录! 13.3.6 临时中文资源的编码转换 在本实例中,需要对中文的资源文件进行转码。首先,建立一个临时的中文资源文件。 在 src 的目录下建立一个 messages_zh_CN.properties 的文件,并添加如下编码。 sys_messagebook=欢迎使用留言本系统 username=用户名 password=密码 today_messages=查看今日留言 week_messages=查看近一周留言 login=登陆 register=注册 message=留言 message_title=留言本 title_lable=标题 text_lable=内容 submit=提交 reset=重置 临时中文资源文件建立后,需要进行编码转换。转换的步骤如下。 (1)将上面的临时资源文件重命名为 messages.properties 并复制到 D 盘根目录下。 (2)在 DOS 下执行以下命令。 d:\>native2ascii messages.properties message.txt (3)到 D 盘根目录下打开 message.txt 文件如下。 sys_messagebook=\u6b22\u8fce\u4f7f\u7528\u7559\u8a00\u672c\u7cfb\u7edf username=\u7528\u6237\u540d password=\u5bc6\u7801 today_messages=\u67e5\u770b\u4eca\u65e5\u7559\u8a00 week_messages=\u67e5\u770b\u8fd1\u4e00\u5468\u7559\u8a00 login=\u767b\u9646 register=\u6ce8\u518c message=\u7559\u8a00 message_title=\u7559\u8a00\u672c title_lable=\u6807\u9898 text_lable=\u5185\u5bb9 submit=\u63d0\u4ea4 reset=\u91cd\u7f6e (4)将原来的临时中文资源中的内容替换为转换后的编码保存。 258 第 3 篇 典 型 实 例 P A R T 3 13.4 开 发 过 程 13.4.1 数据表结构 本系统后台数据库采用的是 SQL Servser 数据库,系统数据库名称为 tab_message Book13。数据库 tab_messageBook13 中包含 3 张数据表:登录数据表 tb_login、留言数据 表 tb_messsage 和注册数据表 tb_register。下面给出各数据表的表结构。 (1)tb_login(留言信息表)主要用于保存用户的留言信息,其表结构如表13.1 所示。 表 13.1 tb_login 表 字 段 名 称 数 据 类 型 字 段 大 小 允 许 为 空 是 否 主 键 说 明 user_id Int 4 否 是 登录的 ID 号 user_name Varchar 30 是 登录用户名 user_password Varchar 30 是 登录密码 (2)tb_message(回复信息表)主要用于保存留言的回复信息,其表结构如表 13.2 所 示。 表 13.2 tb_message 表 字 段 名 称 数 据 类 型 字 段 大 小 允 许 为 空 是 否 主 键 说 明 user_id Int 10 否 是 回复 ID 号 message_user Varchar 30 是 留言的用户号 message_title Varchar 30 是 回复标题 message_conten t Varchar 80 是 回复内容 message_time Datetime 8 是 回复时间 (3)tb_register(管理员信息表)主要用于保存管理员的名称及密码信息,其表结构 如表 13.3 所示。 表 13.3 tb_register 表 字 段 名 称 数 据 类 型 字 段 大 小 允 许 为 空 是 否 主 键 说 明 register_id Int 10 否 是 用户 ID 号 register_realna me Varchar 30 是 真实姓名 register_sex Char 10 是 性别 register_age Int 4 是 年龄 register_addres s Varchar 30 是 地址 register_tel Varchar 50 是 联系方式 register_date Varchar 30 是 注册日期 259 C H A P T E R 1 3 第 13 章 留 言 本 13.4.2 创建控制器 在本例中使用了控制器。这两个控制器都是多方法控制器,分别是 MessageAction 和 LoginAction。MessageAction 主要处理关于留言的操作,如查看留言信息和发布留言等, 而 LoginAction 主要用于处理用户登录系统,例如用户注册。 1.创建登录控制器 (1)创建登录方法 当用户填写账号和密码并提交数据后,由登录控制器的 login()方法响应处理。该方 法主要是核实用户填写的账号和密码在数据库中是否存在。如果存在该账号和密码则登录 成功,反之,如果不存在该用户则登录失败。关键代码如下。 例程 13-1:光盘\mr\13\MessageBook\src\com\login\LoginAction.java Map model = new HashMap(); HttpSession session = request.getSession(); userVo.setUsername(request.getParameter("username")); //将取得的用户名保存到 userVo值对象中 userVo.setUserPassword(request.getParameter("password")); //将取得的密码保存到 userVo值对象中 List list = service.chacekLogin(userVo); //到数据库中查询是否有匹配userVo对 象的记录 if (list.size() > 0) { session.setAttribute("sessionUser", userVo); //将userVo保存在session范 围内 model.put("message", "您已经成功登录请留言"); return new ModelAndView("message", model); } else { model.put("message", "用户名不存在或密码错误。"); } return new ModelAndView("message", model); 该代码主要通过“list = service.chacekLogin(userVo)”语句来核实数据库中是否 存在该用户,如果存在则 list 中就会有值,如果不存在,list 中就为空。通过判断 list 是否为空来确定登录是否成功。登录成功将该账号和密码保存在 session 中,登录不成功 则提示用户账号和密码错误。chacekLogin 的源代码如下。 public List chacekLogin (LoginUserVo user) { return servicesDao.queryObject("select user_name,user_password from tab_login where user_name= '"+user.getUsername()+"' and user_password='"+user.getUserPassword()+"'"); } 通过 select 语句组合 where 条件查询数据库中是否存在 user 账号和密码,返回 list 集合。  说明 servicesDao 为数据访问对象,相信读者已经知道关于它的用法了,这里不再赘述。 (2)创建注册方法 如果是新用户可以先注册留言本系统。注册功能由该登录控制器的 register()方法完 260 第 3 篇 典 型 实 例 P A R T 3 成。它主要是将注册数据插入到数据库中。关键代码如下。 例程 13-2:光盘\mr\13\MessageBook\src\com\login\LoginAction.java String username = request.getParameter("username"); String password = request.getParameter("passwrod"); ⋯⋯ register.setUsername(username); register.setPassword(password); ⋯⋯ if (check.equals(register.getCheckpassword())){ service.userRegister(register); model.put("message", "register_sueecc"); return new ModelAndView("regSueecc", model); } 该方法主要通过“service.userRegister(register);”语句实现向数据库插入注册数 据的功能,userRegister()方法的程序代码如下。 public void userRegister (Register user) { servicesDao.execute("insert into tab_register (⋯⋯) values ('"⋯⋯"); servicesDao.execute("insert into tab_login (⋯⋯) values ('"⋯⋯"); } 由于注册表和登录表是两张表,所以在插入数据的时候要分别向这两张表中插入 信息。 2.创建留言控制器 这个控制器主要实现发表留言、查看留言和回复留言等功能。每一个功能对应多方法 理器中的一个方法。 (1)发表留言方法 首先触发 sendMessage.jsp 页面,在这个页面里会判断用户是否已经登录。未登录则 提示返回登录。如果已经登录则可以留言。留言的功能由 sendMessages()方法完成,关键 步骤是把主题和留言插入数据库,代码如下。 例程 13-3:光盘\mr\13\MessageBook\src\com\message\MessageAction.java public ModelAndView sendMessages (HttpServletRequest request, HttpServletResponse response ) throws SQLException { HttpSession session=request.getSession(); //获得 session //取出session中登录的用户信息 LoginUserVo vo=(LoginUserVo) session.getAttribute("sessionUser"); Map model = new HashMap(); messageVo.setLogin_user(vo.getUsername()); //设置登录的用户 名 messageVo.setMessge_title(request.getParameter("title")); //获得 标题 messageVo.setMessage_text(request.getParameter("content")); //获得留言 内容 service.addMessage(messageVo); //将留言插入数据库 String title=messageVo.getMessge_title(); 261 C H A P T E R 1 3 第 13 章 留 言 本 String contents=messageVo.getMessage_text(); List list=service.queryItemByTitleAndContents(title, contents); return new ModelAndView("success","list",list); //返回留言 内容 (2)获得留言主题方法 在留言本的主页,会显示当天所有留言的标题。实际上,在留言本启动时,默认地触 发了 MessageAction 的 queryMessageToday 方法,这个方法会根据当天的日期从数据库中 查找当日留言标题,并且标题不重复。关键代码如下。 例程 13-4:光盘\mr\13\MessageBook\src\com\services\Service.java public ModelAndView queryMessageToday (HttpServletRequest request, HttpServletResponse response ) throws SQLException { String today=this.today.getToday(); List list=(List) service.queryMessageTitleByDate(today); //查找当天 的留言标题 return new ModelAndView("message","list",list); } (3)查看留言信息 进入主页后,如图 13.5 所示,单击主页上留言的标题就可以查看详细留言内容,如图 13.6 所示。 图 13.5 查看留言 262 第 3 篇 典 型 实 例 P A R T 3 图 13.6 查看结果 单击主题的时候,JSP 页面会把主题作为传参,然后在 queryMessagesByTitle()的方 法里获得标题,并以此作为查询条件来查询所有留言。关键代码如下。 例程 13-5:光盘\mr\13\MessageBook\src\com\services\Service.java public ModelAndView queryMessagesByTitle(HttpServletRequest request, HttpServletResponse response ) throws SQLException, UnsupportedEncodingException { String t=request.getParameter("title"); //取得标题 String title=new String(t.getBytes("iso-8859-1"),"gb2312"); List list=service.queryContentsByTitle(title); //根据标题 查找留言 return new ModelAndView("writeBack","list",list); } (4)回复留言 单击“回复留言”的时候,会出现如图 13.7 所示的页面。 263 C H A P T E R 1 3 第 13 章 留 言 本 图 13.7 查看主题 图 13.7 所示的标题栏中的主题是只读的,是不可修改的。它是通过下面代码查询并显 示在标题栏中的。首先获取回复的主题,并设置在 request 范围内,然后在 JSP 页面将主 题取出放置到文本框中即可,关键代码如下。 例程 13-6:光盘\mr\13\MessageBook\src\com\message\MessageAction.java public ModelAndView loginRevertMessage(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { String t = request.getParameter("title"); //获得标题 String title = new String(t.getBytes("iso-8859-1"), "gb2312"); //转换字符 集 request.setAttribute("title", title); //将标题设置在 request范围内 //转发到sendMessage.jsp RequestDispatcher rd = request.getRequestDispatcher("sendMessage.jsp"); rd.forward(request, response); return new ModelAndView(""); } 回复留言主要是将回复信息保存到数据表中,这是通过共用 sendMessages()方法实现 的,然后再显示出来,如图 13.8 所示。 264 第 3 篇 典 型 实 例 P A R T 3 图 13.8 回复留言 13.4.3 创建数据访问对象 创建数据访问对象 ServserDao 类。这个类在第 11 章有对它的详细介绍,相信读者已 经清楚了,不太理解的读者可参见第 11 章 4.3 节。关键代码如下。 例程 13-7:光盘\mr\13\MessageBook\src\com\services\ServserDao.java package com.services; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; public class ServserDao { private JdbcTemplate jtl; public JdbcTemplate getJtl(){ return jtl; } public void setJtl(JdbcTemplate jtl) { this.jtl = jtl; } public void execute(String sql) { jtl.execute(sql); } public List queryObject(String sql) { List rs = jtl.queryForList(sql); return rs; } } 13.4.4 创建 service 类 为提高程序的可维护性,将所有对数据库增、删、改、查的操作都封装到 service 类 里,使用的时候只需给 service 类传递参数即可。这里,仍然使用 ServserDao 作为数据库 读写的底层操作,全部代码如下。 265 C H A P T E R 1 3 第 13 章 留 言 本 例程 13-8:光盘\mr\13\MessageBook\src\com\services\Service.java package com.services; import java.util.List; import com.login.Register; import com.login.LoginUserVo; import com.message.MessageVo; public class Service { private ServserDao servicesDao; public ServserDao getServicesDao(){ return servicesDao; } public void setServicesDao(ServserDao servicesDao){ this.servicesDao = servicesDao; } //添加留言 public void addMessage(MessageVo vo){ servicesDao.execute("insert into tab_message (message_title,⋯⋯) values('"+vo.getMessge_title()+"',⋯⋯)"); } //通过标题查询留言 public List queryContentsByTitle (String title) { return servicesDao.queryObject("select * from tab_message where message_title='"+title+"'"); } //通过日期查询留言 public List queryMessageByDate (String date) { return servicesDao.queryObject("select message_title,min(message_time) as message_time from tab_ message where message_time like '%"+date+"%' group by message_title"); } //验证登录用户是否存在 public List chacekLogin (LoginUserVo user) { return servicesDao.queryObject("select user_name,user_password from tab_login where user_name=⋯⋯ and ⋯⋯); } //用户注册 public void userRegister (Register user) { servicesDao.execute("insert into tab_register (register_name ⋯⋯) values ('"+user.getUsername()+"'⋯⋯"); servicesDao.execute("insert into tab_login (user_name,user_password) values ("⋯⋯ "); } //通过标题和内容查询留言 public List queryItemByTitleAndContents (String title,String contents){ return servicesDao.queryObject("select * from tab_message where message_title='"+title+"' and message_ content='"+contents+"'"); } } 266 第 3 篇 典 型 实 例 P A R T 3 13.4.5 创建 Daily 类 这是一个时间类,调用 getToday()可以获得当天的日期;调用 getDate_TheFristOfWeek()和 getDate_TheLasttOfWeek()能获取本周周一的日期和本周 周日的日期。关键代码如下。 例程 13-9:光盘\mr\13\MessageBook\src\com\time\Daily.java package com.time; import java.util.Calendar; public class Daily { private Calendar now=Calendar.getInstance(); private int year = now.get(Calendar.YEAR); private int month = now.get(Calendar.MONTH) + 1; private int day = now.get(Calendar.DAY_OF_MONTH); public String getToday(){ return "%"+this.month+"%"+this.day+"%"+this.year+"%"; return today; } public String getDate_TheFristOfWeek(){ int today=now.get(Calendar.DAY_OF_WEEK); int first_day_of_week=now.get(Calendar.DATE)+2-today; return this.month+" "+first_day_of_week+" "+this.year; } public String getDate_TheLasttOfWeek(){ int today=now.get(Calendar.DAY_OF_WEEK); int last_day_of_week=now.get(Calendar.DATE)+6-today; return this.month+" "+last_day_of_week+" "+this.year; } } 13.4.6 国际化的支持 本实例支持国际化。也就是说,本实例不光有中文界面还可以支持英文界面,如图13.9 所示。 267 C H A P T E R 1 3 第 13 章 留 言 本 图 13.9 英文界面 这种效果是通过建立资源文件获得的。首先,将界面上所用的两种语言分别写在两个 资源文件里,即 messages_zh_CN.properties 和 messages_en_US.properties 资源文件。 如果想支持更多的语言,还可以建立与该语言相关的资源文件。 1.创建中英文资源文件 在 src 文 件 夹 下 面 分 别 创 建 messages_zh_CN.properties 和 messages_en_US.properties 文件,打开并编辑如下。 (1)编辑中文资源文件 messages_zh_CN.properties 部分内容如下。 sys_messagebook=欢迎使用留言本系统 username=用户名 password=密码 today_messages=查看今日留言 week_messages=查看近一周留言 login=登录 register=注册 message=留言 (2)转码后替换原来的中文 按照 13.3.4 节所讲方法转码如下。 例程 13-10:光盘\mr\13\MessageBook\src\messages_zh_CN.properties ys_messagebook=\u6b22\u8fce\u4f7f\u7528\u7559\u8a00\u672c\u7cfb\u7edf username=\u7528\u6237\u540d password=\u5bc6\u7801 today_messages=\u67e5\u770b\u4eca\u65e5\u7559\u8a00 week_messages=\u67e5\u770b\u8fd1\u4e00\u5468\u7559\u8a00 login=\u767b\u9646 register=\u6ce8\u518c message=\u7559\u8a00 268 第 3 篇 典 型 实 例 P A R T 3 (3)转码后保存。 (4)创建英文资源文件 英文资源文件 messages_en_US.properties 部分内容如下。 例程 13-11:光盘\mr\13\MessageBook\src\messages_en_US.properties sys_messagebook=Welcome to use this MessageBook username=user Name password=Password today_messages=Today Message week_messages=Weekly Message login=login register=Register message=Message 2.在 web.xml 加载监听 如果要使用 Spring 的国际化功能,需要在 web.xml 中配置监听器,关键配置如下。 例程 13-12:光盘\mr\13\MessageBook\WebRoot\WEB-INF\web.xml contextConfigLocation /WEB-INF/ApplicationContext.xml org.springframework.web.context.ContextLoaderListener 3.配置标签库 资源文件中的内容需要使用 Spring 的标签来把它取出来,所以要在 web.xml 中配置 Spring 的标签库。关键配置如下。 例程 13-13:光盘\mr\13\MessageBook\WebRoot\WEB-INF\web.xml /spring /WEB-INF/lib/spring.tld 4.配置消息资源 在/WEB-INF/文件夹下创建 ApplicationContext.xml 并配置如下。 例程 13-14:光盘\mr\13\MessageBook\WebRoot\WEB-INF\ApplicationContext.xml messages messageSource(消息资源)提供对国际化消息(il8n)的访问 5.使用 Spring 标签 使用将资源文件内容显示在网页上,代码如下。 例程 13-15:光盘\mr\13\MessageBook\WebRoot\WebRoot\message.jsp
中文界面如图 13.10 所示。 图 13.10 中文界面 英文界面如图 13.11 所示。 图 13.11 英文界面 13.4.7 创建 model_bean.xml 该配置文件里主要配置了连接数据库的信息,包括数据库注册驱动、路径、用户名和 获取资源文件中的 用户名和密码 270 第 3 篇 典 型 实 例 P A R T 3 密码等。关键配置如下。 例程 13-16:光盘\mr\13\MessageBook\WebRoot\WebRoot\WEB-INF\model_bean.xml com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=tab_messageBook13 sa 13.4.8 创建 web_config.xml 主要是 springMVC 的配置和类与类之间的依赖关系,相信读者通过以前的学习可以容 易地理解该配置文件。配置如下。 例程 13-17:光盘\mr\13\MessageBook\WebRoot\WebRoot\WEB-INF\web_config.xml //注册Daily类 配置视图解析器⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯.. 配置视图映像⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯. 271 C H A P T E R 1 3 第 13 章 留 言 本 //配置参数解析 action //指定传参变量 queryMessageToday //指定默认处理方 法 //注册MessageVo 类 //注册 LoginUserVo类 /注册 Register类 //配置MessageAction类 //指定使用 ParameterMethodNameResolver类 //指定依赖 //指定依赖 //指定依赖 //配置 LoginAction类 //指定依赖 //指定依赖 //指定依赖 //指定依赖 272 第 3 篇 典 型 实 例 P A R T 3 //指定依赖 13.4.9 视图组件的实现 视图组件是用于显示网页内容的,本例中共有 6 张 jsp 页分别介绍如下。 1.index.jsp 本 jsp 页只有一行代码,它的目的是系统启动时自动触发默认的 queryMessageToday 方法。该方法能查找出当天所有的留言标题,并显示在 message.jsp 页面上。 例程 13-18:光盘\mr\13\MessageBook\WebRoot\WebRoot\index.jsp 触发后显示的界面如图 13.12 所示。 2.message.jsp 本 jsp 页上已经显示了当天的所有留言的标题,当用户单击标题想查看留言内容时, 留言的 ID 号会以参数的形式传递给留言控制器的 queryMessagesByTitle()方法来查看留 言,所以在本页需要将传递留言的 ID 号,关键代码如下。 例程 13-19:光盘\mr\13\MessageBook\WebRoot\WebRoot\message.jsp ${message.message_title} 273 C H A P T E R 1 3 第 13 章 留 言 本 图 13.12 主页面 其中 queryMessagesByTitle 表示触发留言控制器的 queryMessagesByTitle()方法, id=$ {message.message_id}表示将用户单击留言的 id 号作为参数传递, ${message.message_title}表示显示的留言标题。 3.writeBack.jsp 本 jsp 页需要对标题进行回复,所以要在本页里传递一个回复的标题,关键代码如下。 例程 13-20:光盘\mr\13\MessageBook\WebRoot\WebRoot\writeBack.jsp 其中 loginRevertMessage 表示触发留言控制器的 loginRevertMessage()方法, title=$ 是 {message.message_title}表示传递的参数,该参数是回复的标题, 表示取资源文件中的内容(回复标题)显示 在网页上。 13.5 发布与运行 将该实例所在文件夹下的 defaultroot 文件夹拷贝到 Tomcat 安装路径下的“webapps” 文件夹中,并将其重命名为“MessageBook”,然后重新启动 Tomcat,在浏览器中键入 “http://localhost:8080/MessageBook”即可。 第 14 章 文件上传 网站需要对图片或文档定期进行更新和维护。这就要把 本地的文件上传到服务器上,然后再对上传的文件做出处理。 本章将介绍如何利用 Spring 技术将文件上传到服务器上。本 章的文件上传实例具有以下功能。  文件上传:用于把本地文件上传到服务器指定目录下。  显示上传文件:用于查看上传文件的信息。 服务器端  操作系统:Windows 2003 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5.0 数据库:SQL Server 客户端  浏览器:IE 6.0  分辨率:最佳效果 1024×768 像素  浏览器:IE 5.0,推荐使用 IE 6.0  分辨率:最佳效果 1024×768 像素 14.1 实例运行结果 在浏览器的地址栏中输入实例的测试网址(例如,将留言簿中实 例程序的 WebRoot 文件夹放置在 Tomcat 的 webapps 文件夹下,并修 概 述 开发环境 275 C H A P T E R 1 4 第 14 章 文 件 上 传 改文件夹的名称为“upLoadFile”,就可以输入 http://127.0.0.1: 8080/upLoadFile), 即可进入到文件上传的首页,如图 14.1 所示。 图 14.1 运行首页 14.2 设计与分析 14.2.1 系统分析 在设计该文件上传时,需要明确以下问题。 (1)上传文件是把本地的文件上传到服务器。 (2)需要在 xml 中指定服务器路径,以保存用户上传的文件。 (3)上传的文件不能过大,否则会抛出异常。 文件上传系统流程如图 14.2 所示。 文件过大是否成功 服务器的目录下 文件上传 成功 图 14.2 系统流程图 14.2.2 文件夹及文件架构 文件上传的 Web 文件夹及文件架构,如图 14.3 所示。 276 第 3 篇 典 型 实 例 P A R T 3 图 14.3 文件上传的 Web 文件夹及文件架构图 14.3 技 术 要 点 14.3.1 指定上传文件的路径 在文件上传的时候,程序首先需要知道服务器的路径,然后才能把文件上传到该路径 的目录下。可以把服务器的路径写在 Spring 的配置文件中,供 IoC 容器管理。在需要此路 径的时候,IoC 容器会把它动态地注入到程序中。关键配置如下。 C:\upload//指定服务器路径 14.3.2 指定上传文件的大小 在上传文件的时候,通常要限制上传文件的大小,文件过大不能上传。可以通过下 面的配置来指定上传文件的大小。如果上传的文件超过指定的大小会抛出异常。关键配 置如下。 1000000 277 C H A P T E R 1 4 第 14 章 文 件 上 传 14.3.3 上传文件 实现文件的上传分两步,第一步是获取服务器路径,通过如下代码获得: String path=storePath+System.getProperty("file.separator")+form.getName(); 其中 storePath 是写在配置文件中的路径,它由 IoC 容器管理,当程序执行到本行代 码的时候,IoC 容器会把 storePath 的值动态地注入进来。 第二步是上传文件,通过第一步已经得到了上传文件在服务器上的路径,现在将文件 上传到指定的服务器路径上。可以通过输出流的方式将文件写到服务器的路径里。关键代 码如下。 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( new FileOutputStream(path)); bufferedOutputStream.write(form.getContents()); bufferedOutputStream.close(); 14.4 开 发 过 程 14.4.1 数据表结构 本系统使用的是 SQL Servser 数据库,数据库名称为 DB_upload14,该数据库中表 tab_image 主要用于保存上传的信息,其结构如表 14.1 所示。 表 14.1 image 表 字 段 名 称 数 据 类 型 字 段 大 小 允 许 为 空 是 否 主 键 说 明 id int 10 否 是 上传文件的 ID 号 tpaname varchar 50 否 否 文件名称 path varchar 50 否 否 上传的路径 uptime varchar 50 是 否 上传的时间 tpsize char 6 是 否 文件的大小 14.4.2 创建 bean_cogfig.xml 配置文件 该文件是 Spring 的核心配置文件,主要配置了连接数据库的信息、上传文件的路径和 指定上传文件的大小以及 SpringMVC 的配置等信息。这些配置由 SpringIoC 容器管理。关 键配置如下。 例程 14-1:光盘\mr\14\upLoadFile\WebRoot\WEB-INF\bean_cogfig.xml //配置JdbcTemplate //指定数据源 278 第 3 篇 典 型 实 例 P A R T 3 //配置DAO //指定模板 failure //配置默认处理错误的 页面 //配置错误映像 showMaxUploadSizeError 1000000 //配置上传文件的字节数的最大值 4096 QureyAllUpload //指定请求处理类 upLoadController //指定请求处理类 / .jsp 279 C H A P T E R 1 4 第 14 章 文 件 上 传 //指定DAO success//指定成功页面 d:\upload//指定上传文件的路径 14.4.3 创建简单 FileForm 类 该类是一个简单的 JavaBean 类,name 属性用于保存上传文件的名称,contents 属性 以字节数组的形式保存上传的文件。用户提交上传文件后,文件会自动绑定在 FileForm 类里,并且在控制器中被取出来(详见 14.4.5 节)。关键代码如下。 例程 14-2:光盘\mr\14\upLoadFile\src\com\mr\UpLoad\FileForm.java public class FileForm { private String name; private byte[] contents; public byte[] getContents(){ return contents; } public void setContents(byte[] contents){ this.contents = contents; } public String getName(){ return name; } public void setName(String name) { this.name = name; } } 其中 contents 属性需要通过属性编辑器将文件的二进制代码绑定在字节数组中,关键 代码如下。 binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor()); 14.4.4 创建数据访问对象 UpLoadDao 类 该类主要提供访问数据库的方法。在程序中通过调用该类的 execute()方法能够实现 将上传文件的信息插入到数据库中的功能。该类的源代码如下。 280 第 3 篇 典 型 实 例 P A R T 3 例程 14-3:光盘\mr\14\upLoadFile\ src\com\mr\UpLoad\UpLoadDao.java public class UpLoadDao { private JdbcTemplate jtl; public JdbcTemplate getJtl(){ return jtl; } public void setJtl(JdbcTemplate jtl) { this.jtl = jtl; } public void execute(String sql) { jtl.execute(sql); } public List queryObject(String sql) { List rs = jtl.queryForList(sql); return rs; } public int queryForInt(String sql) { return jtl.queryForInt(sql); } } 14.4.5 创建上传控制器 该控制器主要用于完成上传的功能。将客户端提交的文件上传到服务器指定的路径下。 文件提交后,通过obj 参数获得文件信息;将obj 参数转换为 FileForm 类型,通过FileForm 类提供的 getContents()方法可以取得文件的二进制码;然后使用输出流的方式将二进制 码输入到服务器指定的路径下。关键代码如下。 例程 14-4:光盘\mr\14\upLoadFile\src\com\mr\UpLoad\UpLoadAction.java public class UpLoadAction extends SimpleFormController { private String storePath; private UpLoadDao upLoadDao; protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object obj, BindException bind) throws Exception { FileForm form = (FileForm) obj; String Path = storePath + System.getProperty("file.separator") + form.getName(); //服务器存储上传文件的路径 double number = (double) form.getContents().length/ 1024; DecimalFormat df = new DecimalFormat("0.00"); String size = df.format(number); //将上传文件的信息插入数据库 upLoadDao.execute("insert into image (tpname,path,tpsize) values('" + form.getName() + "','" + Path + "','" + size + "') "); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( new FileOutputStream(Path)); bufferedOutputStream.write(form.getContents()); bufferedOutputStream.close(); 将文件上传 到服务器 281 C H A P T E R 1 4 第 14 章 文 件 上 传 return new ModelAndView(getSuccessView(), "filename", form.getName()); } } } 14.4.6 查询上传信息 文件成功上传后,程序会将上传文件的信息插入到数据库中,然后显示出来,如图14.4 所示。 图 14.4 查询结果 通过下面的控制器可以将上传成功文件的信息读取出来,代码如下。 例程 14-5:光盘\mr\14\upLoadFile\src\com\mr\UpLoad\QureyAllUpload.java public class QureyAllUpload implements Controller { private UpLoadDao upLoadDao; public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { List rs = upLoadDao.queryObject("select * from image"); //查询上传文件的信 息 return new ModelAndView("Display", "filepath", rs); } 14.4.7 创建视图组件 该视图组件主要用于上传文件的主页面(见图 14.1),与一般的表单区别不大,需要 注意的是,定义表单的 enctype 编码属性为 multipart/form-data。关键代码如下。 例程 14-6:光盘\mr\14\upLoadFile\WebRoot\index.jsp <%@ page language="java" pageEncoding="UTF-8"%> 282 第 3 篇 典 型 实 例 P A R T 3
上传文件
上传文件名称
请选择上传的文件
14.5 调试、发布与运行 14.5.1 调试 运行该程序时,如果出现如图 14.5 所示的错误页面,则说明上传文件过大。 图 14.5 错误提示页面 指定编码类 型属性值 283 C H A P T E R 1 4 第 14 章 文 件 上 传 (1)解决方法一 根据异常类型配置异常处理。如图14.5 所示出现了 MaxUploadSizeExceededException 异常,这时可以配置当出现这个异常时,指向 showMaxUploadSizeError JSP 页,并提示错 误。关键配置如下。 failure showMaxUploadSizeError //showMaxUploadSizeError为错误页面 这是一张错误页面,抛出异常后将转向这个页面,它只有一行代码提示:上传文件过 大!请更换后重新上传!如图 14.6 所示。 图 14.6 错误页面 关键代码如下。 <%@ page language="java" pageEncoding="UTF-8"%> 错误页面 您上传的文件过大!请更换后重新上传! (2)解决方法二 配置通用异常处理。不论是何种异常类型,只要有异常产生就会转到 failure 页面将 284 第 3 篇 典 型 实 例 P A R T 3 异常类型和异常信息打印到网页上。 failure failure.jsp 异常处理页面的功能在网页中是打印出异常类型,如图 14.7 所示。 图 14.7 错误页面 关键代码如下。 <%@ page language="java" pageEncoding="UTF-8"%> 异常页面

异常信息: <% ee.getMessage(); %>

<% ee.printStackTrace(new PrintWriter(out)); %> 14.5.2 发布与运行 将该实例的 WebRoot 文件夹拷贝到 Tomcat 安装路径下的“webapps”文件夹中,并将其重 命 名 为 “ upLoadFile ”, 然 后 重 新 启 动 Tomcat , 在 浏 览 器 中 键 入 “http://127.0.0.1:8080/upLoadFile”即可。 第 15 章 数据分页 分页显示结果,是网站常见的基本功能,用户通过它可以快速查 找到所需要的信息。分页可以由很多技术实现,如 JSP 技术或 Struts 框架技术。本章主要采用Spring 框架技术实现数据分页显示功能。利 用 Spring 实现分页的优点如下。  优点一:可以将每页显示的记录数写在 XML 中  优点二 :在 XML 中可以指定需要分页的数据库和表 服务器端  操作系统:Windows 2003 Server。  Web 服务器:Tomcat 5.5。  开发工具包:JDK Version 1.5 以上。  数据库:SQL Server 2000。  浏览器:IE6.0。  分辨率:最佳效果 1024×768 像素。 客户端  浏览器:IE5.0,推荐使用 IE6.0。  分辨率:最佳效果 1024×768 像素。 15.1 实例运行结果 在浏览器的地址栏中输入实例的测试网址(例如,将在分页实例 程序的 WebRoot 文件夹放置在 Tomcat 的 webapps 文件夹下,并更名 概 述 开发环境 286 第 3 篇 典 型 实 例 P A R T 3 为“分页”,就可以输入 http://127.0.0.1:8080/fy/),即可进入到分页的首页,如图 15.1 所示。 图 15.1 在分页首页运行结果 15.2 设计与分析 15.2.1 系统分析 在设计该信息系统时,需要明确以下问题。 (1)单击“上一页”或“下一页”会作为请求发给处理器。 (2)让请求分别触发控制器的两个方法。 15.2.2 工作流程图 数据分页的系统流程如图 15.2 所示。 Controller 上一页 数 据库 下一页 显示上一页或下一页 查询 查询 返回下页 返回上页显示 请求上一页 请求下一页 图 15.2 系统的功能结构图 287 C H A P T E R 1 5 第 15 章 数 据 分 页 15.2.3 文件夹及文件架构 数据分页的 Web 文件夹及文件架构如图 15.3 所示。 图 15.3 Web 文件夹及文件架构图 15.3 技 术 要 点 15.3.1 使用命令控制器 在本例中使用 AbstractCommandController 命令控制器作为中央控制器,它的特点是 能够捆绑用户发出的请求。例如当用户单击“上一页”时,会把表单 action 中的传参捆绑 到该控制器的 handle()方法的 command 参数中,然后在程序中取出判断是哪种请求,并作 出相应的处理。关键代码如下。 public class FyAction extends AbstractCommandController { protected ModelAndView handle(HttpServletRequest arg0, HttpServletResponse arg1, Object command, BindException arg3) throws Exception { action = (Action) command; if (action.getAction().equals("up"))//如果是“上一页”请求 { 此处编写上翻页的代码 } if (action.getAction().equals("next"))//如果是“下一页”请求 { 此处编写下翻页的代码 } } 288 第 3 篇 典 型 实 例 P A R T 3 return null; } 15.3.2 如何获取当前页 在编写请求处理分页代码时,首先应当获得当前页,然后确定本次翻页处理的是第几 页。例如翻页前是第 2 页,如果用户单击“上一页”则本次需要处理的是第 1 页,反之, 如果用户单击“下一页”则本次需要处理的是第 3 页。 使用静态的私有变量可以保存当前页。这样,在翻页操作时静态变量里会自动保存着 翻页前的页数。可以通过下面的代码获得当前页。 int nowPage = FyAction.getCurrenrPage(); // 取得翻页前的页数 15.3.3 如何确定本页需要显示记录的起始位置 使用下面的公式可以确定本页需要显示记录的起始位置。 int begin = nowPage * rows - rows + 1; // 本页的第一条记录 int end = nowPage * rows; //本页的最后一条记录 参数说明如下。 begin:本页显示第一条记录在数据库表中的位置。 end:本页显示最后一条记录在数据库表中的位置。 nowPage:现在正在处理页数。 rows:分页单位(即每页显示的行数)。 15.3.4 显示本页的所有记录 显示本页的所有记录可以使用下面的方法。 List list = queryForList(begin, end); 参数说明如下。 queryForList()为 FyAction 类私有方法,它的作用是从数据库表中查询本页显示的所 有记录。该方法有两个参数 begin 和 end,分别显示记录的起始位置和结束位置。该方法 的源代码如下。 private List queryForList(int begin,int end){ String SQL = "select * from " + page.getTableName() + " where id between '"+begin+"' and '"+end+"'"; return fyDao.queryObject(SQL); 15.3.5 如何将分页单位写在配置文件中 以往在实现分页的时候,都是将分页单位固定写在程序里的,当分页单位发生变化时, 就需要修改程序,然后重新编译执行。这样做很麻烦,现在利用 Spring 框架就可以解决这 个问题。将分页单位写到 XML 中,当分页单位发生变化时,只需要修改配置文件,而不必 修改程序。另外,还可以将要访问的数据库、表名称写在 XML 中,当需要对另一张数据表 分页时,就不必修改程序了。 解决思路:首先写一个简单的 JavaBean,它包含关于分页的一些属性,如分页单位、 289 C H A P T E R 1 5 第 15 章 数 据 分 页 分页的表名称和总页面数;然后,在 Spring 配置文件中配置 JavaBean,并使用 SpringIoC 容器管理它;在程序运行的时候动态地将分页单位和数据库、表名注入到程序中。简单的 JavaBean 的关键配置如下。 5 //定义每页单位为5条记录 tab_userinfo //数据库表名 15.4 开 发 过 程 15.4.1 数据表结构 系统数据库名称为 DB_Fy15,该数据库中的数据表 tab_userinfo 用于保存用户信息的 数据,其表结构如表 15.1 所示。 表 15.1 tb_uerinfo 表 字段名称 数据类型 字段大小 是否主键 说 明 Id Int 4 是 自动编号 Username Varchar 50 用户名 Password Varchar 50 密码 Realname Varchar 50 真实姓名 Age Int 4 年龄 Tel Varchar 50 电话 15.4.2 创建 bean-config.xml 该文件是整个分页程序的核心文件,主要包括两个方面的内容,一方面是分页所使用 的数据的配置,包括使用的数据库以及分页单位等;另一方面是视图转发机制的配置,包 括 HandlerMapping、ViewResolver、Controller 和 View 等。大部分内容是前面介绍的知 识,相信读者能够看懂该配置文件,如果阅读本文件有困难就需要复习以前的内容。此处 不再详细介绍。全部的配置如下。 例程 15-1:光盘\mr\15\fy\WEB-INF\bean_cogfig.xml //指定数据 源 290 第 3 篇 典 型 实 例 P A R T 3 com.microsoft.jdbc.sqlserver.SQLServerDriver //指定数据 库驱动 jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName= DB_Fy15//指定url sa //指定数据库用户名 //数据库密码 //指定使用模板 5 //指定每页显示5行 tab_userinfo //指定需要分页的 表名 // 配置转发 fyAction //指定请求的处理 类 // 配置视图 org.springframework.web.servlet.view.JstlView 291 C H A P T E R 1 5 第 15 章 数 据 分 页 / //指定前缀 .jsp //指定后缀 //配置处理类 com.Action //指定命令的接收 类 //指定DAO //指定分页的信息 15.4.3 创建 PageConfig 类 此类是一个简单的 JavaBean 类,主要属性有:分页的单位、表的名称和页面总数。程 序代码如下。 例程 15-2:光盘\mr\15\fy\src\com\PageConfig.java public class PageConfig { private int rows; private String tableName; private int totalPages; public int getTotalPages(){ return totalPages; } public void setTotalPages(int totalPageCounts){ this.totalPages = totalPageCounts; } public String getTableName(){ return tableName; } public void setTableName(String tableName){ this.tableName = tableName; } public int getRows(){ return rows; } public void setRows(int rows) { 292 第 3 篇 典 型 实 例 P A R T 3 this.rows = rows; } } } 需要说明的是,分页的单位和表的名称会从 Spring IoC 容器中获得,而页面总数会在 启动分页程序后第一次访问数据表计算得到。 15.4.4 创建 Action 类 该类用于配合命令控制器,存储用户提交表单时发出的请求命令,代码如下。 例程 15-3:光盘\mr\15\fy\src\com\Action.java public class Action { private String Action; public String getAction(){ return Action; } public void setAction(String action){ Action = action; } } 15.4.5 FyAction 的程序流程以及模块 该类主要由 3 个模块组成,分别标识为 begin、up 和 next。用 3 个 if 语句控制 3 个 模块的执行。 1.begin 模块 该模块只在程序启动的时候被执行 1 次,以后不再执行。该模块的主要目的是访问数 据表查询表中的记录总数,并通过记录总数计算出共需要分成多少页,例如有 101 记录, 记录单位是 10 条,则共需要分成 11 页显示,然后将总页数通过 PageConfig 类的属性保存。 关键代码如下。 int count = fyDao.queryForInt("select count(*) from "+ tableName + ""); // 查询记录 总数 int totalPages = (count + rows - 1) / rows; // 计算共有 多少页 this.page.setTotalPages(totalPages); // 设置总页 数 currenrPage = 1; // 设置当前页为1 List list = queryForList(1, rows); // 查询本页的记 录 return new ModelAndView("result", "list", list); // 返回查询 的记录 293 C H A P T E R 1 5 第 15 章 数 据 分 页  说明 本程序首先从数据表中查询出记录总数,然后根据分页单位计算出总页数,并把总页 数设置在 PageConfig 类的 totalPages 属性中,最后将当前页设置为 1,查询出第 1 页记 录,此模块以后不再执行。 2.up 模块 该模块的执行受控于用户发出的请求。如果用户单击“上一页”会执行此模块。该模 块的功能是从数据库中查询出需要显示的记录。关键代码如下。 int nowPage = FyAction.getCurrenrPage(); // 取得当前页 nowPage--; // 当前页减1得到本页 if (nowPage <= 0 || nowPage > countPage){ List list = queryForList(1,rows); FyAction.setCurrenrPage(1); return new ModelAndView("result", "list", list); } else { FyAction.setCurrenrPage(nowPage); // 将本页设置为当前页 int begin = nowPage * rows- rows + 1; // 计算本页的第一条记 录 int end = nowPage * rows; // 计算本页的最后一条 记录 List list = queryForList(begin,end); return new ModelAndView("result", "list", list); }  说明 本模块对本页进行判断,如果当前页小于 0 或者当前页大于总页数,则将当前页设置 为第一页并查询第一页记录返回。如果本页不满足判断条件,则计算本页显示记录的起始 位置,然后从数据库中查询记录并返回查结果。 3.next 模块 该模块的功能和 up 模块的功能相似,同样要对本页进行判断,如果本页大于最后一页 或者小于 0 则将查询最后一页的记录并返回。如果本页不满足条件,则查询本页记录。程 序代码如下。 int nowPage = FyAction.getCurrenrPage(); // 取得当前页 nowPage++; // 当前页加1得到本页 if (nowPage <= 0 || nowPage > countPage){ currenrPage=countPage; // 将当前页设置为最后 一页 int begin=countPage*rows-rows+1; int end = countPage * rows; List list = queryForList(begin, end); return new ModelAndView("result", "list", list); 294 第 3 篇 典 型 实 例 P A R T 3 } else { FyAction.setCurrenrPage(nowPage); // 将本页设置为当前页 int begin = nowPage * rows - rows + 1; // 计算本页的第一条记 录 int end = nowPage * rows; // 计算本页的最后一条记录 List list = queryForList(begin, end); return new ModelAndView("result", "list", list); }  说明 对本页进行判断,如果本页大于最后一页或者小于 0 则查询最后一页。 程序的控制流关键代码如下。 例程 15-4:光盘\mr\15\fy\src\com\FyAction.java public class FyAction extends AbstractCommandController { protected ModelAndView handle(HttpServletRequest arg0, HttpServletResponse arg1, Object command, BindException arg3) throws Exception { action = (Action) command; String tableName=page.getTableName(); int rows=page.getRows(); if (action.getAction().equals("begin")){ begin模块: ⋯⋯ } int countPage=page.getTotalPages(); //取得总页数 if (action.getAction().equals("up")){ up模块: ⋯⋯ } if (action.getAction().equals("next")){ next模块: ⋯⋯ } } return null; } } 15.4.6 web.xml 文件的配置 web.xml 文件主要用于配置控制器类的中 Servet 以及指定 bean_cogfig.xml 文件的路 径,主要代码如下。 例程 15-5:光盘\mr\15\fy\WEB-INF\web.xml WebModule1 295 C H A P T E R 1 5 第 15 章 数 据 分 页 dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation dispatcherServlet *.do 15.4.7 视图组件的实现 1.index.jsp 此 JSP 页面的主要作用是触发 FyAction 控制器 begin 模块。关键代码如下。 2.result.jsp 该页面是分页显示的结果页,通过单击“上一页”或“下一页”触发 up 模块和 next 模块。关键代码如下。 例程 15-6:光盘\mr\15\fy\WebRoot\result.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 查询结果 296 第 3 篇 典 型 实 例 P A R T 3
用户名 密码 姓名 年龄 电话
${user.username} ${user.password} ${user.realname} ${user.age} ${user.tel}
上一页 下一页
 说明 其中是 jstl 标签,它的作用是取出控制 器传过来的数据。items 表示集合,var 表示集合中成员变量。关于这个标签的详细用法, 读者可自行查找有关资料。 15.5 发布与运行 将该实例的 WebRoot 文件夹拷贝到 Tomcat 安装路径下的“webapps”文件夹中,并将 其重命名为“fy”,然后重新启动 Tomcat,在浏览器中键入“http://127.0.0.1:8080/fy” 查看实例运行结果。 第 16 章 企业通信软件 本章将使用 Spring+Swing 实现一个企业通信软件,它是以 Java 语言为基础的网络即时通信工具,包括服务器端和客户端,可以在企 业内部网络运行。该即时通信软件具有以下功能。  用户登录:用于用户的登录  用户注册:用于用户的注册  发送消息:用于给好友发送消息  接收信息:用于接收好友的信息  查找好友:用于查找好友  添加好友:用于对好友的添加  删除好友:用于删除好友  安全退出:用于用户退出系统 服务器端  操作系统:Windows 2000 Server  Web 服务器:Tomcat 5.0  开发工具包:JDK Version 1.5.0  数据库:SQL Server 2000  浏览器:IE 6.0  分辨率:最佳效果 1024×768 像素  浏览器:IE 5.0,推荐使用 IE 6.0  分辨率:最佳效果 1024×768 像素 概 述 开发环境 298 第 3 篇 典 型 实 例 P A R T 3 16.1 实例运行界面 企业通信软件主要实现企业内部发送消息、接收消息和管理好友等功能。首先启动服 务器,单击“开始”/“运行”打开运行窗口,在文本框中输入“cmd”后单击“确定”按 钮,将路径转到 C 盘根目录下,输入 java -jar Server.jar,然后双击 Client.jar 运行 客户端,此时将出现一个登录框,如图16.1 所示。输入用户名和密码后即可看到如图 16.2 所示的主界面。 图 16.1 内部通信软件登录界面 图 16.2 内部通信软件主界面 16.2 设计与分析 16.2.1 系统分析 在设计该通信软件时,需要明确以下问题。 (1)用户必须先注册才能登录。 (2)为了满足多用户的请求,必须采用多线程。 (3)服务器与用户端的通信采用套接字 Socket 实现。 299 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 16.2.2 系统流程 通信软件的系统流程如图 16.3 所示。 图 16.3 系统流程图 16.2.3 Spring 配置文件及类的分布 企业通信软件的 Spring 配置文件及类的分布如图 16.4 所示。 图 16.4 Spring 配置文件及类的分布图 300 第 3 篇 典 型 实 例 P A R T 3 16.3 技 术 要 点 16.3.1 系统设计框架 由于 Swing 具有可插拔外观风格,并且是轻型组件,能够使用简化的图形基本元素 在屏幕上描绘自己,使用起来非常简单方便。因此本企业通信软件的所有界面使用 Swing 技术绘制。而该软件的后台所有数据操作都是用 Spring 实现的。这也是本章的讲解重 点。 Spring 进行底层数据的操作非常简单,用户在 Spring 配置文件中配置完后,只须在 需要做底层数据操作的类中获得容器的实例即可。 Spring 的配置文件的关键如下。 例程 16-1:光盘\mr\16\Server\src\applicationContext.xml com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=chat root root 上面的配置代码定义了数据库的连接方式,并注入依赖的属性。 进行数据库操作的类是 DaoSupport.java 文件,其代码如下。 301 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 例程 16-2:光盘\mr\16\Server\src\com\dao\DaoSupport.java package com.dao; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; public class DaoSupport { private JdbcTemplate jtl = null; public JdbcTemplate getJtl(){ return jtl; } public void setJtl(JdbcTemplate jtl) { this.jtl = jtl; } public List getObjectList(String sqlSelect){ List list = jtl.queryForList(sqlSelect); return list; } public void executeSql(String insertSql){ jtl.execute(insertSql); } public void updateSql(String updateSql){ jtl.update(updateSql); } }  注意 由于本书主要是讲解 Spring,所以本章主要讲解的就是如何使用 Spring 进行底层数 据操作,对于 Swing 请读者参考相关书籍。 16.3.2 多线程 由于本系统分为服务器端和客户端两部分,当有多个用户同时请求服务器端的时候, 就需要用到多线程的技术以实现多用户管理。服务器端启动线程的关键代码如下。 例程 16-3:光盘\mr\16\Server\src\com\chat\Server.java public void run() { try { while (true) { String str = in.readLine();// 取得输入字符串 System.out.println("Server str=" + str); ⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯..数据操作 } } } 16.3.3 Socket 编程 服务端与客户端通过套接口 Socket(TCP)连接。在Java 中使用套接口相当简单,Java 302 第 3 篇 典 型 实 例 P A R T 3 API 为处理套接口的通信提供了一个类 java.net.Socket,使得编写网络应用程序相对容 易。服务器采用多线程以满足多用户的请求,通过 Spring 与后台数据库连接,并通过创建 一个 ServerSocket 对象来监听来自客户的连接请求,默认端口为 1414,然后无限循环调 用 accept()方法接收客户程序的连接。其关键代码如下。 例程 16-4:光盘\mr\16\Server\src\com\chat\Server.java public static void main(String args[]) throws Exception { ServerSocket s = new ServerSocket(1414);// 在1414端口创建套接口 System.out.println("Server start.." + s.getInetAddress().getLocalHost().getHostAddress()); try { while (true) { Socket socket = s.accept();// 无限监听客户的请求 System.out.println("Connectino accept:" + socket); try { new ServerThread(socket).start();// 创建新线程 } catch (IOException e) { socket.close(); } } } catch (IOException e) { System.out.println("Server " + e.getMessage()); } finally { s.close(); } } 16.4 开 发 过 程 16.4.1 数据表结构 本系统后台数据库采用的是 SQL Server 2000 数据库,系统数据库名称为 db_database16。数据库中包含两张数据表:用户信息表 tb_icq 和用户好友表 tb_friend。 下面给出数据表的表结构。 (1)tb_icq(用户信息表)主要用于保存用户的信息,其表结构如表 16.1 所示。 表 16.1 tb_icq 表 字 段 名 称 数 据 类 型 允 许 为 空 字 段 大 小 是 否 主 键 说 明 Icqno int 否 4 是 用户号码、自增 Nickname varchar 否 50 用户昵称 Password varchar 否 50 用户密码 Status varchar 否 1 用户状态 Ip varchar 是 50 计算机 IP 地址 303 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 续表 字 段 名 称 数 据 类 型 允 许 为 空 字 段 大 小 是 否 主 键 说 明 Info varchar 是 50 个人信息 Pic int 是 4 所选头像 Sex vrchar 是 50 性别 Email varchar 是 50 E-mail 地址 Place varchar 是 50 所在地 (2)tb_friend(用户好友信息表)主要用于保存留言的回复信息,其表结构如表 16.2 所示。 表 16.2 tb_friend 表 字 段 名 称 数 据 类 型 允 许 为 空 字 段 大 小 是 否 主 键 说 明 Icqno int 否 4 是 用户号码 Friend int 否 4 好友号码 16.4.2 创建 Spring 配置文件 创建 Spring 配置文件 applicationContext.xml,该文件中存放着数据库连接驱动程 序类、登录数据库的用户名/密码、DataSource 的注入等。其关键代码如下。 例程 16-5:光盘\mr\16\Server\src\applicationContext.xml com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=chat root root 304 第 3 篇 典 型 实 例 P A R T 3 对于不同的数据连接来源需求,Spring 提供了 javax.sql.DataSource 注入,更换数 据来源只需在 bean 定义文件中修改配置,而不用修改任何程序。 16.4.3 获取容器实例及底层操作类 1.创建底层操作类 底层操作类 DaoSupport.java 中定义了操作数据库的方法,其中包括 getObjectList()、public void executeSql()和 updateSql()方法,它们都包含一个 String 类型的参数用于接收 SQL 语句。其关键代码如下。 例程 16-6:光盘\mr\16\Server\src\com\dao\DaoSupport.java private JdbcTemplate jtl = null; public JdbcTemplate getJtl(){ return jtl; } public void setJtl(JdbcTemplate jtl) { this.jtl = jtl; } public List getObjectList(String sqlSelect){ List list = jtl.queryForList(sqlSelect); System.out.println("DaoSupport list:" + list); return list; } public void executeSql(String insertSql){ jtl.execute(insertSql); } public void updateSql(String updateSql){ jtl.update(updateSql); } 2.获取容器实例 在需要做底层数据操作的类中获取容器的实例就可以获得和数据库的连接,同时也可 以对数据进行底层的操作。其关键代码如下。 例程 16-7:光盘\mr\16\Server\src\com\chat\Server.java public ServerThread(Socket s) throws IOException { // 实例化容器 context = new ClassPathXmlApplicationContext("applicationContext.xml"); dao = (DaoSupport) context.getBean("daosupport"); socket = s;// 取得传递参数 in = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 创建输入 流 获取容器 305 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket .getOutputStream())), true);// 创建输出流 } 16.4.4 用户登录逻辑处理 用户登录逻辑处理主要是用户在登录框输入 IQ 号和密码后单击“登录”按钮时,客户 端将会通过 Socket 连接服务器端,并通过输入输出流将用户名和密码送到服务器端。当用 户的信息在服务器端验证后,将会返回用户的所有好友信息,然后再调转到主界面中。下 面详细介绍用户登录设计的整个过程。 1.客户端 客户端主要包括用户登录界面、用户注册界面、用户主界面和查找添加好友、删除好 友、接收发送消息等功能。客户端包括的类有 Client(用户登录处理)、Register(用户 注册处理)、MainWin(客户端主类主要用于发送接收消息、连接服务器端、查找添加好友 和删除好友等)。本节主要介绍用户登录的设计过程。 (1)用户登录界面的显示,其关键代码如下。 例程 16-8:光盘\mr\16\Client\src\com\chat\Client.java private void jbInit() throws Exception { contentPane = (JPanel) this.getContentPane(); contentPane.setLayout(null); this.setResizable(false); this.setSize(new Dimension(344, 245)); this.setTitle("Spring IQ"); //contentPane.setBackground(new Color(0xF3, 0xFA, 0xFF)); jPanel1.setBounds(new Rectangle(2, 3, 348, 110)); jPanel1.setLayout(null); jLabel1.setText("请输入你的信息"); jLabel1.setBounds(new Rectangle(5, 7, 103, 18)); jLabel2.setText("你的IQ"); jLabel2.setBounds(new Rectangle(7, 66, 58, 18)); jicq.setBounds(new Rectangle(68, 65, 97, 22)); jLabel3.setText("你的密码"); ⋯⋯//确定位置 jPanel2.setLayout(null); login.setText("登录"); login.setBounds(new Rectangle(5, 27, 79, 29)); login.addMouseListener(new java.awt.event.MouseAdapter(){ //触发鼠标事件 public void mouseClicked(MouseEvent e) { System.out.println(e); login_mouseClicked(e); } }); newuser.setText("新建"); 306 第 3 篇 典 型 实 例 P A R T 3 newuser.setBounds(new Rectangle(118, 28, 79, 29)); newuser.addMouseListener(new java.awt.event.MouseAdapter(){ public void mouseClicked(MouseEvent e) { newuser_mouseClicked(e); System.out.println(e); } }); quit.setText("退出"); quit.setBounds(new Rectangle(228, 26, 79, 29)); quit.addMouseListener(new java.awt.event.MouseAdapter(){ public void mouseClicked(MouseEvent e) { quit_mouseClicked(e); System.out.println(e); } }); ⋯⋯//确定位置 }  注意 上面的代码都是 Swing,请读者参考相关的书籍 其运行结果如图 16.5 所示。 图 16.5 用户登录界面 (2)当用户输入用户名和密码后,单击登录按钮时,将会触发相应鼠标事件,调用相 对应的方法。在这个方法中将会新建一个套接字 Socket,通过服务器的 IP 地址和端口号 获得与服务器端的连接,并通过输入输出流将用户信息传入到服务器端,其关键代码如下。 例程 16-9:光盘\mr\16\Client\src\com\chat\Client.java void login_mouseClicked(MouseEvent e) {// 登录按扭 try { Socket socket = new Socket(server, serport);// 连接服务器 System.out.println(serport); BufferedReader in = new BufferedReader(new InputStreamReader(socket .getInputStream())); PrintWriter out = new PrintWriter(new BufferedWriter( 连接服务器 307 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 new OutputStreamWriter(socket.getOutputStream())), true); out.println("login");// 告诉服务器我要登录 out.println(jicq.getText()); out.println(password.getPassword()); String str = " "; str = in.readLine().trim();// 从服务器读取消息 // 如果失败就告诉出错 if (str.equals("false")) JOptionPane.showMessageDialog(this, "对不起,出错了:-(", "ok", JOptionPane.INFORMATION_MESSAGE); else {// 如果成功就打开主程序 this.dispose(); int g = Integer.parseInt(jicq.getText()); MainWin f2 = new MainWin(g, server, serport); f2.setVisible(true); } } catch (IOException e1) { } } 2.服务器端 Server 类 服务器端主要是监听用户的请求、创建线程和执行数据操作。 (1)在 Server 类的主方法中监听用户的请求并创建线程,其代码如下。 例程 16-10:光盘\mr\16\Server\src\com\chat\Server.java public static void main(String args[]) throws Exception { ServerSocket s = new ServerSocket(1414);// 在1414端口创建套接口 System.out.println("Server start.." + s.getInetAddress().getLocalHost().getHostAddress()); try { while (true) { Socket socket = s.accept();// 无限监听客户的请求 System.out.println("Connectino accept:" + socket); try { new ServerThread(socket).start();// 创建新线程 } catch (IOException e) { socket.close(); } } } catch (IOException e) { System.out.println("Server " + e.getMessage()); } finally { s.close(); } } (2)当线程启动后,Server 类将会接收用户的登录信息执行验证操作,如果登录成 监听用户请求 308 第 3 篇 典 型 实 例 P A R T 3 功,将会更改用户的状态,并查询出用户的所有好友,将好友信息返回到用户端。具体 代码如下。 例程 16-11:光盘\mr\16\Server\src\com\chat\Server.java while (true) { String str = in.readLine();// 取得输入字符串 System.out.println("Server str=" + str); if (str.equals("end")) break;// 如果是结束就关闭连接 else if (str.equals("login")) {// 如果是登录 try { int icqno = Integer.parseInt(in.readLine()); String password = in.readLine().trim(); System.out.println("Server icqno " + icqno); System.out.println("Server password " + password); List list = dao .getObjectList("select nickname,password from icq where icqno=" + icqno + " and password='" + password + "'"); // 验证用户的登录信息是否正确 if (list.size() > 0) { out.println("ok"); // 更新用户ip String ip = socket.getInetAddress() .getHostAddress(); dao.updateSql("update icq set ip = '" + ip + "' where icqno = " + icqno); // 更新用户状态 dao .updateSql("update icq set status='1' where icqno = " + icqno); } else { // 告诉用户登录失败 out.println("false"); } } catch (Exception e) { e.printStackTrace(); } socket.close(); } // 登录结束 ⋯⋯//其他代码省略 } 在上面的代码中使用了一个字符串来判断用户的各种操作状态。当用户登录成功后, 将会跳转到用户主界面,如图 16.6 所示。 判断用户操作 309 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 图 16.6 用户主界面 16.4.5 发送、接收消息 本系统是企业通信软件,所以发送、接收消息应该是本系统最重要的功能了。用户可 以使用好友列表里好友的 IP 地址,通过 UDP 协议与其他用户进行信息交流。 用户发送和接收消息的方法都在 MainWin.java 中。 1.创建 UDP 创建 UDP 的代码如下。 例程 16-12:光盘\mr\16\Client\src\com\chat\MainWin.java public void CreatUDP(){ try { sendSocket = new DatagramSocket(); receiveSocket = new DatagramSocket(udpPORT); // System.out.println("udp ok"); } catch (SocketException se) { se.printStackTrace(); System.out.println("false udp"); } } 2.发送消息 (1)发送消息的显示,其代码如下。 例程 16-13:光盘\mr\16\Client\src\com\chat\MainWin.java void sendmessage_mousePressed(MouseEvent e) {// 发送消息菜单 senddata.setLocationRelativeTo(MainWin.this); senddata.setBounds(e.getX() + 50, e.getY() + 50, 400, 280); 310 第 3 篇 典 型 实 例 P A R T 3 index = list.getSelectedIndex(); System.out.println(index); nametext.setText(friendnames.get(index).toString()); icqno.setText(friendjicq.get(index).toString()); theip = friendips.get(index).toString();// ip address System.out.println(theip); senddata.show(); } 其界面运行结果如图 16.7 所示。 图 16.7 发送消息界面 (2)发送消息主方法,其代码如下。 例程 16-14:光盘\mr\16\Client\src\com\chat\MainWin.java void send_mouseClicked(MouseEvent e) {// 发送消息 // *********send message try { String s = sendtext.getText().trim(); // System.out.println(s); byte[] data = s.getBytes(); System.out.println("发送消息 " + theip); theip.trim(); if (theip.equals("null") || theip.equals(" ") || theip.equals("0")){ System.out.println("不在线 " + s); JOptionPane.showMessageDialog(this, ":-(对不起,不在线", "ok", JOptionPane.INFORMATION_MESSAGE); } else { System.out.println("在线 " + s); sendPacket = new DatagramPacket(data, s.length(), InetAddress .getByName(theip), sendPort); sendSocket.send(sendPacket); } } catch (IOException e2) { 311 C H A P T E R 1 6 第 16 章 企 业 通 信 软 件 sendtext.append(sendtext.getText()); e2.printStackTrace(); } senddata.dispose(); // *******end send message } 3.接收消息 (1)接收消息的显示,其代码如下。 例程 16-15:光盘\mr\16\Client\src\com\chat\MainWin.java void getmessage_mousePressed(MouseEvent e) {// 接收消息菜单 String message = received.trim(); index = list.getSelectedIndex(); if (index == index4) getinfo.append(message); else getinfo.append(" "); getfromname.setText(friendnames.get(index).toString().trim()); getfromjicq.setText(friendjicq.get(index).toString().trim()); getdata.show (); } 其界面运行结果如图 16.8 所示。 图 16.8 接收消息界面 (2)接收消息的主方法,其代码如下。 例程 16-16:光盘\mr\16\Client\src\com\chat\MainWin.java void getok_mouseClicked(MouseEvent e) {// 接收消息 getinfo.setText(" "); getdata.dispose(); received = " "; } 至此,本系统中几部分最重要的功能已讲解完,至于其他的几部分功能,如查看好友 312 第 3 篇 典 型 实 例 P A R T 3 资料、注册新用户等流程和用户登录的流程基本一致,读者可参考光盘源代码。 16.5 运 行 本章源代码是 Eclipse 工作目录下的源代码,读者可通过在 Eclipse 中单击工具栏 中的“run”运行。但要注意,要先运行服务器 Server.java 类文件再运行客户端 Client.java 类文件。 第 17 章 在线投票系统 在线投票在网上使用非常普遍。一些网站上经常会见到各种各样 的在线投票,例如一些电影网站会根据用户的投票评选用户最喜欢看 的电影,娱乐网站投票选举年度最受欢迎的明星等。 本章将使用 Hibernate+Spring+MySQL 实现一个在线投票系统。根 据本实例详细介绍Spring集成Hibernate开发在线投票系统的完整过程。 在线投票系统的具体功能如下。  支持后台维护投票信息和投票选项  支持用户自己选择发布单选或者多选的投票信息  支持用户自己选择以哪种图形显示投票结果  支持以图形的方式显示投票结果  防止用户利用同一台机器重复投票  操作系统:Windows 2000 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5.0  数据库:MySQL5.0  浏览器:IE6.0  分辨率:最佳效果 1024×768 像素  开发工具包:Hibernate3.1.3、Spring1.2.7、JDK Version 1.5.0 概 述 开发环境 314 第 3 篇 典 型 实 例 P A R T 3 17.1 系 统 介 绍 “在线投票”对于一般的网民来说并不陌生,因为在一些网站上经 常会见到各种各样的网上调查,而网上调查功能正是在线投票的一种。调查意见的收集和 统计就是通过“在线投票”来实现的。目前,Internet 上的很多网站都具备网上调查功能, 为了使在线投票网站制作得更精致,通常情况下,在显示投票结果时,将采用百分比的形 式;如果网站的调查结果以 3D 饼状图的方式显示,则会使网站更具直观性。运行本实例, 在页面中将以 3D 饼状图和表格的方式显示投票结果。运行结果如图 17.1 所示。 图 17.1 在线投票系统查询页面 17.2 技 术 要 点 该在线投票系统所包含的主要技术为获取用户的投票、防止用户在同一台计算机上重 复投票以及使用图形的方式将用户的投票结果显示出来。本节将重点讲述这三方面的内容。 315 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 17.2.1 防止用户重复投票 在线投票系统中最主要的一个功能就是要禁止用户重复地进行投票,一般这项功能的 实现都是通过在用户第一次提交投票的时候获取用户机器的 IP 地址存入数据库中,当用户 再次使用这台机器投票的时候,系统会弹出错误的消息,告诉用户不能重复投票。 下面介绍防止用户重复投票具体实现过程。 (1)每当用户提交投票时,系统都会先获取用户的本地 IP 地址,并将它与数据库中已 有的 IP 地址比较,看是否已经投过票了,如果能检索出相同的 IP 地址,就给用户弹出错 误信息。如果没有,则提交用户的投票,并执行票数增加的操作。对投票进行操作的关键 代码如下。 例程 17-1:光盘\mr\17\vote\src\com\spring\controller\SubmitController.java TbIp Ip = new TbIp(); TbVoteitem voteitem = new TbVoteitem(); Map model = new HashMap(); String ip = Ip.getMyIP(); List list3 = dao.QueryObject("from TbIp where ip = ’" + ip + "’"); if (list3.size() > 0) { model.put("message", "对不起,您已经投过一次票了!"); return new ModelAndView("error", model); } else { //根据id来查询出该条投票信息 Ip.setIp(ip); dao.InsertOrUpdate(Ip); //执行其它的操作 } (2)从上述代码中可以看出,该代码主要执行用户是否已经投过票了的操作。但是这 项操作最关键的在于获取用户的 IP 地址。获取用户 IP 地址的代码如下。 例程 17-2:光盘\mr\17\vote\src\com\spring\controller\SubmitController.java public String getMyIP() { try { myIPaddress=InetAddress.getLocalHost(); }catch (UnknownHostException e) {} return (myIPaddress.getHostAddress().toString()); } 17.2.2 图形方式显示投票结果 本实例应用的是以 3D 饼状图显示投票系统的查询结果,应用到了开源组件 JfreeChar, 它是一款非常优秀的图形组件,支持以多种图形显示结果,只要开发者在使用时将选项的 内容和数目作为参数传入并选择以何种图形显示即可。下面介绍开源组件 JfreeChar 的具体 应用。 (1)接收投票查询结果参数的关键代码如下。 例程 17-3:光盘\mr\17\vote\src\com\spring\controller\Tryitshow.java DefaultPieDataset data = new DefaultPieDataset(); Iterator it = list.iterator(); while (it.hasNext()) { voteitem = (TbVoteitem) it.next(); String mm = (String) voteitem.getTitle(); int tt = voteitem.getVotenum().intValue(); 316 第 3 篇 典 型 实 例 P A R T 3 data.setValue(mm, tt); } 上述代码的主要功能是将用户传入的参数放入 DefaultPieDataset 中。 (2)生成 3D 饼状图的关键代码如下。 例程 17-4:光盘\mr\17\vote\src\com\spring\controller\Tryitshow.java PiePlot3D plot = new PiePlot3D(data); plot.setToolTipGenerator(new StandardPieToolTipGenerator()); font = new Font("黑体", Font.CENTER_BASELINE, 20); //此处设置统计图标题的字体和大小 JFreeChart chart = new JFreeChart("", JFreeChart.DEFAULT_TITLE_FONT, plot, true); TextTitle tt = new TextTitle(vote.getTitle()); tt.setFont(font); chart.setBackgroundPaint(new java.awt.Color(0xF3, 0xFA,0xFF));//统计图片的底色 chart.setTitle(tt); ChartRenderingInfo info = new ChartRenderingInfo(new StandardEntityCollection()); //把生成的文件写入到临时的目录中 filename = ServletUtilities.saveChartAsPNG(chart, 500, 300,info, session); //选择存储成png格式的文件,当然也可以使用saveChartAsJPEG的方法生成jpg图片 ChartUtilities.writeImageMap(pw, filename, info, true); //把image map 写入到 PrintWriter pw.flush(); (3)生成普通饼状图的关键代码如下。 例程 17-5:光盘\mr\17\vote\src\com\spring\controller\Tryitshow.java if ("pie".equals(vote.getPictype())) { PiePlot plot = new PiePlot(data); plot.setToolTipGenerator(new StandardPieToolTipGenerator()); font = new Font("黑体", Font.CENTER_BASELINE, 20); //此处设置统计图标题的字体和大小 JFreeChart chart = new JFreeChart("", JFreeChart.DEFAULT_TITLE_FONT, plot, true); TextTitle tt = new TextTitle(vote.getTitle()); tt.setFont(font); chart.setBackgroundPaint(new java.awt.Color(0xF3, 0xFA, 0xFF));// 统计图片的底色 chart.setTitle(tt); // 把生成的文件写入到临时的目录中 ChartRenderingInfo info = new ChartRenderingInfo(new StandardEntityCollection()); filename = ServletUtilities.saveChartAsPNG(chart, 500, 300,info, session); // 选择存储成png格式的文件,当然也可以使用saveChartAsJPEG的方法生成jpg图片 ChartUtilities.writeImageMap(pw, filename, info, true); pw.flush(); // 把image map 写入到 PrintWriter } 317 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 17.2.3 获取用户的投票 管理员在后台维护时,可以选择是以单选还是以多选的方式发布投票信息。下面分别 介绍这两种方式。 1.单选状态下的获取 单选状态在网页上通过使用单选按钮 radio 来实现,用户每次只能选择一个选项。显示 单选按钮的关键代码如下。 例程 17-6:光盘\mr\17\vote\WebRoot\vote.jsp 上述代码中可以看出,radio 按钮的属性包括名称(name)、值(value)。在此种情况下获 取用户投票选项的值的关键代码如下。 String id = (String) request.getParameter(vote_id); // 获取被选中的voteitem的id 2.多选状态下的获取 多选状态在网页上通过使用复选按钮来实现,用户可以选择一个或者多个选项,显示 复选按钮的关键代码如下。 例程 17-7:光盘\mr\17\vote\WebRoot\vote.jsp 在此种状态下获取用户投票选项的值的关键代码如下。 String a[] = request.getParameterValues(vote_id); // 获得所有被选中的选项 17.3 系 统 设 计 17.3.1 系统功能结构 系统的功能结构图如图 17.2 所示。 图 17.2 在线投票系统功能结构图 318 第 3 篇 典 型 实 例 P A R T 3 17.3.2 数据库设计 1.数据表概要说明 为使读者对本系统的数据库中的数据表有一个更清晰的认识,笔者将数据库做了一个 截图,如图 17.3 所示。该数据表树型结构图包含系统所有数据表。 图 17.3 数据库结构图 2.主要数据表的结构 结合本系统的实际情况和用户的需求分析,在线投票系统的数据库 db_database17 包含 了 4 张数据表,其中有 tb_ip(投票 IP 记录表)、tb_user 表(用户信息表)、tb_vote 表(投 票信息表)、tb_voteitem 表(投票选项表)。 如表 17.1 所示保存用户信息如下。 表 17.1 tb_user 表 字 段 名 称 数 据 类 型 字 段 大 小 是 否 主 键 说 明 id int 11 主键 自动流水号码 username varchar 20 用户名 password varchar 20 密码 isCheck varchar 20 是否已投票 如表 17.2 所示保存投票信息如下。 表 17.2 tb_vote 表 字 段 名 称 数 据 类 型 字 段 大 小 是 否 主 键 说 明 id int 11 主键 自动流水号码 name varchar 50 投票标识 title varchar 1000 投票题目 votetype varchar 20 选择类型 pictype varchar 20 选择的显示类型 如表 17.3 所示保存投票选项如下。 表 17.3 tb_voteitem 表 字 段 名 称 数 据 类 型 字 段 大 小 是 否 主 键 说 明 id int 11 主键 流水号码 title varchar 1000 投票选项 votenum int 11 票数 vote_id int 11 投票信息 id 319 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 如表 17.4 所示保存投票 IP 记录如下。 表 17.4 tb_ip 表 字 段 名 称 数 据 类 型 字 段 大 小 是 否 主 键 说 明 id int 11 主键 流水号码 ip Varchar 50 用户 ip 17.4 系统总体架构设计 17.4.1 W eb 文件架构设计 Web 文件夹及文件架构如图 17.4 所示。 图 17.4 文件夹及文件架构 17.4.2 类文件架构设计 在线投票系统主要是应用到类中的代码实现的,下面给出类文件架构,如图 17.5 所示。 图 17.5 类文件架构图 17.5 开 发 过 程 17.5.1 系统文件配置 1.DispatcherServlet 控制器配置 在整个 Web MVC 架构中,开发者并不是直接连接到所需要的资源,而是必须先连接 320 第 3 篇 典 型 实 例 P A R T 3 到前端控制器(Front controller),由前端控制器判断开发者的请求要分派给哪一个控制器 对象来处理请求。在 Spring 的 Web MVC 框架中,担任前端控制器角色的是 Dispatcher Servlet,它负责将客户的请求分配给控制对象。和任何 Servlet 一样,Dispatcher Servlet 必 须在 Web 应用系统的 web.xml 文件中进行配置,具体步骤如下。 (1)定义了一个 DispatcherServlet 的实例,其名称为 dispatcherServlet,关键代码如下。 例程 17-8:光盘\mr\17\vote\WebRoot\WEB-INF\web.xml dispatcherServlet org.springframework.web.servlet.DispatcherServlet (2)设置所有以*.htm 结尾的 URL 连接请求都会由 dispatcher servlet 控制器处理,关键 代码如下。 dispatcherServlet *.htm (3)根据应用系统的各种功能,分解出多个 XML 文件配置。在 Servlet 上下文中设置 contextConfigLocation 参数以保证这些配置文件都被载入,关键代码如下。 contextConfigLocation /WEB-INF/View-Config.xml,/WEB-INF/Data-Config.xml,/WEB-INF/Controller-Config.xml 2.视图层文件 View-Config.xml 配置 在 Controller 返回 ModelAndView 后,DispatcherServlet 会交由 viewResolver(视图解 析器)对象来解析相关的视图层,因此需要设置一个 viewResolver 实例。本实例使用了 org.springframework.web.servlet.view.InternalResourceViewResolver 解析器,在这个系统中将 以 JSP 作为视图层技术,所以需要设置解析器的“viewClass”属性为“org.springframework. web.servlet.view.JstlView”。另外在这个配置文件中还定义了请求映射类。具体代码如下。 例程 17-9:光盘\mr\17\vote\WebRoot\WEB-INF\View-Config.xml org.springframework.web.servlet.view.JstlView / .jsp 321 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 3.持久层文件 Data_Config.xml 配置 整个系统的数据持久层的控制是通过 Spring 中集成 Hibernate 框架来完成的。在 Spring 框架与 Hibernate 整合的时候,Hibernate 的连接、事务管理是由建立 SessionFactory 开始的, SessionFactory 在应用程序中通常只需存在一个实例,因而 SessionFactory 底层的 DataSource 可以使用 Spring 中的 IoC 注入。 定义一个数据源的 Bean 名字为 dataSource,它是 Spring 的 JDBC 框架中 DriverManager DataSource 类的一个实例,用于设置连接数据库的不同参数,包括驱动、URL 地址等。具 体代码如下。 例程 17-10:光盘\mr\17\vote\WebRoot\WEB-INF\Data_Config.xml com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/tp?useUnicode=true&characterEncoding=GBK root root 定义一个 sessionFactory 的 Hibernate 会话 Bean,它在应用程序中通常只存在一个实例, 也就是类 springframework.orm.hibernate3.LocalSessionFactoryBean 中一个类,在这个实例中 直接将 Datasource 注入其中。对于 Hibernate 的对象与关联的数据表之间的映射,则指定在 “mappingDirectoryLocations”中,接着定义一个名字为 dao 的 bean,并为它注入 session Factory,具体设置代码如下。 classpath:com/spring/model true 322 第 3 篇 典 型 实 例 P A R T 3 PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED PROPAGATION_REQUIRED 17.5.2 数据库操作的核心类设计 数据库操作的核心类主要是指 DAOSupport 类,它继承了类 HibernateDaoSupport,这 样可以省去一些管理 SessionFactory、HibernateTemplate 资源的工作,只要在配置文件中注 入上面的 SessionFactory 的实例即可。 在这个类中定义了不同的操作方法,系统的控制器也就是通过调用类中的相应方法来 完成数据进行写入、读取操作的。DAOSupport 类的具体编写过程如下。 (1)在包 com.hibernate.dao 下定义一个 HibernateDaoSupport 的子类并导入相应的类文 件,代码如下。 例程 17-11:光盘\mr\17\vote\src\com\spring\dao\DAOSupport.java package com.spring.dao; import java.util.List; import org.hibernate.Query; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class DAOSupport extends HibernateDaoSupport 323 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 (2)定义一个存盘方法 InsertOrUpdate()和一个删除方法 delete()用于存储和删除某一数 据,它们的参数都为 Object 类型,代码如下。 public boolean InsertOrUpdate(Object obj) { getHibernateTemplate().saveOrUpdate(obj); return true; } public boolean delete(Object obj){ getHibernateTemplate().delete(obj); return true; } (3)定义一个查询方法 QueryObject()用于查询满足条件的实体对象,它的参数 QueryStr 为字符串类型,返回类型为 List 类表对象,代码如下。 public List QueryObject(String QueryStr) { return getHibernateTemplate().find(QueryStr); } (4)定义查询方法用于查询满足条件的对象,返回类型也为 List 列表对象,代码如下。 public List QueryObjectFromSql(String SqlStr) { Query query = this.getSession().createSQLQuery(SqlStr); return query.list(); } 17.5.3 系统登录模块设计 登录模块是每个 Web 应用中都必须有的模块,在本系统中用户登录后进入后台管理 页面。在系统登录页面中,系统管理人员可以通过输入正确的管理员名称和密码进入系 统。用户没有输入管理员名称或密码时,系统会禁止访问系统功能,并给出提示信息。 在系统的首页中单击“投票管理”的超链接后进入管理员登录页面,运行结果如图 17.6 所示。 图 17.6 管理员登录页面 324 第 3 篇 典 型 实 例 P A R T 3 1.编写 Hibernate 实体类及映射文件 登录模块在数据库中对应 tb_user 表,其中包含 id、username、password 和 isCheck 字 段,在编写 Hibernate 实体类时要定义对应 tbuser 表的 4 个属性和 get/set 方法。在登录模块 中定义 tb_user 表的实体类 TbUser.java 程序代码如下。 例程 17-12:光盘\mr\17\vote\src\com\hibernate\model\Tbuser.java package com.spring.model; public class Tbuser extends AbstractTbuser implements java.io.Serializable { public Tbuser() { } public Tbuser(Integer id, String username, String password, String isCheck) { super(id, username, password, isCheck); } } Tbuser 类父类为 AbstractTbUser.java,该类实现对表中字段映射实现 get()/set()的方法, 具体代码如下。 例程 17-13:光盘\mr\17\vote\src\com\hibernate\model\AbstractTbUser.java package com.spring.model; public abstract class AbstractTbUser implements java.io.Serializable { private Integer id; private String username; private String password; private String isCheck; public AbstractTbuser() { } public AbstractTbuser(Integer id, String username, String password, String isCheck) { this.id = id; this.username = username; this.password = password; this.isCheck = isCheck; } public Integer getId() { return this.id; } public void setId(Integer id) { this.id = id; } //部分代码省略 } 定义 TbUser 实体类后,需要让 Hibernate 知道 TbUser.java 实体类的存在。这样就必须 编写实体类的映射文件来定义实体类与数据库的对应关系。映射文件以实体类的名称(如 TbUser)为前缀,以“.hbm.xml”为后缀定义文件名称,实体类 TbUser 对应的映射文件为 “TbUser.hbm.xml”,具体代码如下。 325 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 2.页面设计 index.jsp 是登录页面文件。页面中有一个 Form 表单,包含用户名和密码两个文本框和 【登录】按钮,在登录成功后页面会跳转到登录成功的页面。因为使用了标签${message}, 所以需要加载标签库,代码如下。 例程 17-14:光盘\mr\17\vote\WebRoot\index.jsp <%@ page language="java" pageEncoding="GB2312"%> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%> 如果成功加载标签库,在 index.jsp 中,${message}标签可以判断用户的登录信息是否 正确,如果用户的登录信息错误,则会在登录页面上显示“用户名或密码错误”。判断用户 登录信息的代码如下。

用户名:

密  码:

326 第 3 篇 典 型 实 例 P A R T 3      

${message }

3.控制器设计 在登录页面提交登录信息时,LoginController 控制器处理登录的业务逻辑。Login Controller 控制器被 Spring IoC 注入 dao 属性来操作数据库。在接收到登录请求后,会使用 请求中的用户名和密码到数据库中去寻找用户记录,如果用户存在并且密码属实,会返回 登录成功页面 manager.jsp,并将所有的投票信息查询出来显示在页面上。如果登录失败, 则会跳转到登录页面让用户重新登录,并在页面上显示出错误的信息。控制器 Login Controller 程序代码如下。 例程 17-15:光盘\mr\17\vote\src\com\spring\controller\LoginController.java package com.spring.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.SimpleFormController; import com.spring.dao.DAOSupport; import com.spring.model.TbUser; public class LoginController extends SimpleFormController { private DAOSupport dao; public LoginController() { setCommandClass(TbUser.class); } protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { ToChinese toChinese = new ToChinese(); Map model = new HashMap(); TbUser user = (TbUser) command; List list = dao.QueryObject("from TbUser where username=’" + user.getUsername() + "’ and password=’" + user.getPassword() + "’"); 327 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 if (list.size() > 0) { user = (TbUser) list.get(0); List list1 = dao.QueryObject("from TbVote"); if (list1.size() != 0) { return new ModelAndView("manage", "votes", toChinese .toChinese(list1)); } else { model.put("message", "投票信息为空,请添加投票信息!"); return new ModelAndView(getSuccessView(), model); } } else { return new ModelAndView(getFormView(), "message", "用户名或密码错误!"); } } public DAOSupport getDao() { return dao; } public void setDao(DAOSupport dao) { this.dao = dao; } }  注意 这里 LoginController 继承的是 SimpleFormController,有关它的说明请读者参考前面 的基础部分。 4.xml 信息配置 LoginController 是处理简单登录逻辑的控制器,它的代码很少,容易理解。但是它并 不能直接接收到客户的登录请求。要在 Spring 的 xml 配置文件中将 LoginController 控制器 定义一个 Bean,并且注入 dao 属性依赖的 DAOProxy 代理 Bean,然后在“SimpleUrlHandler Mapping”中映射访问 LoginController 控制器的 URL 路径。这样才可以接收到登录请求, 并且通过 dao 属性在数据库中验证用户信息。在本项目中登录模块的配置信息定义在 “Controller_Config.xml”配置文件中,关键代码如下。 例程 17-16:光盘\mr\17\vote\WebRoot\WEB-INF\Controller_Config.xml manage index 328 第 3 篇 典 型 实 例 P A R T 3 View_Config.xml”文件中定义了“视图解析器”和上面提到的“SimpleUrlHandler Mapping”控制器映射,包含控制器映射关系的关键代码如下。 例程 17-17:光盘\mr\17\vote\WebRoot\WEB-INF\View_Config.xml ⋯⋯ //其他控制器映射 login ⋯⋯ //其他控制器映射 上述代码将 URL“login.htm”的访问映射到名为 login 的控制器上(即前面提到的 LoginController 控制器),在登录页面将登录请求提交到“login.htm”时,会将登录请求发 送给 LoginController 控制器,控制器会验证登录信息以确定返回登录成功信息或错误信息。 17.5.4 投票信息维护 投票系统的整个后台非常的简单,主要分为两部分,一部分为投票信息的维护,另一 部分为投票选项的维护。用户登录成功之后将会自动跳转到 manage.jsp 页面,这个页面将 所有的投票信息都显示出来。它的运行效果如图 17.7 所示。 图 17.7 投票信息维护页面 下面介绍投票信息维护具体编写过程。 1.编写 Hibernate 实体类及实体类对应的映射文件 (1)Hibernate 实体类的编写 用于处理投票信息数据存盘的 Hibernate 实体类是 TbVote.,这个类中只是 3 个构造方 329 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 法,它继承自 AbstractVote 类。该类定义了一些基本类型的属性,及用于设置这些属性的 get()/set()方法。AbstractVote 类的关键代码如下。 例程 17-18:光盘\mr\17\vote\src\com\hibernate\model\AbstractTbVote.java package com.spring.model; import java.util.HashSet; import java.util.Set; public abstract class AbstractTbVote implements java.io.Serializable { private Integer id; private String name; private String title; private String votetype; private String pictype; private Set tbVoteitems = new HashSet(0); public AbstractTbVote() { } public AbstractTbVote(Integer id, String name, String title, String votetype, String pictype) { this.id = id; this.name = name; this.title = title; this.votetype = votetype; this.pictype = pictype; } public AbstractTbVote(Integer id, String name, String title, String votetype, String pictype, Set tbVoteitems) { this.id = id; this.name = name; this.title = title; this.votetype = votetype; this.pictype = pictype; this.tbVoteitems = tbVoteitems; } public Integer getId() { return this.id; } public void setId(Integer id) { this.id = id; } //此处省略了部分get()/set()方法 } (2)Hibernate 映射文件的编写 在实体类的配置文件 Vote.hbm.xml 中定义了与数据表 tb_vote 的一一对应关系,同时 还定义了投票信息与投票选项一对多的关系。关键代码如下。 例程 17-19:光盘\mr\17\vote\src\com\hibernate\model\Vote.hbm.xml 330 第 3 篇 典 型 实 例 P A R T 3  注意 如果数据库中的 id 字段是 int 类型的并且是自增的,那么要在上面的配置文件中将 的 class 的值设为 native,否则在添加数据的时候可能会提示手动设置标识。 2.页面设计 页面文件 manage.jsp 主要完成投票信息的显示、删除及发布投票、修改投票、添加投 票选项的按钮。下面介绍 manage.jsp 具体编写过程。 (1)在页面中通过接受查询出来的数据集合,通过循环标签显示所有的投 票信息。每一条投票信息的后边都会有“修改投票”,“删除投票”及“添加选项”的超链 接。关键代码如下。 例程 17-20:光盘\mr\17\vote\WebRoot\manage.jsp

投票管理系统

331 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统
投票id
投票标识
投票名称
投票类型
图形类型

操作类型

${vote.id}
${vote.name}
${vote.title}
${vote.votetype}
${vote.pictype}
>修改投票 < 删除投票 添加选项

发布投票

(2)当用户进入系统维护首页面,单击“发布投票”超链接时,将会进入发布投票的 页面 vote_add.jsp。用户填完投票信息后单击【保存】按钮,将会执行存盘的操作。运行的 效果如图 17.8 所示。 图 17.8 发布投票页面 (3)当用户进入系统维护首页面,单击“修改投票”超链接时,将进入修改投票的页 面 vote_update.jsp。该页面中将会把该条投票信息显示出来,供用户修改。当用户修改完成 后单击【保存】按钮,将会执行存盘操作。关键代码代码如下。 例程 17-21:光盘\mr\17\vote\WebRoot\vote_update.jsp
332 第 3 篇 典 型 实 例 P A R T 3
投票标识 *
投票名称   *
投票类型
 
 
图形类型
 
 
 
当用户进入系统维护首页面时,选择一组要修改的数据,单击“修改投票”超链接后, 运行效果如图 17.9 所示。 3.控制器设计 (1)用户填写完相应的数据信息之后进行单击存盘操作。这一过程是通过控制器类 VoteController 完成的,它的设计过程如下。 333 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 图 17.9 修改投票页面 在包 com.spring.controller 下定义一个 VoteControl 类,然后导入需要的相应的类文 件,定义属性 DAOSupport 用于执行数据操作方法。编写 DAOSupport 的 get()/set()方法, 它实现 AbstractController 中的 handleRequest 方法。在这个方法中,首先通过 request 获 取参数 action 的值是否等于 add,如果是就执行插入的操作,接着通过参数 request 获得 客户端传递过来的表单中的投票的信息,然后再通过公共类 dao 中的 InsertOrUpdate 方 法执行存盘操作。同时通过 dao 中的查询方法 QueryObject()查询出所有的投票信息。关 键代码如下。 例程 17-22:光盘\mr\17\vote\src\com\spring\controller\VoteController.java ToChinese toChinese = new ToChinese(); Map model = new HashMap(); TbVote vote = newTb Vote(); vote.setName(request.getParameter("name")); vote.setTitle(request.getParameter("title")); vote.setVotetype(request.getParameter("votetype")); vote.setPictype(request.getParameter("pictype")); System.out.println("AddController " + vote.getName()); String action = request.getParameter("action"); System.out.println("AddController " + action); if (action.equals("add")) { if (dao.InsertOrUpdate(vote)) { List list = dao.QueryObject("from Vote"); return new ModelAndView("manage","votes",toChinese.toChinese(list)); } else { model.put("message", "添加投票信息失败!"); return new ModelAndView("vote_add", model); } } 334 第 3 篇 典 型 实 例 P A R T 3 定义一个 Map 实例对象 model,如果插入数据正常,将会进入 manage.jsp;如果存盘 失败,将失败信息存入对象 model 中并传回 vote_add.jsp 页面。 (2)当用户单击“删除投票”超链接时,控制器将会判断参数 action 的值是否为 delete, 如果是将会执行删除的操作。关键代码如下。 int id = Integer.parseInt(request.getParameter("id")); System.out.println(id); vote.setId(id); vote.setName("1"); vote.setTitle("1"); vote.setVotetype("1"); vote.setPictype("1"); if(dao.QueryObject("from Voteitem where vote_id="+id).size()==0){ if (dao.delete(vote)) { List list = dao.QueryObject("from Vote"); return new ModelAndView("manage","votes",toChinese.toChinese(list)); }else{ return null; } } (3)用户单击“修改投票”超链接时,将会调用另外一个控制器 UpdateVoteController, 它会根据 ID 号查询出用户所要修改的投票信息。当用户修改完成后,单击【保存】按钮, VoteController 控制器将会根据所获取的参数 action 的值执行修改操作,关键代码如下。 例程 17-23:光盘\mr\17\vote\src\com\spring\controller\UpdateVoteController.java if (action.equals("update")) { int id = Integer.parseInt(request.getParameter("id")); System.out.println(id); vote.setId(id); if (dao.InsertOrUpdate(vote)) { List list = dao.QueryObject("from Vote"); return new ModelAndView("manage","votes",toChinese.toChinese(list)); } else { model.put("message", "修改投票信息失败!"); return new ModelAndView("vote_update", model); } } 4.Controller_Config.xml 文件配置 在将一个请求分发给某一个的业务控制器时,DispatcherServlet 需要取得处理器将请求 分配到对应的控制器组件。具体在 Controller_Config.xml 配置文件的代码如下。 例程 17-24:光盘\mr\17\vote\WebRoot\WEB-INF\Controller_Config.xml 335 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 在 Spring 中默认控制器映射是 BeanNameRulHandlerMaping,为了能够访问到编写的 控制器类,需要在 view_Config.xml 配置文件中的进行配置,具体代码如下。 例程 17-25:光盘\mr\17\vote\WebRoot\WEB-INF\view_Config.xml addvoteController updatevoteController savevoteController deletevoteController 17.5.5 投票选项模块维护 投票选项模块维护的整个流程和投票信息的流程是一样的。投票选项的主页面的运行 效果如图 17.10 所示。 下面介绍投票选项模块维护的具体编写过程。 1.编写 Hibernate 实体类及实体类对应的映射文件 (1)Hibernate 实体类的编写 用于处理投票信息数据存盘的 Hibernate 实体类是 TbVoteitem,它继承自 Abstract TbVoteitem 类。在这个类定义了一些基本类型的属性,及用于设置这些属性的 get()/set()方 法。AbstractTbVoteitem 类的关键代码如下。 336 第 3 篇 典 型 实 例 P A R T 3 图 17.10 投票选项主页面 例程 17-26:光盘\mr\17\vote\src\com\hibernate\model\AbstractTbVoteitem.java package com.spring.model; private Integer id; private TbVote tbVote; private String title; private Integer votenum; public AbstractTbVoteitem() { } public AbstractTbVoteitem(Integer id, TbVote tbVote, Integer votenum) { this.id = id; this.tbVote = tbVote; this.votenum = votenum; } public AbstractTbVoteitem(Integer id, TbVote tbVote, String title, Integer votenum) { 337 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 this.id = id; this.tbVote = tbVote; this.title = title; this.votenum = votenum; } public Integer getId() { return this.id; } public void setId(Integer id) { this.id = id; } //此处省略了部分get()/set()方法 } (2)Hibernate 映射文件的编写 在实体类的配置文件 TbVoteitem.hbm.xml 中定义了与数据表 tb_voteitem 的一一对应关 系,关键代码如下。 例程 17-27:光盘\mr\17\vote\src\com\hibernate\model\TbVoteitem.hbm.xml  注意 如果数据库中的 id 字段是 int 类型的并且是自增的,那么要在上面的配置文件中将 的 class 的值设为 native,否则在添加数据的时候可能会提示手动设置标识。 2.页面设计 页面文件 voteitem_add.jsp 主要完成投票选项的显示、删除以及修改。下面介绍具体的 编写过程。 (1)在页面中通过接受查询出来的数据集合,可以显示用户所要添加选项的投票信息, 还会显示已经存在的选项,并支持用户继续添加选项。在每一条选项的后边都会有“修改 选项”,“删除选项”超链接,关键代码如下。 338 第 3 篇 典 型 实 例 P A R T 3 例程 17-28:光盘\mr\17\vote\WebRoot\voteitem_add.jsp

添加投票选项

投票信息

投票标识:
${sessionScope.vote.name}
投票名称:
${sessionScope.vote.title}
投票类型:
${sessionScope.vote.votetype}
图形类型:
${sessionScope.vote.pictype}

已经存在的投票选项

选项
票数
操作
${voteitem.title} ${voteitem.votenum} 修改选项 删除选项

请输入投票选项的信息

339 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统
选项:  *
票数:    
 

${message}

(2)在投票选项模块维护的首页面中,选择一组要修改的数据,单击“修改选项”超 链接,将会进入修改投票的页面 voteitem_update.jsp。在该页面中将会把该条投票信息显示 出来,供用户修改。当用户修改完成后,单击【保存】按钮,将会执行存盘操作。关键代 码如下。 例程 17-29:光盘\mr\17\vote\WebRoot\voteitem_update.jsp

投票选项修改页面

请修改投票选项的信息

选项:  
票数:  
    340 第 3 篇 典 型 实 例 P A R T 3
投票选项修改页面的运行效果如图 17.11 所示。 图 17.11 修改投票选项页面 3.控制器设计 (1)用户填写完成相应的数据信息之后进行单击存盘操作。这一过程是通过控制器类 VoteControlleritem 完成的,具体设计过程如下。 在包 com.spring.controller 下定义一个 VoteitemController 类,然后导入需要的相应的类 文件,定义属性 DAOSupport 用于执行数据操作方法。编写 DAOSupport 的 get()/set()方法, 它还实现 AbstractController 中的 handleRequest 方法。在这个方法中首先通过 request 获取 参数 action 的值是否等于 add,如果是,就执行插入的操作,接着通过参数 request 获得客 户端传递过来的表单中的投票的信息,然后再通过公共类 dao 中的 InsertOrUpdate 方法 执行存盘操作。同时通过 dao 中的查询方法 QueryObject 查询出所有的投票信息。关键 代码如下。 例程 17-30:光盘\mr\17\vote\src\com\spring\controller\VoteControlleritem.java ToChinese toChinese = new ToChinese(); Map model = new HashMap(); TbVote vote = new TbVote(); vote.setName(request.getParameter("name")); vote.setTitle(request.getParameter("title")); vote.setVotetype(request.getParameter("votetype")); vote.setPictype(request.getParameter("pictype")); String action = request.getParameter("action"); if (action.equals("add")) { 341 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 if (dao.InsertOrUpdate(vote)) { List list = dao.QueryObject("from TbVote"); return new ModelAndView("manage", "votes", toChinese .toChinese(list)); } else { model.put("message", "添加投票信息失败!"); return new ModelAndView("vote_add", model); } } 定义一个 Map 实例对象 model,如果插入数据正常,将会跳转到 voteitem_add.jsp;如 果插入失败,将失败信息存入 model 中并传回 voteitem_add.jsp 页面。 (2)当用户单击“删除投票”超链接时,控制器将会获取参数 action 的值是否为 delete, 如果是,将会执行删除的操作,关键代码如下。 int id = Integer.parseInt(request.getParameter("id")); vote.setId(id); vote.setName("1"); vote.setTitle("1"); vote.setVotetype("1"); vote.setPictype("1"); if (dao.QueryObject("from TbVoteitem where vote_id=" + id).size() == 0) { if (dao.delete(vote)) { List list = dao.QueryObject("from TbVote"); return new ModelAndView("manage", "votes", toChinese .toChinese(list)); } else { return null; } } else { model.put("message", "请将该题的选项删除后在删除!"); List list = dao.QueryObject("from tb_Vote"); request.setAttribute("votes", list); return new ModelAndView("manage", model); } (3)用户单击“修改选项”超链接时,将会调用另外一个控制器 UpdateVoteitem Controller,它会根据 ID 号查询出用户所要修改的投票选项信息。当用户修改完成后,单 击【保存】按钮,VoteitemController 控制器将会根据所获取的参数 action 的值执行修改操 作,关键代码如下。 例程 17-31:光盘\mr\17\vote\src\com\spring\controller\VoteitemController.java if (action.equals("update")) { voteitem.setTitle(request.getParameter("title")); voteitem.setVotenum(Integer.parseInt(request .getParameter("votenum"))); TbVote vote = new TbVote(); vote.setId(Integer.parseInt(request.getParameter("vote_id"))); 342 第 3 篇 典 型 实 例 P A R T 3 voteitem.setTbVote(vote); voteitem.setId(Integer.parseInt(request.getParameter("id"))); if (dao.InsertOrUpdate(voteitem)) { List list = dao.QueryObject("from TbVoteitem where vote_id=" + vote.getId()); return new ModelAndView("voteitem_add", "voteitems", toChinese.toChinese1(list)); } else { model.put("message", "修改投票信息失败!"); return new ModelAndView("voteitem_update", model); } 4.Controller_Config.xml 文件配置 在将一个请求分发给某一个的业务控制器时,DispatcherServlet 需要取得处理器,并将 请求分配到对应的控制器组件上,具体配置代码如下。 例程 17-32:光盘\mr\17\vote\WebRoot\WEB-INF\Controller_Config.xml 343 C H A P T E R 1 7 第 17 章 在 线 投 票 系 统 在 Spring 中控制器默认的是 BeanNameRulHandlerMaping。为了能够访问到编写的上 面的控制器类,需要配置 view_Config.xml 文件中的配置代码如下。 例程 17-33:光盘\mr\17\vote\WebRoot\WEB-INF\view_Config.xml voteitemController addvoteitemController updatevoteitemController deletevoteitemController savevoteitemController 17.5.6 投票信息显示模块 当管理员在后台将投票信息录入完成后,投票和投票选项的信息都会在前台首页上显 示出来。运行效果如图 17.12 所示。 图 17.12 投票信息首页 下面介绍投票信息显示模块具体编写过程。 1.页面设计 如图 17.12 所示对应的页面名称为 vote.jsp,该页面上显示的信息都是从后台数据库中 提取出来的,用 JSTL 标签在页面上显示。关键代码如下。 344 第 3 篇 典 型 实 例 P A R T 3 例程 17-34:光 盘\mr\17\vote\WebRoot\vote.jsp
<%if(request.getAttribute("votetype").equals("radio")){%> <% }else{%> <% }%>
${votes[0].title}
${voteitems.title} ${voteitems.title}
 
2.控制器设计 当用户在浏览器中输入 http://localhost:8080/vote/view.htm 时,将会向后台发送一个请 求的路径,执行 ViewController 类中相应的方法,关键代码如下。 例程 17-35:光盘\mr\17\vote\src\com\spring\controller\ViewController.java protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { ToChinese toChinese = new ToChinese(); Map model = new HashMap(); List list = dao.QueryObject("from Vote"); if(list.size()==1){ for(int i=0;i viewController
4.Controller-Config.xml 文件配置 在将一个请求分发给某一个的业务控制器时,DispatcherServlet 需要获取处理器将请求 分配到对应的控制器组件,配置代码如下。 例程 17-37:光盘\mr\17\vote\WebRoot\WEB-INF\Controller-Config.xml 17.5.7 投票结果显示模块 1.页面设计 vote_view.jsp 页面用于显示投票的结果,关键代码如下。 例程 17-38:光盘\mr\17\vote\WebRoot\vote_view.jsp

投票结果

<% List list = (List)request.getAttribute("voteitems"); Vote vote = (Vote)request.getAttribute("vote"); String filename = Tryitshow.generatePieChart(list,vote,session, new PrintWriter(out)); 346 第 3 篇 典 型 实 例 P A R T 3 String graphURL = request.getContextPath() + "/servlet/DisplayChart?filename=" + filename; %>
选项
结果
${voteitems.title}
${voteitems.votenum}
vote_view.jsp 页面的运行效果如图 17.1 所示。 2.控制器设计 当用户将选票信息提交后将会执行 SubmitController 控制器类,关键代码如下。 例程 17-39:光盘\mr\17\vote\src\com\spring\controller\SubmitController.java protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { ToChinese toChinese = new ToChinese(); TbVoteitem voteitem = new TbVoteitem(); Map model = new HashMap(); //根据id查询出该条投票信息 String vote_id = (String)request.getParameter("id"); List list2 = dao.QueryObject("from Vote where id="+Integer.parseInt(vote_id)); TbVote vote = (TbVote)toChinese.toChinese(list2).get(0); request.setAttribute("vote", vote); //获得所有被选中的选项 String a[] = request.getParameterValues(vote_id); List list = null; if(a.length>1){ for(int i=0;i submitController
4.Controller-Config.xml 的配置 在 Controller-Config.xml 配置的关键代码如下。 例程 17-41:光盘\mr\17\vote\WebRoot\WEB-INF\View-Config.xml 17.6 部 署 先将数据库文件附加到 Mysql 数据库的 data 目录下,再将该项目放入 Tomcat 目录下 的 webapps 目录下,然后启动 Tomcat,打开浏览器,在地址栏中输入 http://192.168.1.1:8080/ vote/,即可进入该投票系统首页。  注意 该系统要求登录数据库的用户名为 root,密码为空。读者也可更改 WEB-INF 目录 下的 Data-Config.xml 文件,将“username”、“password”的值更改为自己数据库的用 户名和密码。 4 第 4 篇 项目实践 第 1 8 章 校园管理系统 第 1 9 章 企业进销存管理系统 第 20 章 企业门户网站 主要内容  软件的需求分析  数据库设计的方法  S pring 的配置  S prings 的执行流程  在 S pring 框架下使用 H ibernate  在 S pring 框架下使用 S truts  公共类的编写  网站设计架构  运用 S pring+S truts+H ibernate 开发网站的流程 山高月小 水落石出 第 18 章 校园管理系统 随着我国教育体制改革的深入进行,教育系统得到了前所未有的 发展。校园管理正逐步迈向管理信息现代化。但是,我国的校园管理 信息化水平还处在初级阶段,主要表现在对学生的交费管理、学生的 档案管理、职工人事管理还在采用原有的人工管理方式。这就造成工 作效率低、误差大,也造成了人力、物力、财力资源的浪费。而学校 是培养国家栋梁之材的场所,无论是在教育,还是在管理上都应走在 最前列,而其中管理又是学校良好运转的关键。功能强大的校园管理 系统软件管理就能够保证学校运转良好。 目前校园管理系统软件管理的实现方式有应用程序管理和 Web 管 理,而 Web 管理方式的实现多种多样,本章将介绍利用 Spring 与 Hibernate 框架结合技术开发校园管理系统的具体流程。  操作系统:Windows 2000 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5.0  数据库:MsSql2000 以及 Sp3 升级包  浏览器:IE6.0  分辨率:最佳效果 1024×768 像素  开发工具包:Hibernate3.1.3、Spring2.0.1、JDK Version 1.5.0 概 述 开发环境 349 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 18.1 需 求 分 析 随着教育体制的改革的深入,学校现有的教学管理和学生收费管 理机制越来越不适应学校发展的要求。信息技术的高速发展,信息的获取、传输、处理和 应用能力将作为人们最基本的能力和文化水平的标志。以计算机技术、微电子技术和通信 技术为特征的现代信息技术,已在社会各个领域中得到广泛应用,正在改变着人们的生产 与生活方式、工作与学习方式。基于上面的因素,学校管理者应当在学生信息管理、学生 成绩管理、图书馆管理采用先进的计算机技术,提高管理水平。 18.2 系 统 设 计 18.2.1 项目规划 校园管理系统是一个典型的数据库开发应用程序,由系统登录模块、代码维护模块、档案管 理模块、成绩管理模块、教职工管模块、图书馆管理模块等部分组成,规划系统功能模块如下。  系统登录模块 该模块主要负责用户登录功能,当用户没有登录系统时,只能使用部分系统功能。  代码维护模块 该模块负责系统中所有代码的维护工作,例如班级代码、年级代码、专业代码等,它 们以代码代表各种系统信息。  档案管理模块 该模块负责学生档案和教师档案的管理工作,例如学生入校登记和基本信息等。  成绩管理模块 该模块主要管理学生在校的学习成绩和安排考试功能。  教职工管模块 该模块主要负责教师的任课和班主任管理功能。  图书馆管理模块 该模块管理学校的图书馆,负责图书的登记、借阅等工作管理。 18.2.2 功能结构分析 系统前台功能结构如图 18.1 所示。 专 业 代 码 维 护 年 级 代 码 维 护 班 级 代 码 维 护 学 科 代 码 维 护 代码维护 图 书 登 记 图 书 维 护 图 书 借 阅 图 书 归 还 借 阅 查 询 图书馆管理 校园管理系统 版 权 声 明 技 术 支 持 系统信息 用 户 查 询 用 户 添 加 用户维护 学 生 基 本 信 息 学 生 入 校 登 记 学 生 信 息 维 护 学 生 登 记 查 询 教 师 基 本 信 息 教 师 信 息 维 护 教 职 任 课 任 班 主 任 任 课 教 师 查 询 班 主 任 查 询 教职工管理教职工管理 成 绩 录 入 成 绩 查 询 班 级 成 绩 统 计 年 级 成 绩 统 计 成绩管理 图 18.1 校园管理系统功能结构图 350 第 4 篇 项 目 实 践 P A R T 4 18.3 数据库设计 18.3.1 数据表概要说明 为使读者对本系统的数据库中数据表有一个更清晰的认识,笔者设计了一个数据表树 型结构图,如图 18.2 所示,该数据表树型结构图包含系统所有数据表。 图 18.2 数据表树型结构图 18.3.2 主要数据表的结构 结合本系统的实际情况和用户的需求分析,校园管理系统数据库 db_school 包含了 13 张不同的数据表,主要为 book_borrow_info 表(图书借阅情况信息表)、book_reginster 表(图书登记情况表)、course_stu_info 表(学生课程成绩表)、docu_stu_info 表(学生 基本信息表)、docu_stu_register 表(学生入学登记情况表)和 docu_tea_info 表(教师 基本情况表)等。校园管理系统数据库中的主要表的设计结构如下。 图书借阅情况信息表结构如表 18.1 所示。 表 18.1 book_borrow_info 表 字段名称 数据类型 字段大小 是否主键 说 明 borrow_id varchar 32 主键 自动流水号码 book_id varchar 13 图书编号 stu_id varchar 12 学生编号 book_name varchar 20 图书名称 price money 8 图书价格 book_type varchar 20 图书类型 borrow_date datetime 8 借出日期 return_date datetime 8 归还日期 czy varchar 8 操作员 351 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 图书登记情况表结构如表 18.2 所示。 表 18.2 book_reginster 表 字段名称 数据类型 字段大小 是否主键 说 明 book_id varchar 13 主键 图书编号 book_name varchar 50 图书名称 book_type varchar 20 图书类型 writer varchar 50 图书作者 book_concern varchar 20 出版社 publish_date datetime 8 出版日期 price money 8 图书价格 reg_date datetime 8 登记日期 book_count int 4 图书数量 czy varchar 12 操作员 remark text 16 备注 学生课程成绩表结构如表 18.3 所示。 表 18.3 course_stu_info 表 字段名称 数据类型 字段大小 是否主键 说 明 lsh varchar 32 主键 流水号码 code varchar 4 科目代码 stu_id varchar 12 学生编号 grade decimal 9 考试成绩 exam_type varchar 10 考试类别 exam_date datetime 8 考试日期 czy varchar 10 操作员 学生基本信息表结构如表 18.4 所示。 表 18.4 docu_stu_info 表 字段名称 数据类型 字段大小 是否主键 说 明 stu_id varchar 12 主键 学生编号 name varchar 12 学生姓名 sex varchar 4 学生性别 age datetime 4 学生年龄 sfzhm varchar 20 身份证号码 csrq datetime 8 出生日期 zzmm varchar 10 政治面貌 jtdh varchar 15 家庭电话 jtdz varchar 50 家庭地址 jkzk varchar 8 健康状况 352 第 4 篇 项 目 实 践 P A R T 4 学生入学登记情况表结构如表 18.5 所示。 表 18.5 docu_stu_register 表 字段名称 数据类型 字段大小 是否主键 说 明 lsh varchar 20 主键 流水号码 classid varchar 8 班级编号 stu_id varchar 12 学生编号 djrq datetime 8 登记日期 jbr varchar 12 经办人 lqfs decimal 9 录取分数 zymc varchar 20 专业名称 教师基本情况表结构如表 18.6 所示。 表 18.6 docu_tea_info 表 字段名称 数据类型 字段大小 是否主键 说 明 tea_id varchar 20 主键 主题编号 name varchar 12 主题标题 sex varchar 2 主题内容 age int 4 年龄 minzu varchar 10 发起人 zzmm varchar 20 表情 skzy varchar 50 E-mail 地址 xueli varchar 10 OICQ 号码 hunfou varchar 4 主页地址 csrq datetime 5 出生日期 sfzh varchar 18 身份证号 lxdh varchar 15 联系电话 gzrq datetime 8 工作日期 gzjj text 16 工作简介 18.4 系统总体架构设计 18.4.1 Web 文件架构设计 系统的发布完成后生成的文件夹架构如图 18.3 所示。 353 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 图 18.3 Web 文件夹架构图 源文件位置:光盘........\.mr..\.18..\.schoo.....l.\.WebRoot....... 18.4.2 类文件架构设计 系统中使用到的类文件结构图如图 18.4 所示。 图 18.4 系统架构设计图 354 第 4 篇 项 目 实 践 P A R T 4 源文件位置:光盘........\.mr..\.18..\.school......\.src... 18.4.3 页面效果图 校园管理系统的首页面运行效果如图 18.5 所示。 2 1 4 3 5 图 18.5 程序运行效果图 为方便读者阅读和有效利用本书附赠光盘中的实例,笔者将首页面的各部分说明以列 表形式给出,如表 18.7 所示。 表 18.7 网站首页说明 区 域 名 称 说 明 对应 JSP 文件 1 页首 旗帜广告 title.jsp 2 左菜单 1 级菜单 left.jsp 3 横向菜单 2 级菜单 DocMain.jsp 4 登录页 用于系统登录。 login.jsp 5 内容页面 根据 1 级菜单和 2 级菜单的选择,现实各模块页面 Content.jsp 源文件位置:光盘........\.mr..\.18..\.school......\.WebRoot.......\.index.jsp......... 首页面的整体框架的关键代码如下。 例程 18-1:光盘\mr\18\school\webRoot\index.jsp 校园管理系统 355 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 <body> </body> 18.5 系统配置与公共类的设计 18.5.1 系统文件配置 1.DispatcherServlet 控制器配置 在整个 Web MVC 架构中,使用者并不是直接连接到所需要的资源,而是先连接到前端 控制器,再由前端控制器判断使用者的请求和要分派给哪一个控制器对象来处理请求。 在 Spring 的 Web MVC 框架中,担任前端控制器角色的是 DispatcherServlet 类,它负 责将客户的请求分配给控制器对象。和任何Servlet 一样,DispatcherServet 类必须在 Web 应用系统的 web.xml 文件中进行配置,具体步骤如下。 (1)首先定义一个 DispatcherServet 的实例,名称为 dispatcherServet,具体代码 如下。 例程 18-2:光盘\mr\18\school\webRoot\WEB-INF\web.xml dispatcherServlet org.springframework.web.servlet.DispatcherServlet (2)设置应用中所有以*.htm 结尾的 URL 请求都由映射名称为 dispatcherServlet 的 实例进行处理,具体代码如下。 dispatcherServlet *.htm (3)根据应用系统的各种功能,分解出多个 xml 文件配置。在 Servlet 上下文中设置 contextConfigLocation 参数来装载 Spring 的配置文件,具体代码如下。 contextConfigLocation 356 第 4 篇 项 目 实 践 P A R T 4 /WEB-INF/View_Config.xml, /WEB-INF/Hibernate_Config.xml, /WEB-INF/System_Config.xml, /WEB-INF/DocuStu_Config.xml, /WEB-INF/DocuTea_Config.xml, /WEB-INF/BookLibrary_Config.xml, /WEB-INF/SourceStu_Config.xml, /WEB-INF/employee_Config.xml 2.视图层配置文件 view_Config.xml 在 Controller 返回 ModelAndView 后,DispatcherServlet 会交由 viewResolver(视 图解析器)对象来解析相关的视图层,因此需要设置一个 viewResolver 实例。本实例使用 了 org.springframework.web.servlet.view.InternalResourceViewResolver 解析器,在 这个系统中将以 JSP 作为视图层技术,所以需要设置解析器的“viewClass”属性为“org. spring framework. web.servlet.view.JstlView”。另外在这个配置文件中还定义了请求 映射类。具体代码如下。 例程 18-3:光盘\mr\18\school\webRoot\WEB-INF\view_Config.xml org.springframework.web.servlet.view.JstlView / .jsp stuinfcontroller ⋯⋯ ⋯⋯ ⋯⋯ 定义视图解析器使 用 JstlView 视图类 定义视图和控制器 的请求映射关系 357 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 ⋯⋯ ⋯⋯ ⋯⋯ ⋯⋯ ⋯⋯ ⋯⋯ 3.持久层文件 Hibernate_Config.xml 配置 整个系统的数据持久层的控制是通过 Spring 中集成的 Hibernate 框架完成的。在 Spring 框架与 Hibernate 整合的时候,Hibernate 的连接、事务管理是由建立 SessionFactory 开始的。SessionFactory 在应用程序中通常只需存在一个实例,因而 SessionFactory 底层的 DataSource 可以使用 Spring 中的 IoC 注入。 定 义 一 个 数 据 源 的 名 字 为 dataSource , 它 是 Spring 的 JDBC 框 架 中 DriverManagerDataSource 类的一个实例,用于设置连接数据库的不同参数,包括驱动、URL 地址等,具体代码如下。 例程 18-4:光盘\mr\18\school\webRoot\WEB-INF\Hibernate_Config.xml com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=db_school sa 定义一个 Hibernate 的 sessionFactory 会话 JavaBean,它在应用程序中通常只存在 一个实例即 springframework.orm.hibernate3.LocalSessionFactoryBean 类,在这个实例 中直接将 Datasource 注入其中。对于Hibernate 的对象与关联数据表之间的映射,则指定 358 第 4 篇 项 目 实 践 P A R T 4 在“mapping DirectoryLocations”中。接着定义一个名字为“dao”的 Javabean 映射名 称,并为它注入“sessionFactory”属性,具体代码如下。 classpath:com/hibernate/model true PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly 359 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 18.5.2 数据库操作的核心类设计 数据库操作的核心类主要是指 DAOSupport 这个类,它继承了 HibernateDaoSupport 类,这样可以省去一些管理 SessionFactory、HibernateTemplate 资源的工作,只要在配 置文件中注入 SessionFactory 的实例就可以了。 在这个类中定义了不同的操作方法,系统的控制器通过调用该类中的对应方法来完成 对数据库进行写入和读取操作。DAOSupport 类的具体编写过程如下。 (1)在包路径 com.hibernate.dao 中定义一个 HibernateDaoSupport 的子类 DAOSupport,并导入相应的类文件,代码如下。 例程 18-5:光盘\mr\18\school\src\com\hibernate\dao\DAOSupport.java package com.hibernate.dao; import java.util.List; import org.hibernate.Query; import org.hibernate.SQLQuery; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class DAOSupport extends HibernateDaoSupport{ ⋯⋯ } (2)在该类中定义一个存储方法 InsertOrUpdate()和一个删除方法 DeleteObject(), 用于存储和删除某一数据,它们的参数都为 Object 类型,代码如下。 public boolean InsertOrUpdate(Object obj) { getHibernateTemplate().saveOrUpdate(obj); return true; } public void DeleteObject(Object obj) { getHibernateTemplate().delete(obj); } (3)定义一个查询方法 QueryObject()用于查询满足条件的实体对象,它的参数 QueryStr 为字符串类型,返回类型为 List 集合类对象,代码如下。 public List QueryObject(String QueryStr){ return getHibernateTemplate().find(QueryStr); } public Object getObject(Class obj, int id) { return getHibernateTemplate().get(obj, Integer.valueOf(id)); } (4)定义查询方法用于查询满足条件的对象,返回类型也为List 集合对象,代码如下。 public List QueryObjectFromSql(String SqlStr,String tname,Object obj){ SQLQuery query = this.getSession().createSQLQuery(SqlStr); query.addEntity(tname,obj.getClass()); return query.list(); } 360 第 4 篇 项 目 实 践 P A R T 4 public List QueryObjectFromSql(String SqlStr){ Query query = this.getSession().createSQLQuery(SqlStr); return query.list(); } 18.6 系统登录模块设计 系统登录模块是校园管理系统中最先使用的功能,是进入系统的入口。在系统登录页 面中,系统管理人员可以通过输入正确的管理员名称和密码进入到系统,当用户没有输入 管理员名称或密码时,系统会禁止访问系统功能,并给予提示信息。系统登录模块位于左 侧主菜单之下,其运行结果如图 18.6 所示。 图 18.6 用户登录界面 18.6.1 编写 Hibernate 实体类及映射文件 登录模块在数据库中对应着 Login 表,其中包含 id,name,username 和 password 字 段,在编写 Hibernate 实体类时要定义对应 Login 表中字段的 4 个属性及 get()/set()方 法。在登录模块中定义 Login 表的实体类 Login.java,关键代码如下。 例程 18-6:光盘\mr\18\school\src\com\hibernate\model\Login.java package com.hibernate.model; public class Login implements java.io.Serializable { private Integer id; //id字段用于索引不同的用户 private String name; /*name字段定义用户的昵称,在页面显示时,显示用户的昵称而不是登录用户名称*/ private String username; private String password; public Login(){ } public Login(Integer id, String name, String username, String password){ this.id = id; this.name = name; this.username = username; this.password = password; } public Integer getId(){ return this.id; } 361 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 public void setId(Integer id) { this.id = id; } public String getName(){ return this.name; } public void setName(String name) { this.name = name; } public String getUsername(){ return this.username; } public void setUsername(String username){ this.username = username; } public String getPassword(){ return this.password; } public void setPassword(String password){ this.password = password; } } 定义 Login 实体类之后,需要编写实体类的映射文件来定义实体类与数据库对应关系 和 ID 主键的生成方式。映射文件以实体类的名称(如 Login)为前缀,以“.hbm.xml”为 后缀定义文件名称。实体类 Login 对应的映射文件为“Login.hbm.xml”,具体代码如下。 例程 18-7:光盘\mr\18\school\src\com\hibernate\model\Login.hbm.xml 定义 Login 实体类 的 ID 字段生成方式 为手动赋值,不需要 Hibernate 参与 362 第 4 篇 项 目 实 践 P A R T 4 18.6.2 页面设计 Login.jsp 是登录页面文件。页面中有一个 Form 表单,包含用户名和密码两个文本框 和登录按钮。在登录成功后页面会显示登录成功信息而不再是 Form 表单。因为使用了 JSP 标签“”判断登录状态,因此,需要加载 JSTL 标签库,具体代码如下。 例程 18-8:光盘\mr\18\school\WebRoot\Login.jsp <%@ page language="java" pageEncoding="GB2312"%> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%> 如果成功加载 JSP 标签库,在 Login.jsp 文件中可以使用“”从 Session 会话 中判断用户的登录情况。如果在 Session 会话中 loginUser 属性为空说明用户没有登录, 那么就将 Form 表单显示给用户,让用户输入登录信息。关键代码如下。
用户名: 密  码:   

${message }

相反,如果在 Session 会话中 loginUser 属性不为空,说明用户已经登录,程序将登 录成功信息显示给用户。通过loginUser 属性的 name 字段读取登录用户的昵称,显示登录 成功信息中,以确定某个用户登录成功。实现显示当前登录用户信息的关键代码如下。 当前用户:${sessionScope.loginUser.name }

退出

18.6.3 控制器设计 在登录页面提交登录信息时,由 LoginController 控制器处理登录的业务逻辑。 LoginController 控制器以 Spring IoC 注入的 dao 属性来操作数据库。在接收到登录请求 后,会使用请求中的用户名和密码到数据库中去寻找用户记录,如果用户存在并且密码属 实,会创建 login 对象代表登录用户并且设置为 Session 的属性“loginUser”,最后返回 363 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 登录成功页面。但是,有个例外的结果,如果用户提交请求中 action 参数不为空(例如: “loginout”),控制器会从Session 会话中移除“loginUser”属性来完成退出功能。控制 器 LoginController 程序具体代码如下。 例程 18-9:光盘\mr\18\school\src\com\hibernate\spring\LoginController.Java package com.spring.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import com.hibernate.dao.DAOSupport; import com.hibernate.model.Login; public class LoginController extends AbstractController { private DAOSupport dao = null; protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { Map model = new HashMap(); HttpSession session = request.getSession(); String user = request.getParameter("username"); String pass = request.getParameter("password"); String action = request.getParameter("action"); if (action == null) { List list = dao.QueryObject("from Login where username='" + user + "' and password='" + pass + "'"); if (list.size() > 0) { Login login = (Login) list.get(0); session.setAttribute("loginUser", login); } else { model.put("message", "用户名不存在或密码错误。"); } }else{ session.removeAttribute("loginUser"); } return new ModelAndView("left", model); } public DAOSupport getDao(){ return dao; } public void setDao(DAOSupport dao) { this.dao = dao; } } 364 第 4 篇 项 目 实 践 P A R T 4 18.6.4 xml 信息配置 LoginController 是处理简单登录逻辑的控制器,它的代码很少,容易理解。但是它 并不能直接接收到客户的登录请求。要在 Spring 的 System_Config.xml 配置文件中将 LoginController 控制器定义为一个 JavaBean,并且注入“dao”属性依赖的 DAOProxy 代 理 JavaBean,然后在 View_Config.xml 配置文件中的“urlMapping”请求映射中设置访问 LoginController 控制器的 URL 路径,这样它才可以接收到登录请求,并且通过 dao 属性 在数据库中验证用户信息。在本实例中登录模块的配置信息定义在“System_Config.xml” 配置文件中,其关键代码如下。 例程 18-10:光盘\mr\18\school\WebRoot\WEB-INF\System_Config.xml 在“View_Config.xml”文件中定义了“视图解析器”和“urlMapping”控制器映射, 其中包含请求映射的关键代码,具体代码如下。 例程 18-11:光盘\mr\18\school\WebRoot\WEB-INF\View_Config.xml //其他控制器映射 login //其他控制器映射 在上述代码中,将“/login.htm”的访问路径映射到名为 login 的控制器上(即前面 提到的 LoginController 控制器),在登录页面将登录请求提交到“/login.htm”时,会将 登录请求发送给 LoginController 控制器,控制器会验证登录信息以确定反对登录成功信 息或错误信息。 365 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 18.7 代码维护模块设计 18.7.1 总体架构设计 1.模块功能介绍 系统代码模块主要完成对系统中各种代码的设置与维护,是整个系统的基础部分,只 有这些基础数据存在才能够操作其他模块。在这一模块中主要包括以下代码的功能设置。 专业代码维护:用于维护所学专业信息。 年级代码维护:用于维护年级信息。 班级代码维护:用于维护班级信息。 学科代码维护:用于维护学科信息。 2.文件架构 代码维护功能模块文件架构如图 18.7 所示。 图 18.7 代码维护功能模块文件架构图 18.7.2 班级代码维护模块设计 代码维护模块中的功能设计和编写过程都基本相似,限于篇幅,这里只讲解学生班级 代码的设计过程,其他的模块读者可参考光盘源文件。在用户登录成功后,选择“代码维 护”/“班级代码维护”进入该页面完成班级代码的设置,运行效果如图 18.8 所示。 图 18.8 班级代码维护效果图 366 第 4 篇 项 目 实 践 P A R T 4 1.编写 Hibernate 实体类及实体类对应的映射文件 (1)Hibernate 实体类的编写 用于处理班级信息数据存盘的 Hibernate 实体类是 SystemClassInfo 类。在这个类中 除了定义了一些基本类型的属性之外还包括两个其他实体类型的属性,分别为专业代码类 和年级代码类,及用于设置这些属性的 get()/set()方法,关键代码如下。 例程 18-12:光盘\mr\18\school\src\com\hibernate\model\SystemClassInfo.Java private String classid; private SystemSpecialtyCode systemSpecialtyCode; //专业代码类 private SystemGradeCode systemGradeCode; //年级代码类 private String classmc; public String getClassid(){ return this.classid; } public void setClassid(String classid){ this.classid = classid; } public SystemSpecialtyCode getSystemSpecialtyCode(){ return this.systemSpecialtyCode; } public void setSystemSpecialtyCode(SystemSpecialtyCode systemSpecialtyCode){ this.systemSpecialtyCode = systemSpecialtyCode; } ⋯.//此处省略了部分get()/set()方法 } (2)Hibernate 映射文件的编写 在 实 体 类 的 配 置 文 件 SystemClassInfo.hbm.xml 中 定 义 了 与 数 据 表 system_class_info 的对应关系,同时还定义了与专业代码类和年级代码类之间的多对一 的关系。关键代码如下。 例程 18-13:光盘\mr\18\school\src\com\hibernate\model\SystemClassInfo.hbm.xml 367 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 2.页面设计 页面文件 sys_code_class.jsp 主要完成班级信息的录入存盘及删除操作,下面介绍设 计过程。 (1)编写前台页面脚本代码,在 javascripte 中定义两个方法 deal()和 check()用于 执行删除和验证操作,关键代码如下。 例程 18-14:光盘\mr\18\school\WebRoot\WEB-INF\systemview\sys_code_class.jsp (2)进入该页面时,读取专业代码和年级代码中的数据信息,以供用户选择。关键代 码如下。
专业名称
年级名称
(3)在页面中通过接收查询出来的数据集合,通过循环标签显示班级中的 基本信息,代码如下。 ${cs.classid} ${cs.classmc} ${cs.spname} ${cs.grname} 删除 369 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 3.控制器设计 (1)在控制器 SystemCodeControl 中定义 CodeList()方法用于进行查询专业和年级两个 代码表中的数据。在这个方法中首先定义一个 name 参数,根据客户端传入的不同参数执 行不同的操作,然后执行调用 dao 中的 QueryObjec()方法检索数据对象,最后返回包含 Model 数据的 JSP 页面,关键代码如下。 例程18-15:光盘\mr\18\school\src\com\hibernate\spring\controller\SystemCodeControl.java public ModelAndView CodeList(HttpServletRequest request,HttpServletResponse response) throws Exception{ String name = request.getParameter("name"); List listObject = null; Map mapMessages = new HashMap(); if(name.equals("cl")){ Map maps = new HashMap(); List gradeList = dao.QueryObject("from SystemGradeCode"); List specList = dao.QueryObject("from SystemSpecialtyCode"); maps.put("grade",gradeList); maps.put("spec",specList); maps.put("classinfo",getClassInfo()); viewName = "systemview/sys_code_class"; return new ModelAndView(this.viewName,"messages",maps); mapMessages.put("listObject", listObject); return new ModelAndView(this.viewName,mapMessages); } (2)用户填写完相应的数据信息之后,进行单击【存盘】按钮。这一操作是通过控制 器 SystemClassControl 类完成的,设计过程如下。 在包 com.spring.controller 下定义一个 SystemClassControl 类,它实现了 Controller 接口, 然后导入需要的类文件,定义属性 DAOSupport 执行数据操作方法,编写 DAOSupport 的 get()/set()方法,代码如下。 例程18-16:光盘\mr\18\school\src\com\hibernate\spring\controller\SystemCodeControl.java package com.spring.controller; import java.util.AbstractList; import java.util.HashMap; ⋯⋯\\省略import public class SystemClassControl implements Controller { private DAOSupport dao = null; private String pageView = null; public DAOSupport getDao(){ return dao; } public String getPageView(){ return pageView; } public void setPageView(String pageView){ 370 第 4 篇 项 目 实 践 P A R T 4 this.pageView = pageView; } public void setDao(DAOSupport dao) { this.dao = dao; } ⋯.//此处省略了部分getXXX()/setXXX()方法 } 实现接口中的 handleRequest()方法。在这个方法中通过参数 request 获得客户端传 递表单中的年级代码和专业代码,然后再通过公共类 dao 中的查询方法 QueryObject 查询 班级表中最大的班级编号 maxclass,通过 maxclass 生成一个新的班级编号,关键代码如 下。 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("gb2312"); String spcode = request.getParameter("hiddspcode"); String grcode = request.getParameter("hiddgrcode"); List maxclass = dao.QueryObject("select max(classid) from SystemClassInfo sci where sci.system SpecialtyCode.spCode = " + spcode + " and sci.systemGradeCode.grCode =" + grcode); Object maxobj = maxclass.get(0); String newclassid = null; if (maxobj==null){ newclassid = spcode + grcode + "01"; } else { String a1 = maxobj.toString(); int aa = Integer.parseInt(a1.substring(a1.length() - 2)) + 1; if(aa > 9){ newclassid = (spcode + grcode + aa) + ""; }else{ newclassid = (spcode + grcode + "0" + aa) + ""; } } 定义一个实体类 SystemClassInfo 的实例 objclass,通过 request 参数获得班级名称 为 objclass 中的班级名称进行赋值。定义类 SystemSpecialtyCode 和 SystemGradeCode 的实例对象并赋值给 spcode 和 grcode 变量值,最后调用实例 objclass 中的 setXXX()方 法进行赋值,然后调用 dao 中的 InsertOrUpdate 方法执行存盘操作,关键代码如下。 SystemClassInfo objclass = new SystemClassInfo(); String name = request.getParameter("classname"); objclass.setClassmc(name); SystemSpecialtyCode spcodeobject = new SystemSpecialtyCode(); spcodeobject.setSpCode(spcode); SystemGradeCode grcodeobject = new SystemGradeCode(); grcodeobject.setGrCode(grcode); objclass.setSystemGradeCode(grcodeobject); objclass.setSystemSpecialtyCode(spcodeobject); 371 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 objclass.setClassid(newclassid); dao.InsertOrUpdate(objclass); 定义一个 Map 集合类的对象 mapMessage,调用 dao 中的 QueryObject()方法进行查询 年级信息和专业信息。定义一个字符串参数 classSql 用于保存查询语句,然后通过循环语 句将查询的结果集合解析生成 SystemClassInfoList 对象,最后将生成的对象填充到 map Message 中,其关键代码如下。 Map mapMessage = new HashMap(); List gradeList = dao.QueryObject("from SystemGradeCode"); List specList = dao.QueryObject("from SystemSpecialtyCode"); String classSql = null; classSql = "SELECT cs.classid as classid, cs.classmc as classmc, sp.name as spname, gr.gr_name as grname FROM system_class_info cs, system_specialty_code sp ,system_grade_code gr where cs.sp_code = sp.sp_code and cs.gr_code = gr.gr_code"; List classinfo = dao.QueryObjectFromSql(classSql); List classlist = new ArrayList(); for(int i=0;i syscodecontroller codeSetControl CodeClass
在 System_Config.xml 配置文件中进行配置代码如下。 例程 18-19:光盘\mr\18\school\WebRoot\WEB-INF\System_Config.xml 373 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 CodeList CodeQuery CodeDel systemview/sys_code_class 现在,班级代码维护模块已经配置完成,其详细内容读者可参见光盘中的源文件,至 于其他的代码维护的编写大体类似,不再赘述。 18.8 档案管理模块设计 18.8.1 总体架构设计 1.模块功能介绍 档案管理模块主要完成学生和教师的基本信息录入、修改和查询操作,具体功能如下。 学生基本信息:用于录入学生的基本信息情况。 学生入校登记:用于登记学生入学的基本信息。 学生信息维护:用于修改学生录入的基本信息。 学生登记查询:用于查询学生的登记信息。 教师基本信息:用于录入教师的基本信息情况。 374 第 4 篇 项 目 实 践 P A R T 4 教师信息维护:用于修改教师的基本信息情况。 2.文件架构 档案管理模块主要的文件架构如图 18.9 所示。 图 18.9 档案管理模块文件架构图 18.8.2 学生入校登记设计 当学生基本信息录入完成后,单击“档案管理”/“学生入校登记”进入该模块,用户 首先输入学生编号,当学生编号正确的时候进行检索数据,然后进行登记情况的输入,输 入完成后单击【提交】按钮进行存盘。运行效果如图 18.10 所示。 图 18.10 学生入校登记情况表 1.编写 Hibernate 实体类及映射文件 (1)用于处理学生注册信息的 Hibernate 实体类是 DocuStuRegister 类,在这个类中 包括对应数据表的属性以及这些属性的 get()/set()方法。关键代码如下。 例程 18-20:光盘\mr\18\school\src\com\hibernate\model\DocuStuRegister.java import java.sql.Date; public class DocuStuRegister implements java.io.Serializable { private String lsh; //定义自动编号 private SystemClassInfo systemClassInfo; //定义班级信息对象 private DocuStuInfo docuStuInfo; //定义学生信息对象 private Date djrq; private String jbr; 375 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 private double lqfs; private String zymc; public String getLsh(){ return this.lsh; } public void setLsh(String lsh) { this.lsh = lsh; } public SystemClassInfo getSystemClassInfo(){ return this.systemClassInfo; } ⋯⋯//此处省略了部分get()/set()方法 } (2)定义 DocuStuRegister 学生登记类所对应的 Hibernate 映射文件,该文件所对应的 数据表为 docu_stu_register,关键代码如下。 例程 18-21:光盘\mr\18\school\src\com\hibernate\model\DocuStuRegister.hbm.xml 376 第 4 篇 项 目 实 践 P A R T 4 2.页面设计 学生入校登记所对应的 JSP 文件为 doc_stuinfo_reg.jsp。在这个页面中,用户输入 学生编号之后,系统检索该学生的基本信息情况并显示在页面中,然后输入经办人和录取 分数进行数据提交。具体实现步骤如下。 (1)定义一个 JavaScripte 函数用于检索用户输入的学生编号信息。关键代码如下。 例程 18-22:光盘\mr\18\school\WebRoot\WEB-INF\docuview\doc_stuinfo_reg.jsp (2)定义一个表单显示学生编号对应的学生基本信息。关键代码如下。
学生姓名 班级名称
专业名称 入学日期
经办人 入学分数
${messages.msg}
377 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 ${sysMsgInf}
3.控制器设计 (1)用户输入学生编号后,按回车键或者单击【确定】按钮,系统通过输入的编号进 行学生基本信息的检索操作,然后控制器将检索信息传递给 doc_stuinfo_reg.jsp 页面。在控 制器 InfoListController 中定义一个 listStuReg 查询方法,在这个方法中,通过公共类 dao 检 索学生的基本信息,如果不存在则进行返回;如果存在定义一个 Map 类型的变量 mapStuinf 然后依次获得班级编号、专业名称等不同信息并填写到 mapStuinf 变量中进行页面返回操 作,关键代码如下。 例程 18-23:光盘\mr\18\school\src\com\spring\controller\InfoListController.java public ModelAndView listStuReg(HttpServletRequest request, HttpServletResponse response) throws Exception { String stuID = request.getParameter("stuid"); List stuinfolist = dao.QueryObject("From DocuStuInfo where stuId = '" + stuID + "'"); if (stuinfolist.isEmpty()){ return new ModelAndView("docuview/doc_stuinfo_reg", "sysMsgInf", ""); } DocuStuInfo stuinfo = (DocuStuInfo) stuinfolist.get(0); String classId = stuinfo.getStuId().substring(0,6); String sp_code = stuinfo.getStuId().substring(0, 2); List classmc = dao.QueryObjectFromSql( "select classmc from system_class_info where classid = '" + classId + "'"); List zymc = dao.QueryObjectFromSql( "select name from system_specialty_code where sp_code = '" + sp_code + "'"); java.sql.Date djrq = new java.sql.Date(System.currentTimeMillis()); java.util.Map mapStuinf = new java.util.HashMap(); mapStuinf.put("lsh", String.valueOf(System.currentTimeMillis())); mapStuinf.put("stuId", stuinfo.getStuId()); mapStuinf.put("name", stuinfo.getName()); mapStuinf.put("classid", classId); mapStuinf.put("classmc", classmc.get(0)); mapStuinf.put("zymc", zymc.get(0)); mapStuinf.put("djrq", djrq); return new ModelAndView("docuview/doc_stuinfo_reg", "messages", mapStuinf); } 378 第 4 篇 项 目 实 践 P A R T 4 (2)建立 StuInfoController 控制器类,完成学生登记信息的存盘操作。 首先在类 StuInfoController 的 formBackingObject()方法中定义控制器对应的表单 类为 DocuStuRegister 类,关键代码如下。 例程 18-24:光盘\mr\18\school\src\com\spring\controller\StuInfoController.java protected Object formBackingObject(HttpServletRequest request) throws Exception { request.setCharacterEncoding("gb2312");//在这里设置字符集 String cname = request.getParameter("ctname"); if(cname.equals("dsg")||cname == "dsg"){ this.setCommandClass(DocuStuRegister.class); } return super.formBackingObject(request); } 然后在类 StuInfoController 的存盘方法 onSubmit 中编写存盘代码。根据用户输入的 学生编号判断该学生是否存在,如果不存在则返回当前页面并进行信息提示;如果存在则 定义一个类 DocuStuRegister 的实例 stuRegForm,并为它进行相应的赋值,然后执行 dao 的存盘方法完成学生登记操作,关键代码如下。 protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse reponse, Object obj, BindException arg3) throws Exception { if (cname.equals("dsg")||cname == "dsg") { viewPage = "docuview/doc_stuinfo_reg"; List stuRegList = dao.QueryObject("From DocuStuRegister dsr where dsr.docuStuInfo.stuId = '" + request.getParameter("stu_id") + "'"); if(!stuRegList.isEmpty()){ msg.put("msg", "该学生已经登记,请重新选择!!!"); return new ModelAndView(viewPage,"messages",msg); } DocuStuRegister stuRegForm = (DocuStuRegister)obj; DocuStuInfo docuStuInfo = new DocuStuInfo(); docuStuInfo.setStuId(request.getParameter("stu_id")); stuRegForm.setDocuStuInfo(docuStuInfo); SystemClassInfo systemClassInfo = new SystemClassInfo(); systemClassInfo.setClassid(request.getParameter("classid")); stuRegForm.setSystemClassInfo(systemClassInfo); dao.InsertOrUpdate(stuRegForm); msg.put("msg", "学生登记信息数据录入成功!!!"); } return new ModelAndView(viewPage,"messages",msg); } 4.DocuStu_Config.xml 文件配置 在 view_config.xml 配 置 文 件 中 配 置 学 生 登 记 提 交 时 对 应 的 URL 请 求 为 “"/studentReg.htm”,并且设置对应的控制器为“studentReg”,关键代码如下。 例程 18-25:光盘\mr\18\school\WebRoot\WEB-INF\DocuStu_Config.xml 379 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 stuinfcontroller studentReg infoListController infoListController studentRegList infoListController infoListController infoListController infoListController 在 DocuStu_Config.xml 中的文件配置中配置学生注册的控制器"studentReg"以及其 他的配置参数,具体设置代码如下。 docuview/doc_stuinfo_reg docuview/doc_stuinfo_reglist listClass listAllStu listStuReg createID listStuModi listStuModiSave 380 第 4 篇 项 目 实 践 P A R T 4 listStuRegSerach 18.8.3 学生信息维护设计 在右侧的菜单栏中,依次单击“档案管理/学生信息维护”的超链接进入学生信息维护 页面,运行效果如图 18.11 所示。 图 18.11 学生信息查询 下面介绍在该页面中代码的具体实现过程。 1.页面设计 (1)学生信息查询页面设计 学生信息查询所对应的 JSP 页面是 doc_stuinfo_modify.jsp。在这个页面中,首先根 据用户输入的查询参数进行学生基本信息的查询,当查询到学生信息之后,选择要维护的 学生信息进行维护操作,具体的编写过程如下。 首先建立一个 form 表单,用于生成查询条件,让用户查询学生的基本信息,关键代码 如下。 例程 18-26:光盘\mr\18\school\WebRoot\WEB-INF\doc_stuinfo_modify.jsp
381 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统
查询条件:
通过循环标签显示学生基本情况的详细信息,该参数为控制器传递过来的 数据集合 messages,关键代码如下。 维护 (2)学生维护页面设计 如图 18.11 所示,选择一组维护信息。单击【维护】按钮,进入维护学生信息页面, 如图 18.12 所示。 382 第 4 篇 项 目 实 践 P A R T 4 图 18.12 学生维护页面 如图 18.12 所示,对应的 JSP 文件为 doc_stuinfo_modify_save.jsp。这个文件主要 通过 form 表单进行学生基本信息的修改,修改完成之后进行数据提交操作。在该页面中最 关键代码是下拉列表的生成,其关键代码如下。 例程 18-27:光盘\mr\18\school\WebRoot\WEB-INF\doc_stuinfo_modify_save.jsp
政治面貌
2.控制器设计 (1)在如图 18.11 所示的学生信息查询页面中,输入查询条件并单击【查询】按钮后, 触发控制器类 InfoListController 中定义的一个 listStuModi ()方法完成查询功能。在 listStuModi()方法中定义两个查询参数,然后生成查询语句,通过公共类方法进行数据查 询,将查询结果返回到页面中,实现 listStuModi()方法的代码如下。 例程 18-28:光盘\mr\18\school\src\com\spring\controller\InfoListController.java public ModelAndView listStuModi(HttpServletRequest request, HttpServletResponse response) throws Exception { String conStr = request.getParameter("select"); String sValue = request.getParameter("strvalue"); if(sValue==null){ conStr = "stuId"; sValue = ""; } List stuList = dao.QueryObject("from DocuStuInfo where " + conStr + " like '" + sValue + "%'"); if(stuList.isEmpty()){ return new ModelAndView("docuview/doc_stuinfo_modify", "msg", "系统提示: 383 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 条件输入有误请重新输入"); }else{ return new ModelAndView("docuview/doc_stuinfo_modify", "messages", stuList); } } (2)在如图 18.11 所示的学生信息查询页面中,选择一组数据后,用户单击【维护】 按钮将触发控制器类 InfoListController 中定义的 listStuModiSave 方法,以检索学生中 的信息,关键代码如下。 例程 18-29:光盘\mr\18\school\src\com\spring\controller\InfoListController.java public ModelAndView listStuModiSave(HttpServletRequest request, HttpServletResponse response) throws Exception { String id = request.getParameter("id"); List stuList = dao.QueryObject("from DocuStuInfo where stuId = '" + id + "'"); return new ModelAndView("docuview/doc_stuinfo_modify_save", "messages", (DocuStuInfo) stuList.get(0)); } (3)在如图 18.12 所示的学生信息维护页面中,用户对学生的基本信息修改完毕后, 单击【提交】按钮完成数据的存盘操作。通过在控制器类 StuInfoController 添加 onSubmit 方法实现学生信息维护的关键代码如下。 例程 18-30:光盘\mr\18\school\src\com\spring\controller\InfoListController.java if(cname.equals("dsim")||cname=="dsim"){ DocuStuInfo stuform =(DocuStuInfo)obj; dao.InsertOrUpdate(stuform); return new ModelAndView(new RedirectView("listStuModi.htm")); } 3.xml 文件配置 在该 xml 配置文件的代码与 18.7.2 节中 System_Config.xml 配置文件的代码基本相 同,读者可以参阅光盘中相对应的代码文件进行查询,不再赘述。 18.8.4 学生登记查询设计 在右侧的菜单栏中,依次单击“档案管理/学生登记查询”的超链接进入学生登记查询 模块,用户根据页面上设计的各种查询参数生成不同的查询条件,然后单击【查询】按钮, 查询满足条件的数据记录,运行效果如图 18.13 所示。 图 18.13 学生登记信息查询页面 384 第 4 篇 项 目 实 践 P A R T 4 下面介绍在该页面中代码的具体实现过程。 1.页面设计 如图 18.13 所示的页面文件为 doc_stuinfo_reglist.jsp,在该页面文件中建立一个 form 表单,并添加表单元素完成用户条件查询的功能,实现 form 表单元素的关键代码如 下。 例程 18-31:光盘\mr\18\school\WebRoot\docuview\doc_stuinfo_reglist.jsp
查询字段: 运算符:
设计一个 table 表格实现学生查询信息,该表格通过JSP2.0 中的迭代标签实现对学生 信息的查询结果,实现学生查询信息的关键代码如下。
385 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统
2.控制器设计 在控制器类 InfoListController 中定义 listStuRegSerach()方法,实现查询学生登 记情况信息,关键代码如下。 例程 18-32:光盘\mr\18\school\src\com\spring\controller\InfoListController.java public ModelAndView listStuRegSerach(HttpServletRequest request, HttpServletResponse response) throws Exception { String propName = request.getParameter("select1"); String conName = request.getParameter("select2"); if (conName.equals("DY")) conName = ">"; if (conName.equals("XY")) conName = "<"; if (conName.equals("DEY")) conName = "="; if (conName.equals("LIKE")) conName = "like"; String strValue = request.getParameter("strvalue"); String sqlSelect = null,fromStr = null,whereStr = null; String selectStr = "select dsi.stuId,dsi.name,dsi.sfzhm,dsi.csrq,dsr.djrq,dsr.lqfs,dsr.jbr "; if (propName.equals("stuId")){ fromStr = "from DocuStuRegister dsr where dsr.docuStuInfo.stuId "; }else{ fromStr = "from DocuStuRegister dsr where dsr." + propName ; } if (conName.equals("like")){ whereStr = " like '%" + strValue + "%'"; }else{ whereStr = conName + "'" + strValue + "'"; } sqlSelect = fromStr + whereStr; List msgList = dao.QueryObject(sqlSelect); Map map = new HashMap(); System.out.println("msgList:" + msgList); if (msgList.isEmpty()){ map.put("messages",null); map.put("msg", "系统提示:没有找到满足条件的数据!!"); 386 第 4 篇 项 目 实 践 P A R T 4 return new ModelAndView("docuview/doc_stuinfo_reglist", map); }else{ map.put("messages", msgList); map.put("msg", null); return new ModelAndView("docuview/doc_stuinfo_reglist", map); } } 3.xml 文件配置 该 xml 配置文件的代码与 18.7.2 节中 System_Config.xml 配置文件的代码基本相同, 读者可以参阅光盘中相对应的代码文件进行查询,这里不再赘述。 18.9 成绩管理模块设计 18.9.1 总体架构设计 1.模块功能设计 成绩管理模块主要完成学生成绩信息的录入修改查询操作,具体功能如下。 成绩录入:用于录入学生的基本信息情况。 成绩查询:用于登记学生入学的基本信息。 班级成绩统计:用于修改学生录入的基本信息。 年级成绩统计:用于查询学生的登记信息。 2.文件架构设计 成绩管理模块主要的文件架构如图 18.14 所示。 图 18.14 成绩管理模块的结构功能图 18.9.2 成绩录入模块设计 左侧菜单栏中单击“成绩管理”的超链接后,在 2 级菜单栏中单击“成绩录入”的超 链接,进入学生成绩录入模块页面。该页面主要完成学生的考试信息保存的功能。用户首 387 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 先输入学生的编号,输入正确后根据页面内容填写正确的数据信息,然后单击【提交】按 钮提交数据,即可完成对该学生的成绩录入过程。运行效果如图 18.15 所示。 图 18.15 成绩录入运行效果 1.编写 Hibernate 实体类及映射文件 (1)Hibernate 所对应的成绩实体类名称为 CourseStuInfo,该类中定义了与成绩关联 的两个属性类分别为 SystemCourseCode 和 DocuStuInfo 用于存储学生编号和科目代码,还 有相应的其他属性及对应的 get()/set()方法,成绩实体类的关键代码如下。 例程 18-33:光盘\mr\18\school\src\com\hibernate\model\CourseStuInfo.java import java.sql.Date; public class CourseStuInfo implements java.io.Serializable { private String lsh; private SystemCourseCode systemCourseCode; private DocuStuInfo docuStuInfo; private Float grade; private String examType; private Date examDate; private String czy; public String getLsh(){ return this.lsh; } public void setLsh(String lsh) { this.lsh = lsh; } public SystemCourseCode getSystemCourseCode(){ return this.systemCourseCode; } public void setSystemCourseCode(SystemCourseCode systemCourseCode){ this.systemCourseCode = systemCourseCode; } 388 第 4 篇 项 目 实 践 P A R T 4 ⋯⋯.//此处省略了部分代码 } (2)成绩实体类对应的 Hibernate 映射文件的名称为 CourseStuInfo.hbm.xml,该映 射文件的关键代码如下。 例程 18-34:光盘\mr\18\school\src\com\hibernate\model\CourseStuInfo.hbm.xml 2.界面设计 (1)成绩录入所对应的 JSP 文件的名称为 doc_stusource_input.jsp。在该页面中首 先定义一个表单 form 用于提供用户输入相应的信息,生成 form 表单的关键代码如下。 例程 18-35:光盘\mr\18\school\WebRoot\docuview\doc_stusource_input.jsp
389 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统
学生编号 学生姓名
考试类别 考试日期
操作员
 
 
(2)通过循环标签显示考试科目信息的关键代码如下。
${list.subject}
3.控制器设计 控制器 SourceAddBatch.java 类文件用于存盘成绩数据。在类中首先引入相应的类包, 然后定义私有变量 DAOSupport,并设置相应的 set()/get()方法,关键代码如下。 例程 18-36:光盘\mr\18\school\src\com\spring\controller\SourceAddBatch.java package com.spring.controller; 390 第 4 篇 项 目 实 践 P A R T 4 import java.util.HashMap; import java.util.List; import java.util.Map; import java.sql.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.validation.BindException; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.web.servlet.mvc.SimpleFormController; import org.springframework.web.servlet.view.RedirectView; import com.hibernate.dao.DAOSupport; import com.hibernate.model.CourseStuInfo; import com.hibernate.model.DocuStuInfo; import com.hibernate.model.SystemCourseCode; public class SourceAddBatch extends AbstractController{ private DAOSupport dao; private String successView; public DAOSupport getDao(){ return dao; } public void setDao(DAOSupport dao) { this.dao = dao; } 在控制器 SourceAddBatch 类中的 handleRequestInternal()方法中,设置学生成绩信 息的批量添加,代码如下。 protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse rep) throws Exception { req.setCharacterEncoding("GB2312"); String stuId,examType; Date examDate; stuId = req.getParameter("stuId"); examType = req.getParameter("examType"); examDate = Date.valueOf(req.getParameter("examDate")); String findSql = "From CourseStuInfo csi where csi.docuStuInfo.stuId = '" + stuId + "' and examType = '" + examType + "' and examDate = '" + examDate + "'"; List findList = dao.QueryObject(findSql); if(!findList.isEmpty()){ Map model = new HashMap(); model.put("message", "该学生的成绩数据已经存在,请重新输入!!!"); List list = dao.QueryObject("from SystemCourseCode"); model.put("list", list); return new ModelAndView("/sourceview/doc_stusource_input", model); } List list = dao.QueryObject("from SystemCourseCode"); 391 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 Object[] obj = list.toArray(); DocuStuInfo stuinfo = new DocuStuInfo(); stuinfo.setStuId(req.getParameter("stuId")); for(inti=0;iCjLoader CjRegBatchController CjLoader (2)在 SourceStu_Config.xml 配置文件中相关成绩管理模块中的配置代码如下。 例程 18-38:光盘\mr\18\school\WebRoot\WEB-INF\SourceStu_Config.xml sourceview/doc_stusource_input 18.9.3 成绩查询模块设计 成绩查询模块显示学生的考试成绩基本信息。用户可以通过不同的查询条件对学生信 392 第 4 篇 项 目 实 践 P A R T 4 息的各种查询,运行效果如图 18.16 所示。 图 18.16 成绩查询运行效果 1.界面设计 (1)用于显示查询成绩信息的 JSP 的名称为 doc_stusource_list.jsp。首先,定义一个 form 表单用于填写查询条件的功能,关键代码如下。 例程 18-39:光盘\mr\18\school\WebRoot\docuview\doc_stusource_list.jsp 请输入查询条件: 393 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 (2)其次,通过循环标签显示查询学生成绩信息的数据,它的参数是通 过控件器传递而来的 List 集合对象。显示查询学生成绩信息的关键代码如下。 ${exam.examType }  ${exam.systemCourseCode.subject }  ${exam.docuStuInfo.stuId }  ${exam.docuStuInfo.name }  ${exam.grade }  2.控制器设计 在控制器类 SourceLoader 中定义 search()查询方法。首先,通过 req 对象获得客户 端表单中的不同数据信息;其次,根据这些数据信息生成不同的查询条件,调用公共类dao 中的查询方法,最后,将查询结果返回到页面中。search()查询方法的关键代码如下。 例程 18-40:光盘\mr\18\school\src\com\spring\controller\SourceLoader.java public ModelAndView search(HttpServletRequest req, HttpServletResponse res) { String condition = req.getParameter("condition"); String conditionContent = req.getParameter("conditionContent"); List list = null; if (condition != null && conditionContent != null) { if (condition.equals("考试类别")) condition = "examType"; else if (condition.equals("科目名称")) condition = "systemCourseCode.subject"; else if (condition.equals("学生姓名")) condition = "docuStuInfo.name"; else if (condition.equals("学生编号")) condition = "docuStuInfo.stuId"; else condition = "docuStuInfo.name"; list = dao.QueryObject("from CourseStuInfo where " + condition + " like '" + conditionContent + "%'"); } else { list = dao.QueryObject("from CourseStuInfo"); } 394 第 4 篇 项 目 实 践 P A R T 4 Map model = new HashMap(); model.put("list", list); return new ModelAndView("/sourceview/doc_stusource_search", model); } 3.xml 文件配置 Source_Config.xml 配置文件中相关成绩查询模块的配置代码如下。 例程 18-41:光盘\mr\18\school\WebRoot\WEB-INF\SourceStu_Config.xml reg search ClassSourceList GradeSourceList 18.9.4 班级成绩统计 班级成绩统计用于查询统计不同班级的考试明细情况,用户可以根据不同的查询条件 进行相应查询,其运行效果如图 18.17 所示。 图 18.17 班级成绩统计运行效果图 395 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 1.界面设计 (1)班级成绩统计 JSP 页面文件的名称为 doc_stusource_class_gather.jsp。首先定 义 form 表单通过下拉列表不同的查询条件信息,便于用户进行查询。关键代码如下。 例程 18-42:光盘\mr\18\school\WebRoot\docuview\doc_stusource_class_gather.jsp

班级名称: 考试类别: 考试日期:

(2)其次,当用户单击【查询】按钮后进行条件查询时,JSP 页接收控制器传递过来 的集合参数,之后通过两个循环标签显示查询的结果集合,显示查询信息的关键代码如下。 396 第 4 篇 项 目 实 践 P A R T 4
2.控制器设计 在控制器类 SourceLoader 中定义 ClassSourceList()查询方法,用于查询满足条件的 成绩数据。 (1)在方法ClassSourceList 中定义 3 个 String 类型的参数用于获得表单中的查询信 息并对这 3 个参数进行赋值操作,关键代码如下。 例程 18-43:光盘\mr\18\school\src\com\spring\controller\SourceLoader.java String classid = request.getParameter("selectmc"); if(classid == null) classid = "%"; String examType = request.getParameter("typeexam"); if(examType == null||examType.length()<=0){ examType = "%"; }else{ examType = new String(request.getParameter("typeexam").getBytes("iso-8859-1"),"gb2312"); } String examDate = request.getParameter("dateexam"); (2)定义 Vector 类型对象 vname,用于显示表格对象的列名称,然后通过 dao 中的查 询方法查询考试科目中的名称并为其赋值,关键代码如下。 List listClass = dao.QueryObject("From SystemClassInfo"); List courseList = dao.QueryObject("from SystemCourseCode"); java.util.Vector vname = new java.util.Vector(); vname.addElement("学生姓名"); vname.addElement("考试类别"); vname.addElement("考试日期"); Iterator iterator = courseList.iterator(); int index = 0; while(iterator.hasNext()){ SystemCourseCode courseObject = (SystemCourseCode)iterator.next(); vname.addElement(courseObject.getSubject()); index ++; 397 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 } vname.addElement("总分数"); (3)定义 String 类型的 sqlSelect 对象用于显示不同的查询条件,然后调用dao 中的 QueryObjectFromSq()l 方法实现数据查询的功能,关键代码如下。 String sqlSelect = null; if(examDate == null||examDate.length()<=0){ examDate = "%"; sqlSelect = "SELECT * FROM course_stu_info WHERE (SUBSTRING(stu_id, 1, 6) LIKE '" + classid + "' AND exam_type LIKE '" + examType + "' AND exam_date like '" + examDate + "')" ; }else{ sqlSelect = "SELECT * FROM course_stu_info WHERE (SUBSTRING(stu_id, 1, 6) LIKE '" + classid + "' AND exam_type LIKE '" + examType + "' AND exam_date = '" + examDate + "')" ; } List courseListObject = dao.QueryObjectFromSql(sqlSelect,"course_stu_info",new CourseStuInfo()); (4)定义对象数组 courseArray,该数组中的数值为上面查询到的结果集,再定义一 个集合 Collection 的一个对象 collection,通过循环语句解析这个结果集中的数据并进 行相应的运算,将运算之后的结果赋值给 collection 变量,关键代码如下。 Object[] courseArray = courseListObject.toArray(); int count = courseArray.length; java.util.Collection collection = new java.util.ArrayList(); int modcount = count / index; for(inti=0;i 400 第 4 篇 项 目 实 践 P A R T 4 2.页面设计 任班主任页面的 form 表单中有一个 teaId 教师编号字段,它使用了 JavaScript 处理 文本改变的事件。在输入教师编号之后,会以表格的方式显示教师的信息。teaId 字段的 事件处理和显示教师信息表格的关键代码如下。 例程 18-46:光盘\mr\18\school\WebRoot\employeeview\teaInstateCharge.jsp  
请输入教师编号:

设置关联表多对一关系并 且设定延迟加载“lazy”为 “false”,这样读取数据是 会同时读取关联表数据 401 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统
教师姓名
性别
年龄
学历
授课专业
婚否
${teaInfo.name } 
${teaInfo.sex } 
${teaInfo.age } 
${teaInfo.xueli } 
${teaInfo.systemSpecialtyCode.name } 
${teaInfo.hunfou } 
页面以选择菜单的方式输入班级名称,同时会通过事件处理读取班级所属的年级和专 业信息。用户只需要输入任职日期然后提交表单便可以完成班主任的任职操作。选择班级 信息和输入任职日期的页面关键代码如下。 402 第 4 篇 项 目 实 践 P A R T 4
 
班级名称
专业
年级
任职日期
   
3.控制器设计 EmployeeLoader 是负责处理整个教职工管理模块的控制器,它继承自 MultiAction Controller 控制器。其中,assignCharge()方法负责处理任班主任功能的业务代码。在接 收到页面的请求时,首先判断页面是否通过【提交】按钮发送的请求,如果不是通过按钮 提交的请求,定义返回页面需要显示的内容(例如:教师信息、班级信息等。);反之则处 理任命教师为班主任的业务。控制器 EmployeeLoader 关键代码如下。 例程 18-47:光盘\mr\18\school\src\com\spring\controller\EmployeeLoader.java package com.spring.controller; import java.sql.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; import com.hibernate.dao.DAOSupport; //省略其他import信息 public class EmployeeLoader extends MultiActionController { private DAOSupport dao; //省略教职任课业务代码 public ModelAndView assignCharge(HttpServletRequest req,HttpServletResponse res) { String changeClassId = req.getParameter("changeClassId"); String changeTeaId = req.getParameter("changeTeaId"); String submit = req.getParameter("Submit"); List list = dao.QueryObject("from SystemClassInfo"); Map model = new HashMap(); model.put("classInfo", list); model.put("rowInfo", list.get(0)); if (submit == null) { if (changeClassId != null) { list = dao.QueryObject("from SystemClassInfo where classid='"+ changeClassId + "'"); 403 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 if (list.size() > 0) { model.put("rowInfo", list.get(0)); } } if (changeTeaId != null) { list = dao.QueryObject("from DocuTeaInfo where teaId='"+ changeTeaId + "'"); if (list.size() > 0) model.put("teaInfo", list.get(0)); } } else { String teaId = req.getParameter("teaId"); String classId = req.getParameter("classid"); DocuTeaInfo teaInfo = new DocuTeaInfo(teaId, null); SystemClassInfo classInfo = new SystemClassInfo(classId); Date date = Date.valueOf(req.getParameter("rzrq")); EmplAssignCharge assignCharge = new EmplAssignCharge(teaInfo,classInfo); assignCharge.setRzrq(date); dao.InsertOrUpdate(assignCharge); model.put("message", "任职班主任成功"); } return new ModelAndView("employeeview/teaInstateCharge", model); } //省略其他功能模块的业务代码 protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("gb2312"); return super.handleRequestInternal(request, response); } } 4.xml 信息配置 employee_Config.xml 文件是教职工功能的配置文件,它将页面的请求和控制器映射 在一起,代码中EmployeeMethodNameResolver 定义了 MultiActionController 控制器(即 EmployeeLoader 控制器的父类)的方法名解析器,并把它和 DAOProxy 一同注入到 EmployeeLoader 控制器中。当控制器接收到请求的 URL 时会根据方法名解析器判断调用控 制器的哪个方法来处理请求。employee_Config.xml 配置文件完整代码如下。 例程 18-48:光盘\mr\18\school\WebRoot\WEB-INF\employee_Config.xml 在 handleRequest Internal() 方 法中设置客户请求的编码方 式,以解决页面的乱码问题 404 第 4 篇 项 目 实 践 P A R T 4 assignCourse assignCharge searchCharge searchTeaCourse 18.10.3 班主任查询功能设计 为方便日后班主任信息的查找,需要设计班主任的查询功能。功能的内容比较简单, 只要通过用户提供的不同查询条件搜索到数据库中满足条件的记录,然后显示到页面中即 可。在搜索结果中还可以处理班主任的离职操作。与其他查询功能相比,本查询中多了一 个可选项“查询历史记录”,用于查询已经离职的班主任信息。班主任查询功能运行页面如 图 18.20 所示。 图 18.20 班主任查询页面 1.页面设计 班主任查询功能的页面中包含一个 form 表单和一个用于显示查询结果的表格,页面的 form 表单由“condition”字段和“conditionContent”字段构成了查询条件。页面的 form 405 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 表单代码如下。 例程 18-49:光盘\mr\18\school\WebRoot\employeeview\teaCourseSearch.jsp
请输入查询条件: checked="yes"> 查询历史记录
显示查询结果的表格中包含一个【离职】按钮,可以使指定的教师脱离班主任的岗位 然后改由其他教师来担任此职务。
406 第 4 篇 项 目 实 践 P A R T 4
教师编号
教师姓名
教师性别
班级名称
年级
班级专业
任职日期
维护
离职日期
${list.docuTeaInfo.teaId }
${list.docuTeaInfo.name }
${list.docuTeaInfo.sex }
${list.systemClassInfo.classmc }
${list.systemClassInfo.systemGradeCode.grName }
${list.systemClassInfo.systemSpecialtyCode.name }
${list.rzrq }
${list.lizhiDate }
2.控制器设计 从 employee_Config.xml 配置文件中可以看出,班主任查询功能的请求处理代码包含 在 EmployeeLoader 控制器的 searchCharge()方法中。控制器接收到请求后,首先通过 “history”参数的值判断查询类型是查询历史记录还是当前在职记录。在获得查询结果之 后,将结果放到 Map 集合类 model 中,返回给查询页面。班主任查询功能的业务关键代码 如下。 例程 18-50:光盘\mr\18\school\src\com\spring\controller\EmployeeLoader.java public ModelAndView searchCharge(HttpServletRequest req, HttpServletResponse res) {// 查询班主任 List list = null; String history=req.getParameter("history"); String nop=history==null?"=":"<>"; String lsh = req.getParameter("lsh"); if (lsh != null) {//教师离职 list = dao.QueryObject("from EmplAssignCharge where lsh='" + lsh + "'"); if (list.size() > 0) { EmplAssignCharge obj = (EmplAssignCharge) list.get(0); 407 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 obj.setLizhiDate(new Date(System.currentTimeMillis())); dao.InsertOrUpdate(obj); } } String condition = req.getParameter("condition"); String conditionContent = req.getParameter("conditionContent"); if (condition == null) { condition = "systemClassInfo.systemSpecialtyCode.name"; } if (conditionContent == null) conditionContent = ""; list = dao.QueryObject("from EmplAssignCharge where " + condition + " like '" + conditionContent + "%' and lizhiDate "+nop+" null"); Map model = new HashMap(); model.put("list", list); model.put("condition", condition); model.put("conditionContent", conditionContent); model.put("history", history); return new ModelAndView("employeeview/chargeSearch", model); } 3.xml 文件配置 该 xml 配置文件的代码与 18.9.2 节中给出的配置文件的代码基本相同,读者可以参阅 光盘中相对应的代码文件进行查询,这里不再赘述。 18.11 图书馆管理模块设计 18.11.1 图书馆管理功能模块总体架构 1.模块功能介绍 图书管理功能模块的具体功能如下。 图书登记:用于添加图书信息。 图书维护:用于根据不同的查询条件查询图书信息并更改信息。 图书借阅:用于登记图书借阅信息。 图书归还:用于删除图书借阅信息。 借阅查询:用于根据不同条件查询图书的借阅信息。 查看图书信息列表:用于显示所有的图书信息。 图书详细信息:用于显示指定图书的详细信息。 2.文件架构 图书档案管理功能模块文件架构如图 18.21 所示。 408 第 4 篇 项 目 实 践 P A R T 4 图 18.21 图书档案管理功能模块文件架构 409 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 18.11.2 图书维护功能设计 作为一个图书馆,无论大小,它都有大量的图书需要统计库存数量和查看图书信息。 图书维护功能可以按照指定的查询条件查询图书信息,也可以通过【更改】按钮,在查看 图书详细资料的同时修改图书信息录入时的错误和库存等信息。图书维护功能的运行页面 如图 18.22 所示。 图 18.22 图书维护功能页面 1.编写 Hibernate 实体类及映射文件 图书登记功能使用数据库中的 book_reginster 表存储数据,那么维护功能也同样需要 在 book_reginster 表中查询数据。图书包含很多的信息字段,本实例中简单列举几个信息 字段来作讲解。实体类的属性关键代码如下。 例程 18-51:光盘\mr\18\school\src\com\hibernate\model\BookReginster.java private String bookId; //书号 private String bookName; //书名 private String bookType; //规类 private String writer; //作者 private String bookConcern; //出版社 private Date publishDate; //发行日期 private Double price; //单价 private Date regDate; //入库日期 private Integer bookCount; //图书数量 private String czy; //操作员 private String remark; //备注 由于没有什么特殊的关联关系,实体类的映射文件相对简单,这里不再赘述,请读者 参见光盘内容。 410 第 4 篇 项 目 实 践 P A R T 4 2.页面设计 在页面设计中首先放置一个 form 表单,表单中的“condition”字段和 “conditionContent”字段构成了查询条件。当提交表单时会将查询条件发送到指定的控制 器来查询信息。图书维护功能页面中的 form 表单内容代码如下。 例程 18-52:光盘\mr\18\school\WebRoot\bookview\bookService.jsp
请输入查询条件:
在输入查询条件并提交表单之后,控制器会根据查询条件搜索满足条件的信息并将查 询结果返回到页面中。这时页面中使用表格来显示控制器返回的查询结果,并在每条记录 的最后添加【更改】按钮,如果记录需要更改或者想要查看图书的详细信息,可以单击此 按钮进入信息更改页面。图书维护功能页面中用于显示控制器查询结果表格内容的关键代 码如下。 411 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统
书号
书名
出版社
单价
库存数量
维护
${book.bookId } 
${book.bookName } 
${book.bookConcern } 
${book.price } 
${book.bookCount } 
3.控制器设计 整个图书馆管理模块都使用 BookManager 控制器,它继承于 MultiActionController 控制器,并且重写了 handleRequestInternal()方法,将请求的编码方式更改为中文编码, 以解决接收乱码信息的问题。另外BookManager 控制器还定义了很多处理客户请求的方法, 例如:add()、list()、search()、detail()、borrow()、returnBook()、和borrowSearch() 等方法。其中图书维护功能使用到了两个方法,list()和 search()。在访问图书维护页面 时,首先访问 BookManager 的 list()方法,读取所有的图书信息并显示在页面上。其次才 将用户输入的查询条件提交到 search()方法去处理条件查询。BookManager 控制器的关键 代码如下。 例程 18-53:光盘\mr\18\school\src\com\spring\controller\BookManager.java package com.spring.controller; import java.sql.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; import com.hibernate.dao.DAOSupport; import com.hibernate.model.BookBorrowInfo; import com.hibernate.model.BookReginster; import com.hibernate.model.DocuStuInfo; public class BookManager extends MultiActionController { private String addPage; private String searchPage; private String bookDetailPage; private String bookBorrowPage; private String borrowSearchPage; private String borrowReturnPage; private DAOSupport dao; public ModelAndView list(HttpServletRequest req, HttpServletResponse res) { List list = dao.QueryObject("from BookReginster"); return new ModelAndView(getSearchPage(), "result", list); } public ModelAndView search(HttpServletRequest req, HttpServletResponse res) { List list; String condition = req.getParameter("condition"); 动态添加【更改】按钮 412 第 4 篇 项 目 实 践 P A R T 4 if (condition.equals("书号")) condition = "bookId"; else if (condition.equals("书名")) condition = "bookName"; else condition = "bookConcern"; String conditionContent = req.getParameter("conditionContent"); list = dao.QueryObject("from BookReginster where " + condition + " like '" + conditionContent + "%'"); return new ModelAndView(getSearchPage(), "result", list); } ⋯⋯//省略其他处理客户请求的方法 protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse res) throws Exception { req.setCharacterEncoding("gbk"); return super.handleRequestInternal(req, res); } } 4.xml 信息配置 图书馆管理功能的 xml 配置文件中定义了 BookManager 控制器,并注入控制器所依赖 的 dao 属性和 View 属性。另外还定义了控制器所依赖的 MethodResolver 解析器来处理页 面请求和控制器的映射关系。所有对 JSP 文件直接访问都由 ParameterizableViewController 类的控制器简介处理,这样可以避免暴露 JSP 文件,同 时也保持了地址栏 web 路径的一致性。BookLibrary_Config.xml 配置文件关键代码如下。 例程 18-54:光盘\mr\18\school\WebRoot\WEB-INF\BookLibrary_Config.xml ⋯⋯//注入页面Vew值 /bookview/bookReturn /bookview/bookBorrow /bookview/bookReg /bookview/borrowService list add search detail modify borrow returnBook borrowSearch
18.11.3 图书借阅功能设计 图书借阅功能用于记录学生的借阅信息。由于学生人数太多,相对借书量会成倍提升。 将借阅信息保存在系统中,可以节省人力和经费,日后的查找工作也非常快捷。图书借阅 功能的运行页面如图 18.23 所示。 图 18.23 图书借阅页面 414 第 4 篇 项 目 实 践 P A R T 4 1.编写 Hibernate 实体类及映射文件 数据库中的 book_borrow_info 表用于储存图书的借阅信息。对应的实体类为 “BookBorrowInfo.java”文件。其中的属性代码如下。 例程 18-55:光盘\mr\18\school\src\com\hibernate\model\BookBorrowInfo.java private String borrowId; private BookReginster bookReginster; private DocuStuInfo docuStuInfo; private String bookName; private Double price; private String bookType; private Date borrowDate; private Date returnDate; private String czy; 在实体类中包含两个特殊的属性 bookReginster 和 docuStuInfo,它们对应着图书信 息表和学生信息表,所以在BookBorrowInfo.hbm.xml 实体类的映射文件中,需要设置多对 一的表关系映射。实体类的映射文件内容如下。 例程 18-56:光盘\mr\18\school\src\com\hibernate\model\BookBorrowInfo.hbm.xml 设 置 关 联 表 的 多对一关系 415 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 2.页面设计 图书借阅功能界面由 form 表单和一个显示借阅信息的表格所组成。form 表单含有很 多字段,但大多数都是只读的。用户只需要输入学号和书号,其余的字段会自动添加内容。 form 表单的代码如下。 例程 18-57:光盘\mr\18\school\WebRoot\bookview\bookReg.jsp
 学 号:  学生姓名:
 书 号:  书 名:
 作 者:  类别名称:
 操 作 员: 416 第 4 篇 项 目 实 践 P A R T 4  单 价: (元)
 图书简介:  
在填写所有字段信息之后单击【保存】按钮,在 form 表单下方的表格中会显示所有该 学生的借阅信息,包括刚刚保存的借阅信息。表格包含学号、姓名、书号、书名和借阅日 期 5 个字段。其内容使用一个 JSP 的标签显示控制器返回的 Model 中的所有借 阅信息。页面的表格代码如下。
学号
姓名
书号
书名
借阅日期
${list.docuStuInfo.stuId }
${info.stu.name }
${list.bookReginster.bookId }
${list.bookName }
${list.borrowDate }
3.控制器设计 BookManager 是整个图书馆管理模块的控制器,它接收属于图书馆管理模块的所有请 求。当接受到图书借阅的请求时,控制器会调用 borrow()方法根据请求中的学号和书号两 个参数从数据库中读取学生姓名、书号所对应的图书信息和该学生的所有借阅记录,并放 到 Model 中返回给图书借阅功能的页面即视图层。当客户提交表单时,将借阅信息保存到 数据库中。BookManager 控制器的部分代码如下。 417 C H A P T E R 1 8 第 18 章 校 园 管 理 系 统 例程 18-58:光盘\mr\18\school\src\com\spring\controller\SourceAddBatch.java public ModelAndView borrow(HttpServletRequest req, HttpServletResponse res) { String submit = req.getParameter("save"); List list = null; Map model = null; DocuStuInfo stuInfo = null; BookReginster bookDetail = null; String stuId = req.getParameter("stu_id"); String bookID = req.getParameter("book_id"); if (stuId != null && stuId != "") { list = null; list = dao.QueryObject("from DocuStuInfo where stuId=" + stuId); if (list != null && list.size() > 0) stuInfo = (DocuStuInfo) list.get(0); } if (bookID != null && bookID != "") { list = null; list = dao.QueryObject("from BookReginster where bookId='" + bookID + "'"); if (list != null && list.size() > 0) bookDetail = (BookReginster) list.get(0); } if (stuInfo != null) { list = null; list = dao.QueryObject("from BookBorrowInfo where docuStuInfo.stuId='" + stuInfo.getStuId() + "'"); model = new HashMap(); model.put("stu", stuInfo); model.put("book", bookDetail); model.put("list", list); } if (submit != null) { BookBorrowInfo borrowInfo = new BookBorrowInfo(); borrowInfo.setBookName(req.getParameter("book_name")); borrowInfo.setBookReginster(bookDetail); borrowInfo.setBookType("book_type"); borrowInfo.setBorrowDate(new Date(System.currentTimeMillis())); borrowInfo.setCzy(req.getParameter("czy")); borrowInfo.setDocuStuInfo(stuInfo); borrowInfo.setPrice(Double.valueOf(req.getParameter("price"))); dao.InsertOrUpdate(borrowInfo); list = dao.QueryObject("from BookBorrowInfo where docuStuInfo.stuId='" + stuInfo.getStuId() + "'"); model = new HashMap(); model.put("stu", stuInfo); model.put("list", list); } return new ModelAndView(getBookBorrowPage(), "info", model); } 第 19 章 企业进销存管理系统 中小企业在我国经济发展中具有重要地位。随着全球经济一体化 的发展和电子商务的兴起,中小企业之间的竞争将越来越激烈。网络 管理的迅猛发展突破了时间、空间的局限性,给中小企业带来了更多 的发展机会,同时也增大了企业之间的竞争。这就要求中小企业必须 改变企业的经营管理模式,提高企业的运营效率。目前,我国中小企 业的信息化水平还很低,相比国外企业,还处于刚起步阶段。随着技 术发展,电脑操作及管理日趋简化,电脑知识日趋普及,同时市场经 济快速多变,竞争激烈,企业采用电脑管理进货、库存、销售等诸多 环节也已成为趋势及必然。本章使用 Java Swing 和 Spirng 框架开发 了一个简单的企业进销存管理系统,使读者了解如何使用 Spring 开 发应用程序。  操作系统:Windows 2003 Server。  开发工具包:JDK Version 1.5.0。  数据库:SQL Server 2000。  分辨率:最佳效果 1024×768 像素。 19.1 需 求 分 析 通过调查,要求系统具有以下功能。 概 述 开发环境 419 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统  考虑到部分操作人员的计算机知识缺乏,要求有良好的人机交互界面。  由于该系统主要针对公司内部人员,不要求严格的权限管理,但是必须有用户登 录,以区分当前操作员。  方便的数据查询,支持多条件查询。  基础信息管理与查询(包括商品信息、客户信息和供应商信息)。  通过计算机能够直接“透视”仓库存储情况。  完善的商品采购信息、商品销售信息管理。  当外界环境(停电、网络病毒)干扰本系统时,系统可以自动保护原始数据的安全。  数据计算自动完成,尽量减少人工干预。 19.2 系 统 设 计 19.2.1 项目规划 企业进销存管理系统是一个典型的数据库开发应用程序,由基础信息模块、采购管理 模块、库存管理模块、商品销售模块和系统设置等模块组成,规划系统功能模块如下。  基础信息模块 该模块主要管理商品信息录入、客户信息录入、供应商信息录入、商品信息查询、客 户信息查询和供应商信息查询。  采购管理模块 该模块主要管理商品采购信息录入和商品采购信息查询。  库存管理模块 该模块主要管理商品入库信息、商品入库退货、商品库存查询和库存商品价格调整。  商品销售模块 该模块主要管理商品销售信息录入和商品销售退货信息录入。  系统设置模块 该模块主要管理添加操作员、更改操作员密码和删除操作员。 19.2.2 功能结构分析 企业进销存管理系统的功能结构如图 19.1 所示。 图 19.1 企业进销存管理系统功能结构图 420 第 4 篇 项 目 实 践 P A R T 4 19.3 数据库设计 19.3.1 数据表概要说明 为使读者对本系统数据库中的数据表有更清晰的认识,下面给出数据表结构图,如图 19.2 所示,该数据表结构图包含系统所有数据表。 图 19.2 数据表视图 19.3.2 主要数据表的结构 企业进销存管理系统是一个简单的教学实例。它拥有用户表、商品信息表、客户信息 表、供应商信息表、库存表等共计 10 个数据库表。 下面给出主要的数据表结构,如表 19.1~表 19.6 所示。 供应商信息表 tab_gysinfo 主要用于存储供应商详细信息,其结构如表 19.1 所示。 表 19.1 tab_gysinfo 供应商信息表 字段名称 数据类型 字段大小 是否主键 说 明 id varchar 32 主键 供应商编号 name varchar 50 供应商名称 jc varchar 20 供应商简称 address varchar 100 供应商地址 bianma varchar 10 邮政编码 421 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 续表 字段名称 数据类型 字段大小 是否主键 说 明 tel varchar 15 电话 fax varchar 15 传真 lian varchar 8 联系人 ltel varchar 15 联系电话 yh varchar 50 开户银行 mail varchar 30 电子信箱 商品信息表 tab_spinfo 主要用于存储商品详细信息,其结构如表 19.2 所示。 表 19.2 tab_spinfo 商品信息表 字段名称 数据类型 字段大小 是否主键 说 明 id varchar 32 主键 商品编号 spname varchar 50 商品名称 jc varchar 30 商品简称 cd varchar 50 产地 dw varchar 10 商品计量单位 gg varchar 10 商品规格 bz varchar 20 包装 ph varchar 32 批号 pzwh varchar 50 批准文号 memo varchar 100 备注 gysname varchar 50 供应商名称 入库主表 tab_ruku_main 主要用于存储入库单据信息,其结构如表 19.3 所示。 表 19.3 tab_ruku_main 入库主表 字段名称 数据类型 字段大小 是否主键 说 明 rkID varchar 32 主键 入库编号 pzs float 8 品种数量 je money 8 总计金额 sf money 8 实付金额 gysname varchar 100 供应商名称 rkdate datetime 8 入库时间 czy varchar 30 操作员 jsr varchar 30 经手人 jsfs varchar 10 结算方式 入库明细表 tab_ruku_detail 主要用于存储入库详细信息,其结构如表 19.4 所示。 422 第 4 篇 项 目 实 践 P A R T 4 表 19.4 tab_ruku_detail 入库明细表 字段名称 数据类型 字段大小 是否主键 说 明 lsh varchar 50 主键 流水号 ID varchar 30 入库编号 spid varchar 50 商品编号 dj money 8 单价 sl float 8 数量 销售主表 tab_sell_main 主要用于存储销售单据信息,其结构如表 19.5 所示。 表 19.5 tab_sell_main 销售主表 字段名称 数据类型 字段大小 是否主键 说 明 sellID varchar 30 主键 销售编号 pzs float 8 销售品种数 je money 8 总计金额 ss money 8 实收金额 khname varchar 100 客户名称 xsdate datetime 8 销售日期 czy varchar 30 操作员 jsr varchar 30 经手人 jsfs varchar 10 结算方式 销售明细表 tab_sell_detail 主要用于存储销售详细信息,其结构如表 19.6 所示。 表 19.6 tab_sell_detail 销售明细表 字段名称 数据类型 字段大小 是否主键 说 明 lsh varchar 50 主键 流水号 ID varchar 50 销售编号 spid varchar 50 商品编号 dj money 8 销售单价 sl float 8 销售数量 19.4 系统总体架构设计 19.4.1 文件架构设计 企业进销存系统的文件架构图如图 19.3 所示。 423 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 企业进销存管理系统 客户信息 添加对应 KeHuTianJia .java文件 客户信息 查询对应 KeHuChaXun.java 文件 供应商 添加对应 GongYingShang TianJia.java 文件 基本信息 库存管理 商品入库对应 ShangPinRuKu .java文件 库存查询对应 KuCunChaXun .java文件 商品入库查询对应 RuKuChaXun.java文件 商品销售 销售信息查询对应 XiaoShouChaXun .java文件 商品销售对应 ShangPinXiao Shou.java 系统设置 删除操作员对应 ShanChuCaoZuoYuan. java文件 添加操作员对应 TianJiaCaoZuoYuan .java文件 更改密码对应 GengGaiMiMa .java文件 采购管理 商品采购对应 ShangPinCaiGou .java文件 采购查询对应 CaiGouChaXun .java文件 价格调整对应 JiaGeTiaoZheng .java文件 商品信息 添加对应 ShangPinTian Jia.java文件 供应商 查询对应 GongYingShang ChaXun.java文件 商品信息 查询对应 ShangPinChaXun .java文件 图 19.3 系统架构设计图 19.4.2 页面效果图 企业进销存管理系统的运行界面如图 19.4 所示。 图 19.4 企业进销存管理系统启动界面 424 第 4 篇 项 目 实 践 P A R T 4 19.5 系统配置与公共类的设计 19.5.1 数据库操作的核心类 UserDAO 设计 在整个系统开发的过程中,数据库连接是必不可少的,并是公用的。企业进销存管理 系统采用 Hibernate 框架操作数据库。Spring 提供了非常方便的 org.springframework.orm. hibernate3.support.HibernateDaoSupport 抽象类,继承这个类可以编写数据库操作的核 心类(例如 UserDAO)。只要在 Spring 的配置文件中为其注入 HibernateDaoSupport 的属性 Session Factory,便可以使用 HibernateDaoSupport 提供的 getHibernateTemplate()方 法获取 Hibernate Template 的实例进行数据库的增、删、改、查操作。UserDAO 的关键代 码如下。 例程 19-1:光盘\mr\19\EnterpriseJXC\src\dao\UserDAO.java package dao; import java.io.Serializable; import java.util.List; import model.Userlist; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class UserDAO extends HibernateDaoSupport { // 添加或更新数据的方法 public void insertOrUpdate(Object userlist){ getHibernateTemplate().saveOrUpdate(userlist); } // 从数据库中获取查询列表 public List queryObject(String QueryStr){ return getHibernateTemplate().find(QueryStr); } // 通过ID获取单条记录 public Object getObject(Class arg0, Serializable arg1) { return getHibernateTemplate().get(arg0, arg1); } // 删除记录 public void delete(Object arg0) { getHibernateTemplate().delete(arg0); } // 库存管理模块使用的入库方法 public void insertRukuInfo(JTable table, String caoZuoYuan, String gysName, Double je, String jieShuanFangShi, String jingShouRen, Double shiFu) { ⋯⋯// 此处代码省略,详细内容可参见光盘 } } 在系统应用中 UserDAO 类被以依赖注入的方式注入到企业进销存管理系统中的每一个 需要数据库操作的模块中。 425 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 19.5.2 Spring 的 XML 配置文件 applicationContext.xml 是 Spring 的配置文件,其中定义了 UserDAO 所依赖的属性 信息和程序中的所有内部窗体。UserDAO 类的依赖属性包括 SessionFactory,而 SessionFactory 定义了 Hibernate 的属性信息和实体类的映射文件的位置,同时它也依赖 于 dataSource 来定义数据库的连接。DAOProxyTransactionFactory 定义了数据库操作的 声明式事务处理,程序中的 UserDAO 类是通过获得它的实例来使用支持事务的 DAO 来操作 数据库的。Spring 的 XML 配置文件的部分代码如下。 例程 19-2:光盘\mr\19\EnterpriseJXC\src\applicationContext.xml com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://lzwsky:1433;DatabaseName=sell sa true org.hibernate.dialect.SQLServerDialect model/Userlist.hbm.xml model/TabCgdan.hbm.xml model/TabGysinfo.hbm.xml model/TabKhinfo.hbm.xml model/TabKucun.hbm.xml model/TabRkph.hbm.xml 定义数据库连接 的 dataSource 定 义 实 体 类 映 射文件的位置 426 第 4 篇 项 目 实 践 P A R T 4 model/TabRukuDetail.hbm.xml model/TabRukuMain.hbm.xml model/TabSellDetail.hbm.xml model/TabSellMain.hbm.xml model/TabSpinfo.hbm.xml PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly ⋯⋯// 内部窗体的定义信息 19.6 系统登录模块设计 企业进销存管理系统,顾名思义,就是某个企业所使用的商品内部管理系统。既然是 为单个企业所使用,那么就需要根据用户所持有的用户名和密码来登录系统,非企业内部 人士不可能拥有企业的用户名和密码。 系统登录模块负责用户的登录,当用户输入用户名和密码后,登录模块会到数据库的 定义声明式 事务。 427 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 userlist 表中查找与登录的用户名和密码相符的记录,如果确实存在此用户,并且密码相 符,便可以证明是企业的内部人士,允许登录。企业进销存管理系统中的登录界面如图 19.5 所示。 图 19.5 企业进销存管理系统登录界面 1.编写 Hibernate 实体类及映射文件 所有的用户登录信息都存储在数据库的 userlist 表中。它包含用户名、登录名、密码、 权限和用户编号等共 5 个字段。其中用户编号(id)、用户名称(name)和登录名(username) 定义为复合主键。定义userlist 数据表的实体类需要给出这些属性,因为使用了复合主键, 并且把复合主键封装成类更能体现面向对象的编程思想,所以登录模块的实体类把复合主 键值的 id、name 和 username 封装成 UserlistId 类,该类的部分程序代码如下。 例程 19-3:光盘\mr\19\EnterpriseJXC\src\model\UserlistId.java package model; public class UserlistId implements java.io.Serializable { // Hibernate规定复合主键类必须实现Serializable接口并且编写hashCode()与equals()方法。 private Integer id; private String name; private String username; public UserlistId(){ } public UserlistId(Integer id, String name, String username){ this.id = id; this.name = name; this.username = username; } ⋯⋯//省略get()和set()方法 public boolean equals(Object other) { if ((this = = other)) return true; if ((other = = null)) return false; if (!(other instanceof UserlistId)) return false; UserlistId castOther = (UserlistId) other; return ((this.getId() = = castOther.getId()) || (this.getId() != null 编写 equals()方法 识别不同数据 428 第 4 篇 项 目 实 践 P A R T 4 && castOther.getId() != null && this.getId().equals( castOther.getId()))) && ((this.getName() == castOther.getName()) || (this.getName() != null && castOther.getName() != null && this.getName() .equals(castOther.getName()))) && ((this.getUsername() == castOther.getUsername()) || (this .getUsername() != null && castOther.getUsername() != null && this .getUsername().equals(castOther.getUsername()))); } public int hashCode(){ int result = 17; result = 37 * result + (getId() == null ? 0 : this.getId().hashCode()); result = 37 * result + (getName() == null ? 0 : this.getName().hashCode()); result = 37 * result + (getUsername() == null ? 0 : this.getUsername().hashCode()); return result; } } 在 Userlist 类中加入刚刚定义的复合主键类和其他非主键的属性,并编写与属性对应 的 get()和 set()方法。Userlist 类的部分程序代码如下。 例程 19-4:光盘\mr\19\EnterpriseJXC\src\model\Userlist.java package model; public class Userlist implements java.io.Serializable { // 复合主键类的对应类需要实现Serializable接口 private UserlistId id; private String pass; private String quan; public Userlist(){ } public Userlist(UserlistId id, String pass, String quan) { this.id = id; this.pass = pass; this.quan = quan; } ⋯⋯// 省略get()和set()方法 } 在编写 Userlist 实体类的映射文件 Userlist.hbm.xml 时,需要指定 UserlistId 主键 类的信息。映射文件的内容如下。 例程 19-5:光盘\mr\19\EnterpriseJXC\src\model\Userlist.hbm.xml 编写 hashCode() 方法 429 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 2.界面设计 设计登录模块的界面首先要解决背景图片的问题。在输入登录信息时搭配象征着企业 形象的背景或和企业有关的图片信息,可以起到宣传企业的目的。对于程序而言,背景图 片解决了界面的美观问题。登录界面是 Swing 的一个 JFrame 类,模块中采用一个继承 JPanel 面板并重写了 paintComponent()方法的 LoginPanel 类解决了背景问题,它在重写 paint Component()方法的过程中绘制了背景图片信息。 LoginPanel 类的程序代码如下。 例程 19-6:光盘\mr\19\EnterpriseJXC\src\LoginPanel.java import java.awt.Graphics; import java.awt.GridBagLayout; import java.awt.Image; import javax.swing.ImageIcon; import javax.swing.JPanel; public class LoginPanel extends JPanel { protected ImageIcon icon = new ImageIcon("res/login.jpg"); public int width = icon.getIconWidth(), height = icon.getIconHeight(); public LoginPanel(){ super(); setLayout(new GridBagLayout()); setSize(width, height); } protected void paintComponent(Graphics g) { super.paintComponent(g); 指定联合主键 UserlistId 类的 信息 绘制背景图 片信息 430 第 4 篇 项 目 实 践 P A R T 4 Image img = icon.getImage(); g.drawImage(img, 0, 0, null); } } 解决登录界面的背景图片问题之后,还要放置两个用于输入用户名和密码的文本框, 处理登录和退出的两个按钮。登录界面的部分程序代码如下。 例程 19-7:光盘\mr\19\EnterpriseJXC\src\Login.java import java.awt.GridBagLayout; import java.awt.Insets; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextField; ⋯⋯// 此处代码省略,详细内容可参见光盘 public class Login extends JFrame {// 继承JFrame类 private JLabel label; private JLabel label_1; private JButton button_1; private JButton button; private Main window; private void initial(){ setTitle("登录企业进销存管理系统"); final JPanel panel = new LoginPanel(); panel.setLayout(new GridBagLayout()); getContentPane().add(panel); setBounds(300, 200, panel.getWidth(), panel.getHeight()); label = new JLabel(); label.setText("用户名:"); panel.add(label, gridBagConstraints); final JTextField userName = new JTextField(); panel.add(userName, gridBagConstraints_1); ⋯⋯// 此处代码省略,详细内容可参见光盘 label_1 = new JLabel(); label_1.setText("密码:"); panel.add(label_1, gridBagConstraints_2); final JpasswordField userPassword = new JPasswordField(); userPassword.addKeyListener(new KeyAdapter(){ public void keyPressed(final KeyEvent e) { if (e.getKeyCode()= =10) button.doClick(); } }); panel.add(userPassword, gridBagConstraints_3); button = new JButton(); 定义带背景图片的 LoginPanel,并添加 到 JFrame 面板中 添加用于输入用 户名的文本框 添加用于输入 密码的文本框 431 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 button.setText("登录"); panel.add(button, gridBagConstraints_4); ⋯⋯ button_1 = new JButton(); button_1.setText("退出"); panel.add(button_1, gridBagConstraints_5); ⋯⋯// 此处代码省略,详细内容可参见光盘 setVisible(true); setResizable(false); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); } } 3.获取登录模块依赖的属性 Login 类是企业进销存管理系统的登录模块,同时它也是系统的首界面。所以在登 录模块中要获得 Spring 的资源文件(也就是 XML 配置文件)来初始化 Spring 的 ApplicationContext 容器。为了不影响应用程序的启动速度,Login 类在构造方法中首 先启动一个单独的线程来初始化 Spring 的 ApplicationContext 容器,然后再进行界面 的初始化。 构造方法初始化 Spring 容器之后,立刻从容器中获得程序主窗体的实例、记录用户登 录信息的 Bean 的实例和前面提到的数据库操作的核心类 UserDAO。这样,在程序启动的同 时,便加载了 Spring 容器,完成了属性的初始化也提升了程序的启动速度。 Login 类的构造方法代码如下。 例程 19-8:光盘\mr\19\EnterpriseJXC\src\Login.java public Login(){ new Thread(new Runnable(){ public void run() { // 初始化ApplicationContext容器 appContext = new ClassPathXmlApplicationContext( "applicationContext.xml"); // 从容器中初始化window主窗体属性 window = (Main) appContext.getBean("Main"); // 从容器中初始化公共类dao属性 dao = (UserDAO) appContext .getBean("DAOProxyTransactionFactory"); // 从容器中获得记录用户登录信息的loginUser实例 user = (Userlist) appContext.getBean("loginUser"); } }).start(); // 初始化程序界面 initial(); } 在以后几乎所有的窗体类都通过从 Spring 的 ApplicationContext 容器中获得这些属 性的实例来完成数据库操作和用户信息的存取。 432 第 4 篇 项 目 实 践 P A R T 4 4.事件处理 在应用程序中,界面上的每一个部件都包含不同的事件和事件的处理方式。利用这些 事件的捕获和处理,可以完成界面的控制和业务处理。在上述的登录界面中含有文本框的 按键处理事件、登录按钮的处理事件和退出按钮的处理事件。因本章主要是借助这个简单 的实例来讲解 Spring 在应用程序中的使用,所以这里只对登录按钮的处理事件进行讲解。 上述界面的登录按钮的事件处理代码如下。 例程 19-9:光盘\mr\19\EnterpriseJXC\src\Login.java button.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { List list = null; while (dao = = null) { try { Thread.sleep(100); } catch (InterruptedException e1) { e1.printStackTrace(); } } list = dao.queryObject("from Userlist where id.username='" + userName.getText() + "' and pass='" + userPassword.getText() + "'"); if (list.size() < 1) { userName.setText(null); userPassword.setText(null); return; } Userlist loginUser = (Userlist) list.get(0); user.setId(loginUser.getId()); user.setPass(loginUser.getPass()); user.setQuan(loginUser.getQuan()); setVisible(false); window.frame.setVisible(true); } }); 19.7 基本信息模块设计 19.7.1 基本信息模块总体架构 企业进销存的基本信息模块主要完成管理客户、商品、供应商的基本信息的添加与查 询工作。在其他的入库、销售等模块中都需要从基本信息中读取信息。 1.模块功能介绍 基本信息模块的具体功能如下。 单击登录按钮后,程序如果检 测到 dao 属性为空,说明初始 化容器的线程还没有为属性 赋值,程序每间隔 0.1 秒钟会 再次检测 dao 属性,直到 dao 属性被赋值再继续运行程序 正确登录以后,用户 信息保存到容器中 433 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 客户信息添加:用于添加新的客户基本信息。 商品信息添加:用于添加新的商品基本信息。 供应商信息添加:用于添加新的供应商基本信息。 客户信息查询:用于查询客户基本信息。 商品信息查询:用于查询商品基本信息。 供应商信息查询:用于查询供应商基本信息。 2.文件架构 基本信息模块文件架构如图 19.6 所示。 客户信息 添加对应 KeHuTianJia .java文件 客户信息 查询对应 KeHuChaXun.java 文件 供应商 添加对应 GongYingShang TianJia.java 文件 基本信息 商品信息 添加对应 ShangPinTian Jia.java文件 供应商 查询对应 GongYingShang ChaXun.java文件 商品信息 查询对应 ShangPinChaXun .java文件 图 19.6 基本信息模块文件架构 19.7.2 商品添加功能设计 商品添加功能向系统中添加新的商品信息,它记录商品的名称、产地、单位、规格、 供应商名称等信息。商品添加功能的程序运行界面如图 19.7 所示。 图 19.7 商品信息添加界面 1.编写 Hibernate 实体类及映射文件 商品信息存储在数据库中的 tab_spinfo 表中,按照以往的实体类定义方式,定义实体 类的属性和 set()/get()方法。商品信息的实体类部分程序代码如下。 434 第 4 篇 项 目 实 践 P A R T 4 例程 19-10:光盘\mr\19\EnterpriseJXC\src\model\TabSpinfo.java package model; public class TabSpinfo implements java.io.Serializable { private String id; private String spname; private String jc; private String cd; private String dw; private String gg; private String bz; private String ph; private String pzwh; private String memo; private String gysname; ⋯⋯// 省略get()和set()方法 } TabSpinfo.hbm.xml 文件中定义了 TabSpinfo 实体类中的属性与数据库 tab_spinfo 表 中字段的映射关系,其中主键 id 的生成方式定义为 assigned,由程序来为主键赋值。映 射文件的代码如下。 例程 19-11:光盘\mr\19\EnterpriseJXC\src\model\TabSpinfo.hbm.xml 435 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 2.界面设计 商品添加功能的界面由标签、文本框、下拉式组合框组成,实现很简单,只是因属性 的原因使组件布置得较多一些。在下面的程序代码中,只给出了部分组件的定义信息,而 省略了定义方法类似的其他组件定义信息。商品添加功能的部分程序代码如下。 例程 19-12:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinTianJia.java package internalFrame.jiBenXinXi; import java.util.List; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import model.TabSpinfo; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import dao.UserDAO ⋯⋯// 此处代码省略,详细内容可参见光盘 public class ShangPinTianJia extends JInternalFrame implements ApplicationContextAware{ private JComboBox gysQuanCheng; private JTextField beiZhu; private JTextField wenHao; private JTextField piHao; private JTextField baoZhuang; private JTextField guiGe; private JTextField danWei; private JTextField chanDi; private JTextField jianCheng; private JTextField quanCheng; private UserDAO dao; private ApplicationContext appContext; 定义界面上包含 的所有组件属性 436 第 4 篇 项 目 实 践 P A R T 4 public void setApplicationContext(ApplicationContext app) throws BeansException { appContext=app; dao = (UserDAO) appContext.getBean("DAOProxyTransactionFactory"); } public ShangPinTianJia(){ setMaximizable(true); setIconifiable(true); setClosable(true); setTitle("商品信息添加"); getContentPane().setLayout(new GridBagLayout()); setBounds(100, 100, 550, 400); final JLabel label = new JLabel(); label.setFont(new Font("", Font.PLAIN, 14)); label.setText("商品名称:"); getContentPane().add(label, gridBagConstraints); ⋯⋯// 此处代码省略,详细内容可参见光盘 quanCheng = new JTextField(); quanCheng.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(quanCheng, gridBagConstraints_10); ⋯⋯// 此处代码省略,详细内容可参见光盘 final JLabel label_1 = new JLabel(); label_1.setFont(new Font("", Font.PLAIN, 14)); label_1.setText("简 称:"); getContentPane().add(label_1, gridBagConstraints_1); ⋯⋯// 此处代码省略,详细内容可参见光盘 jianCheng = new JTextField(); jianCheng.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(jianCheng, gridBagConstraints_11); ⋯⋯// 此处代码省略,详细内容可参见光盘 final JLabel label_2 = new JLabel(); label_2.setFont(new Font("", Font.PLAIN, 14)); label_2.setText("产 地:"); getContentPane().add(label_2, gridBagConstraints_2); chanDi = new JTextField(); chanDi.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(chanDi, gridBagConstraints_12); ⋯⋯// 省略了部分组件的定义信息 final JLabel label_8 = new JLabel(); label_8.setFont(new Font("", Font.PLAIN, 14)); label_8.setText("供应商全称:"); getContentPane().add(label_8, gridBagConstraints_8); gysQuanCheng = new JComboBox(); gysQuanCheng.setMaximumRowCount(5); gysQuanCheng.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(gysQuanCheng, gridBagConstraints_18); ⋯⋯// 此处代码省略,详细内容可参见光盘 final JButton tjButton = new JButton(); GysQuanCheng 组件是 下拉选择框组件,它的 内容由窗体激活事件 从根据数据库中的信 息来定义 437 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 tjButton.setText("添加"); getContentPane().add(tjButton, gridBagConstraints_20); ⋯⋯// 此处代码省略,详细内容可参见光盘 final JButton resetButton = new JButton(); getContentPane().add(resetButton, gridBagConstraints_21); resetButton.setText("重添"); ⋯⋯// 此处代码省略,详细内容可参见光盘 } } 3.事件处理 商品添加功能的业务逻辑很简单,只是将指定的属性信息添加到数据库中。下面针对 上述的界面设计中包含的事件处理方法作单独讲解。 内部窗体激活事件是在窗体显示时发生的事件。当事件发生时与之对应的窗体监听器 会侦测到该事件的发生,并执行相应的业务逻辑,完成窗体界面的初始化和依赖的属性(例 如操作数据库的 dao 属性)初始化任务。 商品添加功能需要初始化界面中的 gysQuanCheng 组件。它是下拉式的组合框,其内 容是根据数据库的信息动态改变的,所以不可能在界面中把组合框的内容写死,窗体事件 的监听器从数据库中读取所有供应商的名称,并将其放到组合框的模型中,从而实现了组 合框的初始化操作。窗体监听器的程序代码如下。 例程 19-13:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinTianJia.java addInternalFrameListener(new InternalFrameAdapter(){ public void internalFrameActivated(final InternalFrameEvent e) { List list = dao.queryObject("select id.name from TabGysinfo"); String[] gysList = new String[list.size()]; list.toArray(gysList); gysQuanCheng.setModel(new DefaultComboBoxModel(gysList)); } }); 按钮事件是在用户单击界面上的某个按钮时才发生的事件,在界面设计中的 tjButton 组件实际上就是负责将商品信息保存到数据库中的【添加】按钮。如果用户单击了该按钮 或在按钮上回车,将会执行该按钮的监听器中定义的 actionPerformed()方法。 该方法在将商品信息存储到数据库之前要进行数据的简单验证、获得数据库中现存商 品的最大商品编号以确定将要添加到数据库中的商品的编号,最后确定无误后才将信息存 储到数据库中。商品添加功能的【添加】按钮的事件监听器程序代码如下。 例程 19-14:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinTianJia.java tjButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { if(validate()){// 简单验证界面输入的信息 List list = dao.queryObject("select max(id.id) from TabSpinfo"); String spIds = ((String) list.get(0)).trim(); int spId = 1; if (spIds!= null || !spIds.equals("")) 从数据库中确 定商品的编号 438 第 4 篇 项 目 实 践 P A R T 4 spId = Integer.parseInt(spIds.substring(2)) + 1; spIds = "sp" + spId; TabSpinfo spInfo = new TabSpinfo(); saveProduct(spIds, spInfo);// 保存商品信息到数据库中 JOptionPane.showMessageDialog(getContentPane(), "商品信息已经成功添加", "商品添加", JOptionPane.INFORMATION_MESSAGE); resetButton.doClick();// 清除界面中以保存到数据苦中的商品信息。 } } private boolean validate(){ if (baoZhuang.getText().equals("") || chanDi.getText().equals("") || danWei.getText().equals("") || guiGe.getText().equals("") || jianCheng.getText().equals("") || piHao.getText().equals("") || wenHao.getText().equals("") || quanCheng.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请完成未填写的信息。", "商品添加", JOptionPane.ERROR_MESSAGE); return false; } return true; } private void saveProduct(String spIds, TabSpinfo spInfo){ spInfo.setId(spIds); spInfo.setBz(baoZhuang.getText().trim()); spInfo.setCd(chanDi.getText().trim()); spInfo.setDw(danWei.getText().trim()); spInfo.setGg(guiGe.getText().trim()); spInfo.setGysname(gysQuanCheng.getSelectedItem().toString() .trim()); spInfo.setJc(jianCheng.getText().trim()); spInfo.setMemo(beiZhu.getText().trim()); spInfo.setPh(piHao.getText().trim()); spInfo.setPzwh(wenHao.getText().trim()); spInfo.setSpname(quanCheng.getText().trim()); dao.insertOrUpdate(spInfo); } }); 【重添】按钮事件的监听器实现很简单,它将界面中所有的组件初始化,达到清零的目 的,监听器的实现代码如下。 例程 19-15:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinTianJia.java resetButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { baoZhuang.setText(null); validate()方法负责做简单 的数据验证,它将检验界 面中除备注之外的所有组 件的输入值是否为空来确 定验证结果 验证数据并生成商品编号以 后,创建实体类的实例,然后 将商品信息保存在实例中,通 过公共类 UserDAO 的实例 dao 保存实体类到数据库中 439 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 chanDi.setText(null); danWei.setText(null); guiGe.setText(null); jianCheng.setText(null); beiZhu.setText(null); piHao.setText(null); wenHao.setText(null); quanCheng.setText(null); } }); 19.7.3 商品查询功能设计 为方便商品的管理,一般都会搭配一个查询功能来查看商品信息。商品查询功能和商 品添加功能使用相同的数据库表、实体类和映射文件,但它们实现的功能却相反。商品查 询功能的运行界面如图 19.8 所示。 图 19.8 商品信息查询界面 1.界面设计 商品查询功能的界面主要由两个下拉式组合框和一个输入查询内容的文本框构成。【查 询】和【显示全部数据】两个按钮负责执行查询命令,而表格组件负责显示查询的结果集。 商品查询功能的界面代码如下。 例程19-16:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinChaXun.java package internalFrame.jiBenXinXi; import java.util.Vector; import model.TabSpinfo; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import dao.UserDAO; ⋯⋯// 省略其他import public class ShangPinChaXun extends JInternalFrame implements ApplicationContextAware{ private JTable table; private JTextField conditionContent; private JComboBox conditionOperation; 440 第 4 篇 项 目 实 践 P A R T 4 private JComboBox conditionName; private UserDAO dao; private ApplicationContext appContext; public void setApplicationContext(ApplicationContext app) throws BeansException { appContext=app; dao = (UserDAO) appContext.getBean("DAOProxyTransactionFactory"); } public ShangPinChaXun(){ super(); setMaximizable(true); setIconifiable(true); setClosable(true); setTitle("商品信息查询"); getContentPane().setLayout(new GridBagLayout()); setBounds(100, 100, 609, 375); final JScrollPane scrollPane = new JScrollPane(); getContentPane().add(scrollPane, gridBagConstraints_6); ⋯⋯// 此处代码省略,详细内容可参见光盘 table = new JTable(); final DefaultTableModel dftm = (DefaultTableModel) table.getModel(); String[] tableHeads = new String[] { "客户ID", "商品名称", "简称", "产地", "单 位", "规格", "包装", "批号", "批准文号", "供应商全称", "备注", }; dftm.setColumnIdentifiers(tableHeads); scrollPane.setViewportView(table); ⋯⋯// 此处代码省略,详细内容可参见光盘 final JLabel label = new JLabel(); label.setFont(new Font("", Font.PLAIN, 14)); label.setText(" 选择查询条件:"); getContentPane().add(label, gridBagConstraints); ⋯⋯// 此处代码省略,详细内容可参见光盘 conditionName = new JComboBox(); conditionName.setModel(new DefaultComboBoxModel(new String[] { "商品名称", "供应商全称", "产地", "规格" })); conditionName.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(conditionName, gridBagConstraints_1); ⋯⋯// 此处代码省略,详细内容可参见光盘 conditionOperation = new JComboBox(); conditionOperation.setModel(new DefaultComboBoxModel(new String[]{ "等于", "包含" })); conditionOperation.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(conditionOperation, gridBagConstraints_2); ⋯⋯// 此处代码省略,详细内容可参见光盘 conditionContent = new JTextField(); conditionContent.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(conditionContent, gridBagConstraints_3); ⋯⋯// 此处代码省略,详细内容可参见光盘 441 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 final JButton queryButton = new JButton(); getContentPane().add(queryButton, gridBagConstraints_4); queryButton.setFont(new Font("", Font.PLAIN, 12)); queryButton.setText("查询"); ⋯⋯// 此处代码省略,详细内容可参见光盘 final JButton showAllButton = new JButton(); getContentPane().add(showAllButton, gridBagConstraints_5); showAllButton.setFont(new Font("", Font.PLAIN, 12)); showAllButton.setText("显示全部数据"); ⋯⋯// 此处代码省略,详细内容可参见光盘 } ⋯⋯// 此处代码省略,详细内容可参见光盘 } 2.事件处理 商品查询功能中只包含【查询】和【显示全部数据】两个按钮的事件,下面分别对这 两个事件进行讲解。 【查询】按钮事件根据两个下拉式组合框和一个包含查询内容的文本框所组成查询条件 到数据库中获得满足条件的商品记录,然后将查询结果显示在表格中。查询按钮的事件监 听器程序代码如下。 例程19-17:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinChaXun.java queryButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { String conName, conOperation, content; conName = conditionName.getSelectedItem().toString().trim(); conOperation = conditionOperation.getSelectedItem().toString(); content = conditionContent.getText().trim(); List list = null; // 根据查询条件搜索数据库中满足条件的记录 list = searchInfo(conName, conOperation, content, list); // 用搜索的结果集更新表格数据 updateTable(list, dftm); } private List searchInfo(String conName, String conOperation, String content, List list) { if (conOperation.equals("等于")){ if (conName.equals("商品名称")) list = dao.queryObject("from TabSpinfo where id.spname='" + content + "'"); if (conName.equals("供应商全称")) list = dao.queryObject("from TabSpinfo where id.gysname='" + content + "'"); if (conName.equals("产地")) list = dao.queryObject("from TabSpinfo where id.cd='" + content + "'"); 442 第 4 篇 项 目 实 践 P A R T 4 if (conName.equals("规格")) list = dao.queryObject("from TabSpinfo where id.gg='" + content + "'"); } else { if (conName.equals("商品名称")) list = dao.queryObject("from TabSpinfo where id.spname like '%" + content + "%'"); if (conName.equals("供应商全称")) list = dao.queryObject("from TabSpinfo where id.gysname like '%" + content + "%'"); if (conName.equals("产地")) list = dao.queryObject("from TabSpinfo where id.cd like '%" + content + "%'"); if (conName.equals("规格")) list = dao.queryObject("from TabSpinfo where id.gg like '%" + content + "%'"); } return list; } }); 【显示全部数据】按钮事件非常简单,从数据库中提取所有商品信息,然后用返回的结 果集更新表格数据即可。其事件监听器的程序代码如下。 例程 19-18:光盘\mr\19\EnterpriseJXC\src\internalFrame\jiBenXinXi\ShangPinChaXun.java final JButton showAllButton = new JButton(); showAllButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { List list = dao.queryObject("from TabSpinfo"); updateTable(list, dftm); } }); 19.8 商品销售模块设计 19.8.1 商品销售功能模块总体架构 1.模块功能介绍 商品销售功能模块的具体功能如下。 商品销售:用于销售指定商品。 销售查询:用于查询商品的销售记录。................. 2.文件架构 商品销售功能模块文件架构如图 19.9 所示。 443 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 商品销售 销售信息查询对应 XiaoShouChaXun .java文件 商品销售对应 ShangPinXiao Shou.java 图 19.9 商品销售功能模块图 19.8.2 商品销售功能设计 此功能记录商品的销售记录并从现有的库存中将商品数量减少。商品销售功能的程序 运行界面如图 19.10 所示。 图 19.10 商品销售界面 1.编写 Hibernate 实体类及映射文件 商品销售模块对应着数据库中的 tab_sell_main 销售主表和 tab_sell_detail 销售明 细表。销售主表存储每次的销售记录,其中包括客户名称、销售时间、金额、品种数等字 段。销售明细表存储每次销售记录所包含的每种商品的明细信息和销售明细信息,其中包 括销售编号、商品编号、单价和数量等字段。在企业进销存管理系统中创建了对应这两个 表的实体类 TabSellMain 和 TabSellDetail,它们是主表与子表的关系,拥有一对多的关 系映射。主表的实体类 TabSellMain 中的 tabSellDetails 是一个 Set 集合,用于存储 TabSellDetail 子表实体类的引用。实体类 TabSellMain 的部分程序代码如下。 例程 19-19:光盘\mr\19\EnterpriseJXC\src\model\TabSellMain.java package model; import java.util.Date; import java.util.HashSet; import java.util.Set; public class TabSellMain implements java.io.Serializable { private String sellId; private Float pzs; private Double je; 444 第 4 篇 项 目 实 践 P A R T 4 private Double ss; private String khname; private Date xsdate; private String czy; private String jsr; private String jsfs; private Set tabSellDetails = new HashSet(0); ⋯⋯// 省略get()方法和set()方法 public Set getTabSellDetails(){ return this.tabSellDetails; } public void setTabSellDetails(Set tabSellDetails){ this.tabSellDetails = tabSellDetails; } } TabSellMain.hbm.xml 是销售主表的实体类映射文件,它定义主键 id 的生成方式为 “assigned”,即由程序运算生成主键 id 值,其他的属性映射与普通映射文件相同。但是 tabSellDetails 属性要映射为一个 Set 集合并且设置一对多的关系映射,如果定义延迟加 载属性 lazy 值为 false,那么在程序中操作和查询主表和子表中的数据就方便多了,因为 它会把关联的子表数据一起查询出来。销售主表的实体类映射文件代码如下。 例程 19-20:光盘\mr\19\EnterpriseJXC\src\model\TabSellMain.hbm.xml 445 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 子表的实体类中包含了商品信息表和销售主表两个表的关联属性 tabSpinfo 和 tabSell Main。其他的单价和数量属性都是子表中字段的属性定义。销售子表的实体类 TabSellDetail 部分程序代码如下。 例程 19-21:光盘\mr\19\EnterpriseJXC\src\model\TabSellDetail.java package model; public class TabSellDetail implements java.io.Serializable { private String lsh; private TabSpinfo tabSpinfo; private TabSellMain tabSellMain; private Double dj; private Float sl; ⋯⋯// 省略set()和get()方法 } 在子表实体类的映射文件中,定义主键 id 的生成方法为“uuid”,由Hibernate 生成 编码长度为 32 位的惟一值作为主键,例如“4028819b0d62fee9010d62ff563a0001”。子表 实体类 TabSellDetail.hbm.xml 的内容代码如下。 例程 19-22:光盘\mr\19\EnterpriseJXC\src\model\TabSellDetail.hbm.xml 2.界面设计 商品销售功能的界面由一个显示销售内容表格、选择客户信息、商品信息和规格的 3 个下拉式组合框、一些文本框和【销售】、【结帐】两个按钮组成。商品销售功能的程序关 键代码如下。 例程 19-23:光盘\mr\19\EnterpriseJXC\src\internalFrame\shangPinXiaoShou\Shang PinXiaoShou.java package internalFrame.shangPinXiaoShou; ⋯⋯// 省略import代码 public class ShangPinXiaoShou extends JInternalFrame implements ApplicationContextAware { private JTextField shishou; private JTextField shuliang; private JTextField danjia; private JTextField jsr; private JComboBox guige; private JComboBox spName; private JTextField weishou; private JTextField yingshou; private JComboBox jsMode; private JComboBox clientName; private JTable table; private DefaultTableModel dtm; private JLabel sellDate; private JLabel czy; private Userlist user; private UserDAO dao; private ApplicationContext appContext; private Date cellDateTime; private TabSellMain sellMain=new TabSellMain(); private Set tabSellDetails = sellMain.getTabSellDetails(); 定义界面组件 属性 获得 Dao 数据库操 作类和已经登录的 当前用户信息 447 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 public void setApplicationContext(ApplicationContext app) throws BeansException { appContext = app; dao = (UserDAO) appContext.getBean("DAOProxyTransactionFactory"); user = (Userlist) appContext.getBean("loginUser"); } public ShangPinXiaoShou(){ setMaximizable(true); setIconifiable(true); setClosable(true); getContentPane().setLayout(new GridBagLayout()); setTitle("商品销售"); setBounds(100, 100, 647, 375); final JLabel label = new JLabel(); label.setFont(new Font("", Font.PLAIN, 14)); label.setText("操 作 员:"); getContentPane().add(label, gridBagConstraints); ⋯⋯// 省略部分组件定义 } } 3.事件处理 界面的窗体中包含客户全称和商品名称两个下拉式组合框,它们都需要在显示到窗体 上之前,初始化组合框的内容。商品销售功能在内部窗体激活事件中完成对这两个下拉式 组合框的初始化操作,窗体事件监听器程序代码如下。 例程 19-24:光盘\mr\19\EnterpriseJXC\src\internalFrame\shangPinXiaoShou\Shang PinXiaoShou.java addInternalFrameListener(new InternalFrameAdapter(){ public void internalFrameActivated(final InternalFrameEvent e) { czy.setText(user.getId().getName()); new Thread(new Runnable(){ public void run() { while (true) { cellDateTime = new Date(); sellDate.setText(cellDateTime.toLocaleString()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); List list = dao.queryObject("select khname from TabKhinfo"); Iterator iterator = list.iterator(); DefaultComboBoxModel dcbm = (DefaultComboBoxModel) clientName 448 第 4 篇 项 目 实 践 P A R T 4 .getModel(); dcbm.removeAllElements(); while (iterator.hasNext()){ dcbm.addElement(iterator.next().toString().trim()); } list = dao.queryObject("select spname from TabKucun"); iterator = list.iterator(); dcbm = (DefaultComboBoxModel) spName.getModel(); dcbm.removeAllElements(); while (iterator.hasNext()){ dcbm.addElement(iterator.next().toString().trim()); } } }); 在商品销售的窗体界面中还包含一个“规格”下拉式组合框,它的初始化要根据商品 名称的变化而动态改变,所以将它的初始化操作放在了商品下拉组合框的事件中,当用户 改变商品名称的时候,其事件监听器会重新初始化“规格”下拉组合框。事件监听器的程 序代码如下。 例程 19-25:光盘\mr\19\EnterpriseJXC\src\internalFrame\shangPinXiaoShou\Shang PinXiaoShou.java spName.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { List list = dao.queryObject("from TabKucun where spname='" + spName.getSelectedItem() + "'"); DefaultComboBoxModel dcbm = (DefaultComboBoxModel) guige .getModel(); dcbm.removeAllElements(); Iterator iterator = list.iterator(); while (iterator.hasNext()){ dcbm.addElement(((TabKucun) iterator.next()).getGg()); } } }); 当用户单击【销售】按钮的时候,程序会通过表格中的数据计算出客户应交纳的费用。 当用户在窗体中的“实收”文本框中输入实际收取的费用时,以下的按键监听器会结算出 未付款的金额。 例程 19-26:光盘\mr\19\EnterpriseJXC\src\internalFrame\shangPinXiaoShou\Shang PinXiaoShou.java shishou.addKeyListener(new KeyAdapter(){ public void keyReleased(final KeyEvent e) { double ys=Double.parseDouble(yingshou.getText()); double ss=Double.parseDouble(shishou.getText()); weishou.setText((ys-ss)+""); 449 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 } }); 【销售】按钮用于将单条的销售记录暂时保存在窗体界面上的表格中,当用户单击【结 帐】按钮时才真正的将销售记录存储到数据库中。当用户单击【销售】按钮时,事件监听 器首先会简单地验证用户的输入数据是否为空。如果不为空,在把销售记录保存到表格的 同时,也把单条的销售记录添加到一个 Set 集合中,用于生成子表数据。【销售】按钮的事 件监听器程序代码如下。 例程 19-27:光盘\mr\19\EnterpriseJXC\src\internalFrame\shangPinXiaoShou\Shang PinXiaoShou.java cellButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { System.out.println("danjia=" + danjia.getText()); if (danjia.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请填写单价!"); return; } if (shuliang.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请填写数量!"); return; } Vector vector = new Vector(); vector.add(spName.getSelectedItem() + ""); vector.add(guige.getSelectedItem() + ""); vector.add(Double.valueOf(danjia.getText().trim())); vector.add(Integer.valueOf(shuliang.getText().trim())); dtm.addRow(vector); TabSellDetail sellDetail=new TabSellDetail(); int rowNum = dtm.getRowCount(); Double ys = new Double(0); sellDetail.setDj(Double.parseDouble(danjia.getText())); sellDetail.setSl(Float.parseFloat(shuliang.getText())); List list = dao.queryObject("select id from TabKucun where spname='" + spName.getSelectedItem() + "' and gg='" +guige.getSelectedItem() + "'"); sellDetail.setTabSellMain(sellMain); sellDetail.setTabSpinfo(new TabSpinfo(list.get(0) + "")); tabSellDetails.add(sellDetail); for (int i = 0; i < rowNum; i++) { ys = ys + Double.parseDouble(dtm.getValueAt(i, 2) .toString()) * Double.parseDouble(dtm.getValueAt(i, 3) .toString()); } yingshou.setText(ys + ""); shuliang.setText(""); 简单验证输入 数据是否是空 数据 将单条的销售记 录添加到 Set 集 合中 450 第 4 篇 项 目 实 践 P A R T 4 } }); 记录了每种商品的销售信息以后,单击【结帐】按钮将销售数据保存到数据库中。按 钮的事件监听器会根据用户输入的查询条件调用 dao 属性的 queryObject()方法,从数据 库中读取销售记录的最大编号来定义此次销售记录的编号。然后创建一个包含销售信息的 销售主表实体类的实例,将与界面表格同步的 Set 集合添加到实体类中一同保存到数据库 中,完成关联子表的保存操作。【结帐】按钮的事件监听器程序代码如下。 例程 19-28:光盘\mr\19\EnterpriseJXC\src\internalFrame\shangPinXiaoShou\Shang PinXiaoShou.java jiezhangButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { if(table.getRowCount()<1){ JOptionPane.showMessageDialog(getContentPane(), "请填写销售信息!"); return; } if(shishou.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请填写实收金额!"); return; } if(jsr.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请填写销售经手人!"); return; } String date=new java.sql.Date(System.currentTimeMillis())+""; date=date.replace("-", ""); List list = dao.queryObject("select max(id) from TabSellMain" + " where id like '%" + date + "%'"); String suffix; if(list.get(0)==null){ suffix="000"; }else { suffix=list.get(0).toString(); suffix=suffix.substring(suffix.length()-3); int suf = Integer.parseInt(suffix)+1; suffix=suf<10?"00"+suf:suf<100?"0"+suf:suf+""; } sellMain.setSellId(maxId); sellMain.setCzy(user.getId().getName()); sellMain.setJe(Double.valueOf(yingshou.getText())); sellMain.setJsfs(jsMode.getSelectedItem()+""); sellMain.setJsr(jsr.getText()); sellMain.setKhname(clientName.getSelectedItem()+""); sellMain.setPzs(Float.valueOf(table.getRowCount())); sellMain.setSs(Double.valueOf(shishou.getText())); 创建包含销售信 息的主表实体类 的实例,并存储 到数据库中 451 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 sellMain.setXsdate(new Date()); dao.insertOrUpdate(sellMain); JOptionPane.showMessageDialog(getContentPane(), "商品销售完毕!"); int rowCount=dtm.getRowCount(); for(int i=0;i'" + startDate.getText() + "' and tabSellMain.xsdate<'" + endDate.getText()+"'" : "")); Iterator iterator = list.iterator(); updateTable(iterator); } }); 456 第 4 篇 项 目 实 践 P A R T 4 19.9 库存管理模块设计 库存管理主要用于管理库存的商品信息,包括商品入库、商品入库查询、库存查询、 价格调整 4 部分。 19.9.1 库存管理功能模块总体架构 1.模块功能介绍 库存管理功能模块的具体功能如下。 商品入库:把进货商品入库。 商品入库查询:查询商品入库记录。 库存查询:查询当前库存数量。 价格调整:调整库存中不合理的商品价格。 2.文件架构 库存管理功能模块文件架构如图 19.12 所示。 图 19.12 库存管理功能模块图 19.9.2 商品入库功能设计 库存管理功能用于维护系统日常数据,对所有商品的销售、入库和进货进行计算并自 动存储到数据库中。本实例对应其进货功能设计了商品入库,它将进货的商品信息、供应 商信息、操作员等入库记录存储在数据库中。程序运行界面如图 19.13 所示。 1.编写 Hibernate 实体类及映射文件 库存管理模块操作数据库中的库存表 tab_kucun、入库主表 tab_ruku_main 和入库 详细表 tab_ruku_detail 共 3 个数据表。其中入库主表和入库详细表保存着每次入库的 详细记录,库存表主要用于记录商品的库存数量和价格。下面分别介绍这 3 个表的关键 映射代码。 TabRukuMain.hbm.xml 是入库主表的映射文件,其中映射了数据表的所有字段,子表 的关联和一对多关系。最主要的是这段映射代码中设置了 tabRukuDetails 字段的 cascade 属性为 all,使它和入库明细表产生关联操作。关键代码如下。 457 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 图 19.13 商品入库界面 例程 19-32:光盘\mr\19\EnterpriseJXC\src\model\TabRukuMain.hbm.xml ⋯⋯// 此处代码省略,详细内容可参见光盘 对应主表的实体类 TabRukuMain 包含了表中的所有字段的属性并以 Set 集合关联多个 明细表。其关键代码如下。 例程 19-33:光盘\mr\19\EnterpriseJXC\src\model\TabRukuMain.java package model; import java.util.Date; import java.util.HashSet; 设置入库主表的 cascade 属性为 all,这样主表的 Set 集合会和入库明细表 的对象绑定在一起 458 第 4 篇 项 目 实 践 P A R T 4 import java.util.Set; public class TabRukuMain implements java.io.Serializable { // 字段属性 private String rkId; private Float pzs; private Double je; private Double sf; private String gysname; private Date rkdate; private String czy; private String jsr; private String jsfs; private Set tabRukuDetails = new HashSet(0); ⋯⋯// 此处代码省略,详细内容可参见光盘 } TabRukuDetail.hbm.xml 是入库明细表的映射文件,它关联了商品信息表和入库主表 并设置了对应关系。关键代码如下。 例程 19-34:光盘\mr\19\EnterpriseJXC\src\model\TabRukuDetail.hbm.xml 入库明细表的实体类中定义了商品信息表和入库主表的实体类对象的引用,可以通过 对它们的引用获取关联表的数据。关键代码如下。 例程 19-35:光盘\mr\19\EnterpriseJXC\src\model\TabRukuDetail.java package model; 459 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 public class TabRukuDetail implements java.io.Serializable { // 字段属性 private String lsh; private TabSpinfo tabSpinfo; //商品信息表实体对象的引用 private TabRukuMain tabRukuMain; //入库主表实体对象的引用 private Double dj; private Float sl; ⋯⋯// 此处代码省略,详细内容可参见光盘 } TabKucun.hbm.xml 是库存表的映射文件,是只包含一个 ID 主键的普通数据表,它的 映射文件也非常简单,没有数据表的关联和对应关系。关键代码如下。 例程 19-36:光盘\mr\19\EnterpriseJXC\src\model\TabKucun.hbm.xml ⋯⋯// 此处代码省略,详细内容可参见光盘 库存表的实体类中定义了所有字段属性,它是一个含有普通数据类型的实体类,没有 对任何数据表定义引用,其关键代码如下。 例程 19-37:光盘\mr\19\EnterpriseJXC\src\model\TabKucun.java package model; public class TabKucun implements java.io.Serializable { // 字段属性 private String id; private String spname; private String jc; private String cd; private String gg; private String bz; private String dw; private Double dj; private Integer kcsl; ⋯⋯// 此处代码省略,详细内容可参见光盘 } 460 第 4 篇 项 目 实 践 P A R T 4 2.界面设计 商品入库功能的界面由一个表格定义销售的商品列表和两个分别用于选择供应商、结 算方式的下拉式组合框、一些文本框和【添加记录】、【全部入库】两个按钮组成。程序界 面如图 19.13 所示。 3.事件处理 商品入库功能在窗体事件监听器中完成对窗体组件的初始化工作。它使用 UserDAO 类 从数据库中读取所有商品信息和供应商信息来填充界面中的两个下拉式组合框。窗体事件 监听器的关键代码如下。 例程 19-38:光盘\mr\19\EnterpriseJXC\src\internalFrame\kuCunGuanLi\ShangPin RuKu.java addInternalFrameListener(new InternalFrameAdapter(){ public void internalFrameActivated(final InternalFrameEvent e) { int rowC = dftm.getRowCount(); for (int i = 0; i < rowC; i++) dftm.removeRow(0); caoZuoYuan.setText(user.getId().getName()); ruKuShiJian.setText(rkTime.toLocaleString()); List list = dao.queryObject("select id.name from TabGysinfo"); //读取供应 商信息 Iterator iterator = list.iterator(); DefaultComboBoxModel dfcbm = (DefaultComboBoxModel) gysName .getModel(); dfcbm.removeAllElements(); while (iterator.hasNext()){ dfcbm.addElement(iterator.next().toString().trim()); } list = dao.queryObject("select id.spname from TabSpinfo"); //读取商品 信息 dfcbm = (DefaultComboBoxModel) shangPinName.getModel(); dfcbm.removeAllElements(); iterator = list.iterator(); while (iterator.hasNext()){ dfcbm.addElement(iterator.next().toString().trim()); } TableColumn tc1 = table.getColumnModel().getColumn(0); TableColumn tc2 = table.getColumnModel().getColumn(1); tc1.setCellEditor(new DefaultCellEditor(shangPinName)); tc2.setCellEditor(new customCellEditor(dao)); } }); 【全部入库】按钮的事件监听器是商品入库功能的关键所在,它必须完成汇总商品表格 中的所有商品数据、验证输入数据的空值等功能,最后通过UserDAO 类的 insertRukuInfo() 461 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 方法在事务中将数据存入数据库,利用事务保证数据的完整性。该事件监听器的关键程序 代码如下。 例程 19-39:光盘\mr\19\EnterpriseJXC\src\internalFrame\kuCunGuanLi\ShangPin RuKu.java ruKu.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { if (table.getRowCount() < 1) { JOptionPane.showMessageDialog(getContentPane(), "请添加入库记录"); return; } if (shiFu.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请添写实付金额"); return; } if (jingShouRen.getText().equals("")){ JOptionPane.showMessageDialog(getContentPane(), "请添写入库经手人"); return; } String czy=caoZuoYuan.getText().trim(); String gys=gysName.getSelectedItem().toString(); Double je=Double.valueOf(yingFu.getText()); String jsfs=jieShuanFangShi.getSelectedItem() + ""; String jsr=jingShouRen.getText(); Double sf=Double.valueOf(shiFu.getText()); dao.insertRukuInfo(table,czy,gys,je,jsfs,jsr,sf); JOptionPane.showMessageDialog(getContentPane(), "商品已经成功入库。", "商品入库", JOptionPane.INFORMATION_MESSAGE); int rowCou = dftm.getRowCount(); for (int i = 0; i < rowCou; i++) dftm.removeRow(0); yingFu.setText(null); shiFu.setText(null); weiFu.setText(null); jingShouRen.setText(null); } }); 在【全部入库】按钮的事件监听器中调用 UserDAO 类的 insertRukuInfo()方法,这个 方法在 19.5.1 节中已经给出方法定义,但是没有给出方法的实体。下面就此方法实体为内 容讲解企业进销存管理系统中用到的事务操作。 19.5.2 节中的 Spring 的 XML 配置文件中曾提到:DAOProxyTransactionFactory 定义 了数据库操作的声明式事务处理,程序中的 UserDAO 类是通过获得它的实例来使用支持事 务的 DAO 来操作数据库。那么,在 Spring 的配置文件中是如何配置 DAOProxyTransaction Factory 实例的呢。 实例中以 TransactionProxyFactoryBean 类定义事务代理工厂,由这个事务代理工厂 验证输入是否 空数据 调用 UserDao 的 insert RukuInfo()方 法在事务中完成 商品入库的操作 462 第 4 篇 项 目 实 践 P A R T 4 为 UserDAO 类生成支持事务的代理对象。这个代理对象的所有以 insert 和 get 为前缀的方 法都以声明式事务运行。关键代码如下。 例程 19-40:光盘\mr\19\EnterpriseJXC\src\applicationContext.xml PROPAGATION_REQUIRED PROPAGATION_REQUIRED,readOnly 既然定义了 UserDAO 类的 insert 为前缀的方法是事务方法,那么 insertRukuInfo()方 法就是一个事务方法,它的所有操作都是在事务中进行的,如果任何一个操作环节出现异常, 事务会将数据回滚,使数据恢复到事务操作之前的状态。insertRukuInfo()方法负责入库操 作,这个操作影响着 3 个数据表,它需要获取入库主表的最大 ID 值、关联入库明细表,还 要把商品信息和数量更新到库存表中,在事务中执行这段操作是最好的选择。程序关键代码 如下。 例程 19-41:光盘\mr\19\EnterpriseJXC\src\dao\UserDAO.java public void insertRukuInfo(JTable table, String caoZuoYuan, String gysName, Double je, String jieShuanFangShi, String jingShouRen, Double shiFu) { TabRukuMain rkMain = new TabRukuMain(); // 创建入库主表对象 HashSet detailSet = new HashSet(); // 创建保存入库明细表对象的集 合 int rowNum = table.getRowCount(); if (rowNum > 0 && (table.getValueAt(rowNum - 1, 2) == null || table .getValueAt(rowNum - 1, 3) == null)) rowNum--; java.sql.Date theDate = new java.sql.Date(System.currentTimeMillis()); String rkDate = theDate.toString().replace("-", ""); List list = queryObject("select max(rkId) from " + "TabRukuMain " + "where rkId like '%" + rkDate + "%'"); String suffix; if (list.get(0) == null) { suffix = "000"; } else { 根据入库主表的 最大 ID 值确定当 前记录的 ID 463 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 suffix = list.get(0).toString(); suffix = suffix.substring(suffix.length() - 3); int suf = Integer.parseInt(suffix) + 1; suffix = suf < 10 ? "00" + suf : suf < 100 ? "0" + suf : suf + ""; } String rkId = "RK" + rkDate + suffix; rkMain.setRkId(rkId); rkMain.setCzy(caoZuoYuan); rkMain.setGysname(gysName); rkMain.setJe(je); rkMain.setJsfs(jieShuanFangShi); rkMain.setJsr(jingShouRen); rkMain.setPzs(Float.valueOf(rowNum + "")); rkMain.setRkdate(new Date()); rkMain.setSf(Double.valueOf(shiFu)); rkMain.setTabRukuDetails(detailSet); for (int i = 0; i < rowNum; i++) {// 入库明细表赋值 TabKucun kucun; String spName = table.getValueAt(i, 0).toString(); String gg = table.getValueAt(i, 1).toString(); Double dj = Double .valueOf(table.getValueAt(i, 2).toString().trim()); Integer kcsl = Integer.valueOf(table.getValueAt(i, 3).toString() .trim()); list = queryObject("from TabSpinfo where id.spname='" + spName + "' and id.gg='" + gg + "'"); TabSpinfo spinfo = (TabSpinfo) list.get(0); list = queryObject("from TabRukuDetail where tabSpinfo.spname='" + spName + "' and tabSpinfo.gg='" + gg + "'"); TabRukuDetail rkdetail; if (list.size() > 0) rkdetail = (TabRukuDetail) list.get(0); else rkdetail = new TabRukuDetail(); rkdetail.setTabRukuMain(rkMain); rkdetail.setDj(dj); rkdetail.setSl(Float.valueOf(table.getValueAt(i, 3).toString() .trim())); rkdetail.setTabSpinfo(spinfo); detailSet.add(rkdetail); list = queryObject("from TabKucun where id.spname='" + spName + "' and id.gg='" + gg + "'"); if (list != null && list.size() > 0) { kucun = (TabKucun) list.get(0); } else { kucun = new TabKucun(); kucun.setBz(spinfo.getBz()); 设置入库主 表的数据 在 for 循环中填充所有 入库明细表,并把所有 明细表的对象放到入 库主表的 Set 集合中 把每个明细表记录的 商品信息和商品数量 存储到库存表中 464 第 4 篇 项 目 实 践 P A R T 4 kucun.setCd(spinfo.getCd()); kucun.setDj(dj); kucun.setDw(spinfo.getDw()); kucun.setGg(gg); kucun.setJc(spinfo.getJc()); kucun.setSpname(spinfo.getSpname()); kucun.setId(spinfo.getId()); } kucun.setKcsl((kucun.getKcsl() == null ? new Integer(0) : kucun .getKcsl()) + kcsl); insertOrUpdate(kucun); } insertOrUpdate(rkMain); } 19.9.3 库存查询功能设计 库存查询功能可以根据不同的查询条件,查询出各种商品在库存中的数量和单价等信 息,方便查看企业商品的库存情况,以调整商品的供应需求。程序运行界面如图 19.14 所示。 图 19.14 库存查询功能界面 1.编写 Hibernate 实体类及映射文件 库存查询功能从数据库的 tab_kucun 表中检索数据。在程序中对应的 Hibernate 映射 文件是 TabKucun.hbm.xml,其中对应数据表 id 字段的生成由程序管理。tab_kucun 表的 Hibernate 映射代码如下。 例程 19-42:光盘\mr\19\EnterpriseJXC\src\model\TabKucun.hbm.xml 将入库主表保存到数 据库中,其 Set 集合 中的所有明细表数据 都会自动存储 465 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 Hibernate 的映射文件 TabKucun.hbm.xml 映射了数据表和实体类的对应关系,在实体 类中必须包括映射文件中定义的所有属性并且搭配 get()/set()方法,这是一个非常严格 的定义过程。在实体类 TabKucun 中不仅定义了映射文件对应的属性和 get()/set()方法, 还分别定义了根据 id 属性和根据所有属性初始化实体类的两个构造方法。关键代码如下。 例程 19-43:光盘\mr\19\EnterpriseJXC\src\model\TabKucun.java public class TabKucun implements java.io.Serializable { // 定义映射文件对应的属性 private String id; private String spname; private String jc; private String cd; private String gg; private String bz; private String dw; private Double dj; private Integer kcsl; 466 第 4 篇 项 目 实 践 P A R T 4 // Constructors /** default constructor */ public TabKucun(){ } public TabKucun(String id) { this.id = id; } public TabKucun(String id, String spname, String jc, String cd, String gg, String bz, String dw, Double dj, Integer kcsl) { this.id = id; this.spname = spname; this.jc = jc; this.cd = cd; this.gg = gg; this.bz = bz; this.dw = dw; this.dj = dj; this.kcsl = kcsl; } // Property accessors public String getId(){ return this.id; } public void setId(String id) { this.id = id; } public String getSpname(){ return this.spname; } public void setSpname(String spname){ this.spname = spname; } public String getJc(){ return this.jc; } public void setJc(String jc) { this.jc = jc; } public String getCd(){ return this.cd; } public void setCd(String cd) { this.cd = cd; } public String getGg(){ return this.gg; } 467 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 public void setGg(String gg) { this.gg = gg; } public String getBz(){ return this.bz; } public void setBz(String bz) { this.bz = bz; } public String getDw(){ return this.dw; } public void setDw(String dw) { this.dw = dw; } public Double getDj(){ return this.dj; } ⋯⋯//此处代码省略,详细内容参见可光盘 } 2.界面设计 库存查询功能的界面由两个下拉组合框、输入条件的文本框、【查询】和【显示全部数 据】按钮组成。由一个数据表格显示对应的查询结果,其中包括库存编号、商品名称、产 地、规格、单价等信息,关键代码如下。 例程 19-44:光盘\mr\19\EnterpriseJXC\src\internalFrame.kuCunGuanLi.KuCunCha Xun.java package internalFrame.kuCunGuanLi; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; ⋯⋯// 省略imposrt import dao.UserDAO; public class KuCunChaXun extends JInternalFrame implements ApplicationContextAware { private JButton button; private JTable table; private JTextField textField; private JComboBox comboBox_1; private JComboBox comboBox; private UserDAO dao; private Userlist user; private ApplicationContext appContext; public void setApplicationContext(ApplicationContext app) throws BeansException { appContext = app; dao = (UserDAO) appContext.getBean("DAOProxyTransactionFactory"); user = (Userlist) appContext.getBean("loginUser"); 定义界面组件、数据库 操作对象、Application Context 容器等属性 468 第 4 篇 项 目 实 践 P A R T 4 } public KuCunChaXun(){ super(); setMaximizable(true); setIconifiable(true); setClosable(true); setTitle("库存查询"); getContentPane().setLayout(new GridBagLayout()); setBounds(100, 100, 609, 375); //创建数据表格对象 table = new JTable(); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); final DefaultTableModel tm = new DefaultTableModel(new String[]{ "库存编号","商品名称", "简称", "产地", "规格", "包装", "单位", "单价", " 库存数量", "库存金额" }, 0); table.setModel(tm); table.putClientProperty("Quaqua.Table.style", "striped"); //创建标签 final JLabel label = new JLabel(); label.setFont(new Font("", Font.PLAIN, 14)); label.setText(" 选择查询条件:"); final GridBagConstraints gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridy = 0; gridBagConstraints.gridx = 0; getContentPane().add(label, gridBagConstraints); //创建下拉组合框 comboBox = new JComboBox(); comboBox.setModel(new DefaultComboBoxModel(new String[] { "库存编号", "商品名称", "简称", "库存数量" })); comboBox.setFont(new Font("", Font.PLAIN, 14)); final GridBagConstraints gridBagConstraints_1 = new GridBagConstraints(); gridBagConstraints_1.gridy = 0; gridBagConstraints_1.gridx = 1; getContentPane().add(comboBox, gridBagConstraints_1); //创建下拉组合框 comboBox_1 = new JComboBox(); comboBox_1.setModel(new DefaultComboBoxModel(new String[] { "等于", "包含", "大于", "小于" })); comboBox_1.setFont(new Font("", Font.PLAIN, 14)); final GridBagConstraints gridBagConstraints_2 = new GridBagConstraints(); gridBagConstraints_2.gridy = 0; gridBagConstraints_2.gridx = 2; getContentPane().add(comboBox_1, gridBagConstraints_2); //创建文本框 textField = new JTextField(); textField.addKeyListener(new KeyAdapter(){ ⋯⋯//事件代码 关于界面组件的 定位、显示文字、 组件大小等属性 设置的部分代码 469 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 }); textField.setFont(new Font("", Font.PLAIN, 14)); final GridBagConstraints gridBagConstraints_3 = new GridBagConstraints(); gridBagConstraints_3.ipadx = 105; gridBagConstraints_3.insets = new Insets(0, 10, 0, 10); gridBagConstraints_3.fill = GridBagConstraints.HORIZONTAL; gridBagConstraints_3.weightx = 1.0; gridBagConstraints_3.gridy = 0; gridBagConstraints_3.gridx = 3; getContentPane().add(textField, gridBagConstraints_3); button = new JButton(); button.addActionListener(new ActionListener(){ ⋯⋯//事件代码 }); final GridBagConstraints gridBagConstraints_4 = new GridBagConstraints(); gridBagConstraints_4.gridy = 0; gridBagConstraints_4.gridx = 4; getContentPane().add(button, gridBagConstraints_4); button.setFont(new Font("", Font.PLAIN, 12)); button.setText("查询"); final JButton button_1 = new JButton(); button_1.setSelected(true); button_1.addActionListener(new ActionListener(){ ⋯⋯事件代码 }); final GridBagConstraints gridBagConstraints_5 = new GridBagConstraints(); gridBagConstraints_5.insets = new Insets(0, 0, 0, 10); gridBagConstraints_5.anchor = GridBagConstraints.WEST; gridBagConstraints_5.gridy = 0; gridBagConstraints_5.gridx = 5; getContentPane().add(button_1, gridBagConstraints_5); button_1.setFont(new Font("", Font.PLAIN, 12)); button_1.setText("显示全部数据"); final JScrollPane scrollPane = new JScrollPane(); final GridBagConstraints gridBagConstraints_6 = new GridBagConstraints(); gridBagConstraints_6.weighty = 1.0; gridBagConstraints_6.anchor = GridBagConstraints.NORTH; gridBagConstraints_6.insets = new Insets(0, 10, 5, 10); gridBagConstraints_6.fill = GridBagConstraints.BOTH; gridBagConstraints_6.gridwidth = 6; gridBagConstraints_6.gridy = 1; gridBagConstraints_6.gridx = 0; getContentPane().add(scrollPane, gridBagConstraints_6); scrollPane.setViewportView(table); } } 470 第 4 篇 项 目 实 践 P A R T 4 3.事件处理 库存查询功能中包含【显示全部数据】按钮和【查询】按钮的事件,另外还有检查输..................................... 入查询条件文本框的按键事件,它在文本框输入回车时会调用【查询】按钮的事件,文本....................................... 框事件的关键代码如下。........... 例 程.. 19..-.45..: 光 盘...\.mr..\.19..\.EnterpriseJXC.............\.src...\.internalFrame.kuCunGuanLi.KuCunCha.................................. Xun.java........ textField.addKeyListener(new KeyAdapter(){ public void keyPressed(final KeyEvent e) { if(e.getKeyCode()==10)//判断按键是否回车键 button.doClick(); } }); 【显示全部数据】按钮的事件处理需要从数据表中检索出所有的库存数据,然后把数据 显示在数据表格中,其中数据要根据数据表格字段排列好。关键代码如下。 例程 19-46:光盘\mr\19\EnterpriseJXC\src\internalFrame.kuCunGuanLi.KuCunCha Xun.java button_1.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { List list = dao.queryObject("from TabKucun"); Iterator iterator = list.iterator(); updateTable(tm, iterator);//更新数据表格的方法 } }); 【查询】按钮的事件处理要繁琐很多。首先必须判断用户根据什么条件来查询库存,例 如库存编号、商品名称、简称或者库存数量;其次要获取用户选择的查询关系,可供选择 的关系有等于、包括、大于和小于;再次要获取用户输入的查询内容,通过这些条件的组 合从数据库中查询出相关的库存数据;最后把查询出的库存结果更新到显示数据的表格组 件中。关键代码如下。 例程 19-47:光盘\mr\19\EnterpriseJXC\src\internalFrame.kuCunGuanLi.KuCunCha Xun.java button.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { List list; String condition, operation; String content = textField.getText(); switch (comboBox.getSelectedIndex()){ case 0: condition = "id"; break; case 1: 判断用户根据什 么查询库存 471 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 condition = "spname"; break; case 2: condition = "jc"; break; case 3: condition = "kcsl"; break; default: condition = "id"; break; } switch (comboBox_1.getSelectedIndex()){ case 0: operation = "= '"+content+"'"; break; case 1: operation = "like '%"+content+"%'"; break; case 2: if(!condition.equals("kcsl")){ JOptionPane.showMessageDialog(getContentPane(), "只有库存数量能查询大于小于 "); return; } operation = "> "+content; break; case 3: if(!condition.equals("kcsl")){ JOptionPane.showMessageDialog(getContentPane(), "只有库存数量能查询大于小于 "); return; } operation = "< "+content; break; default: operation = "= '"+content+"'"; break; } //根据查询条件从数据库中检索数据 list=dao.queryObject("from TabKucun where "+ condition+" "+operation); updateTable(tm, list.iterator());//更新数据表格 } ); 在【查询】和【显示全部数据】按钮的事件处理中都使用了更新数据表格 updateTable() 方法,这个方法接收表格模型和 List 集合的迭代对象,然后把迭代对象的数据全都放到表 获取用户选择 的查询关系 472 第 4 篇 项 目 实 践 P A R T 4 格的模型中,由表格模型负责把数据显示到界面中。关键代码如下。 例程 19-48:光盘\mr\19\EnterpriseJXC\src\internalFrame\kuCunGuanLi\KuCunCha Xun.java private void updateTable(final DefaultTableModel tm, Iterator iterator){ int num = tm.getRowCount(); for (int i = 0; i < num; i++) tm.removeRow(0); while (iterator.hasNext()){ Vector vector = new Vector(); TabKucun kucun = (TabKucun) iterator.next(); vector.add(kucun.getId().trim()); vector.add(kucun.getSpname().trim()); vector.add(kucun.getJc().trim()); vector.add(kucun.getCd().trim()); vector.add(kucun.getGg().trim()); vector.add(kucun.getBz().trim()); vector.add(kucun.getDw().trim()); vector.add(kucun.getDj()); vector.add(kucun.getKcsl()); vector.add(kucun.getDj() * kucun.getKcsl()); tm.addRow(vector); } } 19.9.4 库存价格调整功能设计 库存价格调整可以调整库存中指定的商品单价,以适应目前销售市场的价格标准,稳 固销售量。程序运行结果如图 19.15 所示。 图 19.15 库存价格调整界面 1.编写 Hibernate 实体类及映射文件 库存价格调整功能和库存查询功能使用同一个数据表 tab_kucun,所以它们使用的 Hivernate 映射文件与实体类都相同,相关代码可以参见例程 19-42。 2.界面设计 库存价格调整功能的界面主要由选择商品的下拉组合框、输入单价的文本框、显示商 473 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 品库存数量的文本框、计算库存金额的文本框和显示商品信息的 Label 标签组成,这些标 签负责显示商品的规格、产地、简称、包装和单位信息。关键代码如下。 例 程 19-49 : 光 盘 \mr\19\EnterpriseJXC\src\internalFrame\kuCunGuanLi\JiaGeTiao Zheng.java package internalFrame.kuCunGuanLi; ⋯⋯//省略import import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import dao.UserDAO; public class JiaGeTiaoZheng extends JInternalFrame implements ApplicationContextAware { private TabKucun kcInfo; private JLabel guiGe; private JTextField kuCunJinE; private JTextField kuCunShuLiang; private JTextField danJia; private JComboBox shangPinMingCheng; private UserDAO dao; private ApplicationContext appContext; // 实现ApplicationContextAware接口的方法 public void setApplicationContext(ApplicationContext app) throws BeansException { appContext=app; dao = (UserDAO) appContext.getBean("DAOProxyTransactionFactory"); } public JiaGeTiaoZheng() { setTitle("价格调整"); setBounds(100, 100, 531, 253); // 定义显示商品名称的标签 final JLabel label = new JLabel(); label.setFont(new Font("", Font.PLAIN, 14)); label.setText("商品名称:"); getContentPane().add(label, gridBagConstraints); // 定义选择商品的下拉组合框 shangPinMingCheng = new JComboBox(); shangPinMingCheng.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(shangPinMingCheng, gridBagConstraints_1); // 定义显示商品规格的标签组件 final JLabel label_1 = new JLabel(); label_1.setFont(new Font("", Font.PLAIN, 14)); label_1.setText("规 格:"); getContentPane().add(label_1, gridBagConstraints_2); guiGe = new JLabel(); guiGe.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(guiGe, gridBagConstraints_3); 定义界面组件、容 器和数据库操作 对象 定义显示名称的 标签组件 定义选择商品的下 拉组合框和显示商 品规格的标签组件 474 第 4 篇 项 目 实 践 P A R T 4 // 定义显示商品产地的标签组件 final JLabel label_2 = new JLabel(); label_2.setFont(new Font("", Font.PLAIN, 14)); label_2.setText("产 地: "); getContentPane().add(label_2, gridBagConstraints_4); // 商品单位标签组件 final JLabel chanDi = new JLabel(); chanDi.setForeground(Color.BLUE); chanDi.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(chanDi, gridBagConstraints_5); // 定义显示商品简称组件的标签 final JLabel label_4 = new JLabel(); label_4.setFont(new Font("", Font.PLAIN, 14)); label_4.setText("简 称:"); getContentPane().add(label_4, gridBagConstraints_6); // 定义显示商品包装的标签 final JLabel label_5 = new JLabel(); label_5.setFont(new Font("", Font.PLAIN, 14)); label_5.setText("包 装:"); getContentPane().add(baoZhuang, gridBagConstraints_9); // 定义显示商品单位的标签 final JLabel label_7 = new JLabel(); label_7.setFont(new Font("", Font.PLAIN, 14)); label_7.setText("单 位:"); getContentPane().add(label_7, gridBagConstraints_10); final JLabel danWei = new JLabel(); danWei.setForeground(Color.BLUE); danWei.setFont(new Font("", Font.PLAIN, 14)); danWei.setText("袋 "); getContentPane().add(danWei, gridBagConstraints_11); // 定义输入单价的文本框组件 final JLabel label_9 = new JLabel(); label_9.setFont(new Font("", Font.PLAIN, 14)); label_9.setText("单 价:"); getContentPane().add(label_9, gridBagConstraints_12); danJia = new JTextField(); danJia.addKeyListener(new KeyAdapter(){ ⋯⋯// 事件代码 }); danJia.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(danJia, gridBagConstraints_13); // 定义显示库存数量的标签组件 定义显示商品 产地和单位的 标签组件 定义显示商品简 称组件的标签 定义显示商品包 装的标签组件 定义显示商品单 位的标签组件 定义输入单价 的文本框组件 475 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 final JLabel label_10 = new JLabel(); label_10.setFont(new Font("", Font.PLAIN, 14)); label_10.setText("库存数量:"); getContentPane().add(label_10, gridBagConstraints_14); kuCunShuLiang = new JTextField(); kuCunShuLiang.addKeyListener(new KeyAdapter(){ ⋯⋯// 事件代码 }); kuCunShuLiang.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(kuCunShuLiang, gridBagConstraints_15); // 定义计算库存金额的文本框组件 final JLabel label_11 = new JLabel(); label_11.setFont(new Font("", Font.PLAIN, 14)); label_11.setText("库存金额:"); getContentPane().add(label_11, gridBagConstraints_16); kuCunJinE = new JTextField(); kuCunJinE.setEditable(false); kuCunJinE.setFont(new Font("", Font.PLAIN, 14)); getContentPane().add(kuCunJinE, gridBagConstraints_17); ⋯⋯此处代码省略,详细代码可参见光盘 } } 3.事件处理 库存价格调整功能在窗体加载事件中读取库存中所有商品名称,用这些商品名称初始 化商品下拉组合框的上下文内容,关键代码如下。 addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameActivated(final InternalFrameEvent e) { DefaultComboBoxModel mingChengModel = (DefaultComboBoxModel) shangPinMingCheng .getModel(); mingChengModel.removeAllElements(); List list = dao.queryObject("select id.spname from TabKucun"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { mingChengModel.addElement(iterator.next()); } } }); 在更改价格调整界面中的单价时,必须根据输入的价格计算库存金额,库存价格调整 功能在输入单价的文本框的按键事件中调用 updateJinE()方法更新库存金额。这个按键事 件在按下键盘上的每一个按键时,都会产生该事件。关键代码如下。 定义显示库存数 量的标签组件和 文本框组件 定义计算库存金 额的标签组件和 文本框组件 476 第 4 篇 项 目 实 践 P A R T 4 danJia.addKeyListener(new KeyAdapter(){ public void keyReleased(final KeyEvent e) { updateJinE(); } }); 事件中调用的 updateJinE()方法的关键代码如下。 private void updateJinE(){ Double dj=Double.valueOf(danJia.getText()); Integer sl=Integer.valueOf(kuCunShuLiang.getText()); kuCunJinE.setText((dj*sl)+""); } 当用户修改了库存中的商品单价时,库存金额会自动更改,单击【确定】按钮时会产 生按钮的单击事件。库存价格调整功能在【确定】按钮的单击事件中修改了库存中对应的 商品单价,然后弹出“价格调整完毕。”对话框,提示用户库存中对应的商品价格已经更改。 关键代码如下。 okButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { kcInfo.setDj(Double.valueOf(danJia.getText())); kcInfo.setKcsl(Integer.valueOf(kuCunShuLiang.getText())); dao.insertOrUpdate(kcInfo); JOptionPane.showMessageDialog(getContentPane(), "价格调整完毕。", kcInfo.getSpname() + "价格调整", JOptionPane.QUESTION_MESSAGE); } }); 选择商品名称的下拉组合框在窗体加裁事件中已经被初始化,它包含库存中所有的商 品名称。库存价格调整功能在这个下拉组合框的更改选项事件中,根据下拉组合框当前选 择的商品名称更新商品的其他信息,例如商品单位、库存数量、商品单价、商品包装等。 关键代码如下。 public void itemStateChanged(final ItemEvent e) { int dj, sl; System.out.println(e.getItem().toString().trim()); List list = dao.queryObject("from TabKucun where id.spname='" + e.getItem() + "'"); kcInfo = (TabKucun) list.get(0); dj = kcInfo.getDj().intValue(); sl = kcInfo.getKcsl().intValue(); chanDi.setText(kcInfo.getCd().trim()); jianCheng.setText(kcInfo.getJc().trim()); baoZhuang.setText(kcInfo.getBz().trim()); danWei.setText(kcInfo.getDw().trim()); danJia.setText(kcInfo.getDj().toString().trim()); 477 C H A P T E R 1 9 第 19 章 企 业 进 销 存 管 理 系 统 kuCunShuLiang.setText(kcInfo.getKcsl().toString().trim()); kuCunJinE.setText(dj * sl + ""); guiGe.setText(kcInfo.getGg()); } ); 【关闭】按钮的单击事件中简单地调用了窗体默认的关闭动作。在这个事件中不能使用 系统的关闭事件,那样会使整个系统都被关闭。关键代码如下。 closeButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { JiaGeTiaoZheng.this.doDefaultCloseAction(); } }); 第 20 章 企业门户网站 企业商务网是企业信息化管理进程中的一个重要的标志,一个 企业的信息化水平是企业综合实力和市场竞争力的基本标志之一, 加强信息化管理是提高企业现代化管理水平和企业生存与发展的关 键。企业商务网的建立不仅从网络上为企业提供了一个新的宣传平 台,而且还标志企业已经跨出了企业信息化管理的第一步。建立健 全的企业商务网已经成为中国企业信息化管理进程中的当务之急。 本章结合了 Struts 和 Spring 建立了一个简单的企业门户网站。  操作系统:Windows 2000 Server  Web 服务器:Tomcat 5.5  开发工具包:JDK Version 1.5,Struts1.1、Spring2.0.1  数据库:SQL Server 2000  浏览器:IE6.0  分辨率:最佳效果 1024×768 像素 20.1 需 求 分 析 企业信息化是指企业利用现代化信息技术,即计算机技术,网络 技术等,对信息资源进行深入开发和广泛利用,以不断提高企业生产、 经营、管理决策的效率和水平,进而提高企业经济效益和企业市场竞 争力的过程。在全球化的浪潮下,中国企业尽管在利用互联网运营方 概 述 开发环境 480 第 4 篇 项 目 实 践 P A R T 4 面有很大的进展,但与全球领先的企业之间仍然存在很大的差距。一个企业的信息化水平 是企业综合实力和市场竞争力的基本标志之一,加强信息化管理,提高企业现代化管理水 平是企业生存与发展的关键。建立健全的企业商务网已经成为中国企业信息化管理进程中 的当务之急。 20.2 系 统 设 计 系统功能结构图如图 20.1 所示。 图 20.1 企业门户网系统功能结构图 20.3 数据库设计 20.3.1 数据表概要说明 为使读者对本系统数据库中的数据表有一个更清晰的认识,笔者设计了一个数据表树 型结构图,如图 20.2 所示,该数据表树型结构图包含系统所有数据表。 图 20.2 数据表树型结构图 20.3.2 主要数据表的结构 结合本系统的实际情况和用户的需求分析,企业门户网站数据库 db_enterpriseweb 481 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 包含了 7 张不同的数据表,其中主要数据表为 tb_Manager 表(管理员信息表)、tb_products 表(产品信息表)、tb_Honour 表(公司荣誉信息表)、tb_instructor 表(技术支持信息表), 其结构如表 20.1~表 20.4 所示。 表 20.1 db_Manager 表 字段名称 数据类型 字段大小 是否主键 说 明 Manager varchar 20 主键 用户名 PWD varchar 20 密码 表 20.2 tb_products 表 字段名称 数据类型 字段大小 是否主键 说 明 ID int 4 主键 自增字段 pname varchar 50 产品名称 introduce varchar 900 产品说明 temperature float 8 温度 pressure float 8 压力 imagepath varchar 50 图片存放路径 表 20.3 tb_Honour 表 字段名称 数据类型 字段大小 是否主键 说 明 ID int 4 主键 自增字段 title varchar 900 公司荣誉 imagepath varchar 50 图片存放路径 表 20.4 tb_instructor 表 字段名称 数据类型 字段大小 是否主键 说 明 ID int 4 主键 自增字段 title varchar 900 技术支持 imagepath varchar 50 图片存放路径 20.4 系统总体架构设计 20.4.1 类文件架构设计 企业门户网站的类文件架构图如图 20.3 所示。 图 20.3 系统架构设计图 482 第 4 篇 项 目 实 践 P A R T 4 20.4.2 页面效果图 企业门户网站的运行界面如图 20.4 所示。 图 20.4 首页运行效果图 20.5 系统配置与公共类的设计 20.5.1 系统文件配置 1.ActionServlet 控制器配置 在整个 Web MVC 架构中,使用者并不直接连接到所需要的资源,而是必须先连接到 ActionServlet 前端控制器(Front controller),由前端控制器判断使用者的请求要分派 给哪一个控制器对象来处理请求。在 Struts 的 Web MVC 框架中,担任前端控制器角色的是 ActionServlet,它负责将客户的请求分配给控制对象。和任何 Servlet 一样, ActionServlet 必须在 Web 应用系统的 web.xml 文件中进行配置,其关键代码如下。 首先定义了一个 ActionServlet 的实例,其名称为 action。 483 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 例程 20-1:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\web.xml action org.apache.struts.action.ActionServlet 然后设置所有以*.do 结尾的 URL 连接请求都由该 Servlet 进行处理。 例程 20-2:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\web.xml action *.do 根据应用系统的各种功能,分解出两个XML 文件配置。在Servlet 中设置 config 参数 来保证这些配置文件都被载入。 例程 20-3:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\web.xml config /WEB-INF/struts-config.xml 2.转发文件 struts-config.xml 配置 Struts 与 Spring 结合使用,主要是让 Struts 知道 Spring 的存在。让 Spring 管理相 关组件,避免在程序中直接编写组件的依赖关系,这就需要在Struts 的 struts-config.xml 中使用标签注册 org.springframework.web.struts.ContextLoaderPlugIn,关 键设置代码如下。 例程 20-4:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\ struts-config.xml 当用户从页面中发出*.do 的请求时,ActionServlet 会将用户的请求与 struts-config.xml 中的某个元素中的 path 属性值相匹配,从而找到 Spring 的 代理类 org.spring framework.web.struts.DelegatingActionProxy,具体设置代码如下。 例程 20-5:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\struts-config.xml 484 第 4 篇 项 目 实 践 P A R T 4 3.底层文件 beans-config.xml 配置 整个系统的数据持久层的控制是通过 Spring 完成的。 定义一个数据源的 JavaBean,名字为 dataSource,它是 Spring 的 JDBC 框架中 DriverManager DataSource 类的一个实例,用于设置连接数据库的不同参数,包括驱动、URL 地址等。 例程 20-6:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\beans-config.xml com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=db_enterpriseweb sa sa 在 Struts 中使用 Action 类的子类,这可以交由Spring 来作依赖注入,可以将Action 类的子类都交由 Spring 管理,关键设置代码如下。 例程 20-7:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\beans-config.xml 20.5.2 数据库操作的核心类设计 数据库操作的核心类主要是指 DAOSupport 类。在这个类中定义了不同的操作方法,系 统的控制层也就是通过调用类中的相应方法来完成数据的写入、读取操作。DAOSupport 类 的具体编写过程如下。 (1)在包 com.dao 下定义一个 DaoSupport 的类并导入相应的类文件,其代码如下。 例程 20-8:光盘\mr\20\ enterpriseweb\src\com\dao\DaoSupport.java import java.util.Iterator; import java.util.List; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.jdbc.core.JdbcTemplate; 485 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 import com.dao.PageBean; (2)根据 JdbcTemplate 获取数据库的连接,其代码如下。 例程 20-9:光盘\mr\20\ enterpriseweb\src\com\dao\DaoSupport.java private JdbcTemplate jtl = null; public JdbcTemplate getJtl(){ retrun jtl; } public JdbcTemplate setJtl(JdbcTemplate jtl){ this.jtl = jtl; } (3)定义一个查询方法 getObjectList(),用于查询满足条件的实体对象,它的参数 sqlSelect 为字符串类型,返回类型为 List 类型对象,其代码如下。 例程 20-10:光盘\mr\20\ enterpriseweb\src\com\dao\DaoSupport.java public List getObjectList(String sqlSelect)( List list = jtl.queryForList(sqlSelect); System.out.println("list:" + list); return list; ) (4)定义一个插入数据的方法,其参数是字符串类型的,不返回数据,其代码如下。 例程 20-11:光盘\mr\20\ enterpriseweb\src\com\dao\DaoSupport.java public void executeSql(String insertSql){ jtl . execute(insertSql); } (5)定义一个控制动态分页的方法,其参数是从Action 类中传入的一些常量和一些变 量,并用它们组成 sql 语句,返回类型为 PageBean 类型,其关键代码如下。 例程 20-12:光盘\mr\20\ enterpriseweb\src\com\dao\DaoSupport.java // 控制动态分页 public PageBean getPageBean(String sql1, String sql2, String sql3, String sql4, String sql5, String tablename, int curPage, int maxRowCount){ // 初始化一个PageBean对象,存储分页数据 PageBean pageBean = new PageBean(Constants.PAGE_LENGTH); try { Vector vec = new Vector(); log.info("执行SQL语句SELECT TOP 查询出前几条语句!"); List list = jtl.queryForList(sql1 + pageBean.rowsPerPage + sql2 + tablename + sql3 + (curPage - 1) * pageBean.rowsPerPage + sql4 + Constants.Products + sql5); int i = 0; Iterator it = list.iterator(); while (it.hasNext()){ if (i > (curPage - 1) * Constants.PAGE_LENGTH - 1) { vec.add(it.next()); } 486 第 4 篇 项 目 实 践 P A R T 4 i++; } pageBean.setMaxRowCount(maxRowCount); System.out.println("记录总条数 " + pageBean.getMaxRowCount()); pageBean.setRowsPerPage(Constants.PAGE_LENGTH); System.out.println("每页的条数 " + pageBean.getRowsPerPage()); pageBean.countMaxPage(); pageBean.curPage = curPage; System.out.println("当前页 " + pageBean.curPage); pageBean.setData(vec); } catch (Exception e) { e.printStackTrace(); } return pageBean; } 20.6 网站前台设计 网站前台的功能模块主要是将企业的基本信息和企业的新闻显示出来,其具体功能如下。  公司简介:对公司基本情况的介绍。  产品介绍:显示公司产品信息。  公司荣誉:显示公司荣誉信息。  售后服务:显示公司服务信息。  技术支持;显示公司技术支持信息。 20.6.1 公司简介模块设计 公司简介模块主要负责向客户描述公司的基本信息,例如成立日期、公司地点、企业 面貌、公司的品牌等信息。 由于公司的这些基本信息都属于固定属性,不需要动态修改,模块的所有信息都集中 在视图文件中,所以不再介绍模块代码。 公司简介模块的运行页面如图 20.5 所示。 图 20.5 公司简介页面 487 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 20.6.2 产品介绍模块设计 公司产品介绍模块主要负责向客户展示产品信息,例如产品名称、适用温度、工作压 力或产品的图样展示等,页面效果如图 20.6 所示。 图 20.6 公司产品信息 下面介绍产品介绍模块的具体编写过程。 1.页面设计 在页面上显示的信息都是从后台数据库中提取出来,然后运用遍历方法在页面上显示。 其关键代码如下。 例程 20-13:光盘\mr\20\ enterpriseweb\WebRoot\produce_jj.jsp <% PageBean pageCtl = (PageBean)request.getAttribute("PageBean"); Vector vec = (Vector)pageCtl.getData(); Iterator it = vec.iterator(); if(it.equals("")||it==null){ out.println("暂时没有商品!"); }else{ while(it.hasNext()){ Map productform = (HashMap)it.next(); %> 488 第 4 篇 项 目 实 践 P A R T 4 <% } } %>
产品名称 产品简介
','','width=700,height=382')"><%=productform.get("pname") %> <%=productform.get("introduce")%>
2.控制器设计 当用户单击首页上的【产品介绍】按钮时,将会向后台发送一个请求的路径,执行 ProductsAction 类中相应的方法,该方法将获取请求中的参数信息,根据获取的参数确定 分页结果。关键代码如下。 例程 20-14:光盘\mr\20\ enterpriseweb\src\com\action\ProductsAction.java // 控制动态分页 public ActionForward view(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); request.setCharacterEncoding("GB2312"); // 获取跳转的页面 String action = request.getParameter("action"); System.out.println("页面跳转变量 " + action); /* 获取参数值:跳转到的页码编号 */ int curPage; String pageNum = request.getParameter("jumpPage"); if (pageNum == null || pageNum.equals("")){ curPage = 1; } else { curPage = Integer.parseInt(pageNum); } log.info("过去所有的记录"); int maxRowCount = dao.totalPage("SELECT count(*) FROM tb_products"); log.info(" view():页面跳转第" + curPage + "页!!!"); try { log.info("将查询的值存入request中!"); if(curPage>1){ PageBean pb=dao.getPageBean( Constants.Product_sql1, Constants.Product_sql2, Constants.Product_sql3, Constants.Product_sql4,Constants.Product_sql5, Constants.Products, curPage, maxRowCount); 489 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 System.out.println(pb.getMaxRowCount()); request.setAttribute("PageBean", pb); }else{ String sql = "SELECT top " + Constants.PAGE_LENGTH + " ID, pname, introduce, temperature, pressure, imagepath FROM tb_products"; PageBean pb=dao.getPageBean1(sql, curPage, maxRowCount); request.setAttribute("PageBean", pb); } } catch (Exception e) { System.out.println("view " + e.getMessage()); } if (action.equals("zs")){ return mapping.findForward("produce_zs"); } else { if (action.equals("jj")){ return mapping.findForward("produce_jj"); } else { return mapping.findForward("produce_user"); } } }  注意 上述代码中有分页的操作,到第一页和到其它的页所执行的 sql 语句是不一样的。请 读者参考光盘。 从上述代码中读者会发现,在调用 dao 的方法时传入了大量的参数,这些参数主要是 为了构成 sql 语句,它们都在 Constants 类中。其关键代码如下。 例程 20-15:光盘\mr\20\ enterpriseweb\src\com\dao\Constants.java package com.dao; import java.io.Serializable; public class Constants implements Serializable { // 用于分页显示中的每页显示的数量 public static final int PAGE_LENGTH = 6; //分页查询中要用的sql语句 public static final String Product_sql1 = "SELECT top "; public static final String Product_sql2 = " ID, pname, introduce, temperature, pressure, imagepath FROM "; public static final String Product_sql3 = " WHERE ID>(SELECT MAX(ID) from (SELECT top "; public static final String Product_sql4 = " ID FROM "; public static final String Product_sql5 = " ) as aaa) ORDER BY ID"; public static final String Honour_sql1 = "SELECT top "; public static final String Honour_sql2 = " ID, title, imagepath FROM "; public static final String Honour_sql3 = " WHERE ID>(SELECT MAX(ID) FROM (SELECT top "; public static final String Honour_sql4 = " ID FROM "; 490 第 4 篇 项 目 实 践 P A R T 4 public static final String Honour_sql5 = " ) as aaa) ORDER BY ID"; public static final String Instructor_sql1 = "SELECT top "; public static final String Instructor_sql2 = " ID, title, imagepath FROM "; public static final String Instructor_sql3 = " WHERE ID>(SELECT MAX(ID) FROM (SELECT top "; public static final String Instructor_sql4 = " ID FROM "; public static final String Instructor_sql5 = " ) as aaa) ORDER BY ID"; //数据库名称 public static final String Products = " tb_products "; public static final String Honour = " tb_Honour "; public static final String Instructor = " tb_instructor "; } 3.DAOSupport 类的设计 控制器只用于获取请求参数、组织 SQL 语句并控制转发操作,真正业务操作是由 DAOSupport 类负责执行的。DAOSupport 类中的 getPageBean()方法负责数据查询,并将查 询的数据存入一个分页对象中。其关键代码如下。 例程 20-16:光盘\mr\20\ enterpriseweb\src\com\dao\DaoSupport.java // 控制动态分页 public PageBean getPageBean(String sql1, String sql2, String sql3, String sql4, String sql5, String tablename, int curPage, int maxRowCount){ // 初始化一个PageBean对象,存储分页数据 PageBean pageBean = new PageBean(Constants.PAGE_LENGTH); try { Vector vec = new Vector(); log.info("执行SQL语句SELECT TOP 查询出前几条语句!"); List list = jtl.queryForList(sql1 + pageBean.rowsPerPage + sql2 + tablename + sql3 + (curPage - 1) * pageBean.rowsPerPage + sql4 + Constants.Products + sql5); System.out.println(list.size()); System.out.println(sql1 + pageBean.rowsPerPage + sql2 + tablename + sql3 + (curPage - 1) * pageBean.rowsPerPage + sql4 + Constants.Products + sql5); int i = 0; Iterator it = list.iterator(); while (it.hasNext()){ if (i > (curPage - 1) * Constants.PAGE_LENGTH - 1) { log.info("到此!"); // String[] productForm = (String[]) it.next(); // System.out.println(productForm); vec.add(it.next()); } i++; } pageBean.setMaxRowCount(maxRowCount); 491 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 System.out.println("记录总条数 " + pageBean.getMaxRowCount()); pageBean.setRowsPerPage(Constants.PAGE_LENGTH); System.out.println("每页的条数 " + pageBean.getRowsPerPage()); pageBean.countMaxPage(); pageBean.curPage = curPage; System.out.println("当前页 " + pageBean.curPage); pageBean.setData(vec); } catch (Exception e) { // log.error("数据库操作发生异常!!!"); // System.out.println("pagebean + " + e.getMessage()); e.printStackTrace(); } return pageBean; } 在上面的代码中,将数据查询结果存入了一个 PageBean 类中。PageBean 类是存储分 页信息的数据模型。其程序代码如下。 例程 20-17:光盘\mr\20\ enterpriseweb\src\com\dao\PageBean.java package com.dao; import java.util.Vector; public class PageBean { /*当前页码*/ public int curPage; /*总页数*/ public int maxPage; /*信息总条数*/ public int maxRowCount; /*每页的信息条数*/ public int rowsPerPage; /*信息内容的容器类*/ public Vector data; //默认的构造器 public PageBean(){ super(); } public PageBean(int rpp) { this.rowsPerPage=rpp; } //本方法用于计算总页数 public void countMaxPage() { this.maxPage=(this.maxRowCount+this.rowsPerPage-1)/this.rowsPerPage; } //获取信息内容 public Vector getData(){ return data; 492 第 4 篇 项 目 实 践 P A R T 4 } public void setData(Vector data) { this.data = data; } public void setMaxRowCount(int maxRowCount){ this.maxRowCount = maxRowCount; } public int getMaxRowCount(){ return maxRowCount; } public int getRowsPerPage(){ return rowsPerPage; } public void setRowsPerPage(int rowsPerPage){ this.rowsPerPage = rowsPerPage; } } 4.struts-config.xml 文件配置 在 struts-config.xml 文件中配置模块所要跳转的页面,其关键代码如下。 例程 20-18:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\struts-config.xml 20.6.3 公司荣誉模块设计 公司荣誉模块用于向客户展示企业曾经荣获的各种荣誉证书,体现有关部门对公司的 实力和技术能力的认可。公司荣誉页面如图 20.7 所示。 1.页面设计 页面从后台数据库中提取出企业曾经获得的所有荣誉证书的图片和证书的描述信息, 然后运用遍历方法在页面上分页显示,其关键代码如下。 493 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 图 20.7 公司荣誉页面 例程 20-19:光盘\mr\20\ enterpriseweb\WebRoot\honour.jsp <% PageBean pageCtl = (PageBean)request.getAttribute("PageBean"); Vector vec = (Vector)pageCtl.getData(); if(vec.equals("")||vec==null){ out.println("暂时没有公司荣誉!"); }else{ Iterator it = vec.iterator(); while(it.hasNext()){ Map honourform = (HashMap)it.next(); %>
<% } }%>
494 第 4 篇 项 目 实 践 P A R T 4
"/>
<%=honourform.get("title")%>

每页<%=pageCtl.getRowsPerPage()%> 行  共<%=pageCtl.getMaxRowCount()%>行  第<%=pageCtl.curPage%> 页  共<%=pageCtl.maxPage%>页   <%if (pageCtl.curPage = = 1) { out.print(" 首页 上一页"); } else {%> 首页 上一页 <%}%> <%if (pageCtl.curPage = = pageCtl.maxPage){ out.print("下一页 尾页"); } else {%> 下一页 尾页 <%}%> 转到第
495 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 2.控制器设计 公司荣誉页面中显示的荣誉证书和描述信息都是由 HonourAction 类控制器的 view()方 法负责从数据库中检索的,该方法根据请求中的参数判断分页显示的结果。关键代码如下。 例程 20-20:光盘\mr\20\enterpriseweb\src\com\action\HonourAction.java public ActionForward view(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GB2312"); /* 获取参数值:跳转到的页码编号 */ int curPage; String pageNum = request.getParameter("jumpPage"); if (pageNum == null || pageNum.equals("")){ curPage = 1; } else { curPage = Integer.parseInt(pageNum); } log.info("过去所有的记录"); int maxRowCount = dao.totalPage("SELECT count(*) FROM tb_Honour"); log.info(" view():页面跳转第" + curPage + "页!!!"); try { log.info("将查询的值存入request中!"); if(curPage>1){ PageBean pb=dao.getPageBean( Constants.Honour_sql1, Constants.Honour_sql2, Constants.Honour_sql3, Constants.Honour_sql4,Constants.Honour_sql5, Constants. Honour, curPage, maxRowCount); request.setAttribute("PageBean", pb); }else{ String sql = "SELECT top " + Constants.PAGE_LENGTH + " ID, title, imagepath FROM tb_Honour"; PageBean pb=dao.getPageBean1(sql, curPage, maxRowCount); request.setAttribute("PageBean", pb); } } catch (Exception e) { System.out.println("view " + e.getMessage()); } return mapping.findForward("honour"); } 3.struts-config.xml 文件配置 在 struts-config.xml 文件中配置公司荣誉模块所要跳转的所有页面,它们将在控制 器中被调用。其关键代码如下。 例程 20-21:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\struts-config.xml 496 第 4 篇 项 目 实 践 P A R T 4 20.6.4 售后服务模块设计 售后服务模块向用户说明购买产品之后应该注意的使用事项和安装方法,另外它还对 用户提供了检修要求和质量服务跟踪卡服务。 该模块主要负责向客户提供固定的服务描述信息,是企业固定的服务流程,不需要动 态修改,所以这些描述信息都被定义在视图文件中。这些视图文件只是负责简单的信息显 示,这里不再介绍文件代码,读者可从光盘中获得视图文件并查看相关代码。 售后服务页面如图 20.8 所示。 图 20.8 售后服务页面 20.6.5 技术支持模块设计 技术支持模块为客户提供了相关产品的技术信息,例如产品安装图和结构图等。技术 支持页面如图 20.9 所示。 1.页面设计 技术支持在页面上显示的产品安装图和结构图等信息都是从后台数据库中提取出来 的,并运用遍历方法在页面上显示。其关键代码如下。 497 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 图 20.9 技术支持页面 例程 20-22:光盘\mr\20\ enterpriseweb\WebRoot\instructor.jsp <%PageBean pageCtl = (PageBean)request.getAttribute("PageBean"); Vector vec = (Vector)pageCtl.getData(); if(vec.equals("")||vec==null){ out.println("暂时没有技术支持!"); }else{ Iterator it = vec.iterator(); while(it.hasNext()){ Map instructorform = (HashMap)it.next();%>
<% } }%>
498 第 4 篇 项 目 实 践 P A R T 4
"/>
<%=instructorform.get("title")%>

每页 <%=pageCtl.getRowsPerPage()%>行  共 <%=pageCtl.getMaxRowCount()%>行  第 <%=pageCtl.curPage%>页  共 <%=pageCtl.maxPage%>页   <%if (pageCtl.curPage = = 1) { out.print(" 首页 上一页"); } else {%> 首页 上一页 <%}%> <%if (pageCtl.curPage = = pageCtl.maxPage){ out.print("下一页 尾页"); } else {%> 下一页 尾页 <%}%>转到第
2.控制器设计 技术支持有关的请求由 InstructorAction 类控制器负责所有业务流程的控制,其中 view()方法负责为客户显示技术支持信息的页面提供显示的数据,它根据请求中的参数信 499 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 息把数据进行了分页。关键代码如下。 例程 20-23:光盘\mr\20\enterpriseweb\src\com\action\InstructorAction.java public ActionForward view(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GB2312"); /* 获取参数值:跳转到的页码编号 */ int curPage; String pageNum = request.getParameter("jumpPage"); if (pageNum = = null || pageNum.equals("")){ curPage = 1; } else { curPage = Integer.parseInt(pageNum); } log.info("过去所有的记录"); int maxRowCount = dao.totalPage("SELECT count(*) FROM tb_instructor"); log.info(" view():页面跳转第" + curPage + "页!!!"); try { log.info("将查询的值存入request中!"); if(curPage>1){ PageBean pb=dao.getPageBean( Constants.Instructor_sql1, Constants.Instructor_sql2, Constants.Instructor_sql3, Constants.Instructor_sql4,Constants.Instructor_sql5, Constants.Instructor, curPage, maxRowCount); System.out.println(pb.getMaxRowCount()); request.setAttribute("PageBean", pb); }else{ String sql = "SELECT top " + Constants.PAGE_LENGTH + " ID, title, imagepath FROM tb_instructor"; PageBean pb=dao.getPageBean1(sql, curPage, maxRowCount); request.setAttribute("PageBean", pb); } } catch (Exception e) { System.out.println("view " + e.getMessage()); } return mapping.findForward("instructor"); } 3.struts-config.xml 文件配置 在 struts-config.xm 文件中配置技术支持模块块所要跳转的所有页面,它们将在控制 器中被调用。其关键代码如下。 例程 20-24:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\struts-config.xml 20.7 网站后台设计 网站的后台主要完成对系统中各种功能的设置与维护,它是整个系统的基础部分,只 有存在这些基础数据才能够操作其他模块。网站后台设计主要包括以下功能设置。  系统登录:用于登录网站后台。  公司产品管理:用于维护公司的产品信息。  公司荣誉管理:用于维护公司荣誉信息。  技术支持管理:用于维护公司技术支持信息。 20.7.1 系统登录模块设计 本实例在网站前台的首页面中建立了后台登录的连接,这个连接比较隐秘,主要是方 便管理员登录使用,有了这个登录连接可以省去在地址栏输入后台登录的 URL 地址。在前 台首页面的右下角有一个 TOP 连接,如图 20.10 所示,在这个连接的右侧有一条横线,图 中已经用圆圈标注,这条横线就是进入网站后台的连接。 系统登录模块是企业门户网站中最先使用的功能,是进入系统的入口。在系统登录页 面中,系统管理人员可以通过输入正确的管理员名称和密码进入到系统。当用户没有输入 管理员名称或密码时,系统会禁止访问系统功能,并给予提示信息。系统登录模块位于右 下角,其运行结果如图 20.11 所示。 图 20.10 网站后台连接 图 20.11 网站后台登录界面 501 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 1.编写 ManagerForm 实体类 登录模块在数据库中对应 tb_Manager 表,其中包含 id、username 和 password 字段。 在编写 ManagerForm 实体类时要定义对应 tb_Manager 表的 4 个属性和 get/set 方法。在登 录模块中定义 tb_Manager 表的实体类 ManagerForm,程序代码如下。 例程 20-25:光盘\mr\20\ enterpriseweb\WebRoot\src\com\form\ManagerForm.java package com.form; import org.apache.struts.action.ActionForm; public class ManagerForm extends ActionForm { private String manager = null; private String pwd = null; public void setManager(String manager){ this.manager = manager; } public String getManager(){ return manager; } public void setPwd(String pwd){ this.pwd = pwd; } public String getPwd(){ return pwd; } } 定义 ManagerForm 实体类之后,如果要让Struts 知道 ManagerForm 实体类的存在,就 必须要在 strut-config.xml 中对 ManagerForm 进行配置,这样用户在登录的时候才能把登 录的信息自动映射到 ManagerForm 类中,其配置代码如下。 例程 20-26:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\struts-config.xml 2.页面设计 Login_M.jsp 是登录页面文件,页面中有一个 form 表单,其中包含用户名和密码两个 文本框和登录按钮,在登录成功后页面会显示登录成功信息而不再是 form 表单。 Login_M.jsp 代码如下。 例程 20-27:光盘\mr\20\ enterpriseweb\WebRoot\Login_M.jsp form method="post" action="Manager_Login.do" name="ManagerForm"> 502 第 4 篇 项 目 实 践 P A R T 4
       
    管理员:  
    密  码:  
如果登录成功页面将跳转到后台管理主页面;如果用户的登录信息有误,那么页面将 跳转到 error.jsp 页面。error.jsp 代码如下。 例程 20-28:光盘\mr\20\ enterpriseweb\WebRoot\error.jsp
登录出错!
请重新登录
3.控制器设计 登录页面提交登录信息后,由 ManagerAction 控制器处理登录的业务逻辑,接收到登 录请求后,ManagerAction 控制器将根据请求中的用户名和密码到数据库中寻找用户记录, 如果该用户名存在并且密码符合,会取出用户名,设置为Session 中 managerLogin 属性的 值,最后返回登录成功页面。控制器 ManagerAction 程序代码如下。 例程 20-29:光盘\mr\20\ enterpriseweb\src\com\action\ManagerAction.java package com.action; import java.util.*; import java.servlet.http.*; import org.apache.commnos.logging.*; import org.apache.struts.action.*; import com.dao.DaoSupport; import com.form.ManagerForm; 503 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 public class ManagerAction extends Action { protected static final Log log = LogFactory.getLog(ManagerAction.class .getName()); // 管理员登录 private ManagerForm managerForm = null; private DaoSupport dao = null; public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); log.info("获取管理员信息"); managerForm = (ManagerForm) form; System.out.println(managerForm.getManager()); System.out.println(managerForm.getPwd()); log.info("所要执行的sql语句"); String SelSql = "SELECT * FROM tb_Manager WHERE Manager = '" + managerForm.getManager() + "' AND PWD = '" + managerForm.getPwd() + "'"; log.info("执行查询操作!"); List list = dao.getObjectList(SelSql); System.out.println(list.size()); log.info("执行验证操作!"); if (list.size() > 0) { log.info("正常登录!"); try{ log.info("数据迭代"); Iterator it = list.iterator(); while(it.hasNext()){ Map managerform = (HashMap)it.next(); session.setAttribute("managerLogin", managerform.get("Manager")); } }catch(Exception e){ System.out.println(e.getMessage()); } } else { return (new ActionForward(mapping.getInput())); } return mapping.findForward("success"); } public DaoSupport getDao(){ return dao; } public void setDao(DaoSupport dao) { this.dao = dao; } } 504 第 4 篇 项 目 实 践 P A R T 4 4.XML 信息配置 ManagerAction 是处理简单登录逻辑的控制器,但是它并不能直接接收到客户的登录 请求,要在 struts-conig.xml 中对它进行配置才能接收到客户的请求。其关键代码如下。 例程 20-30:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\struts-config.xml 在上面的代码中,通过/Manager_Login 并不能直接找到 ManagerAction 类,而是先找 到 Spring 的一个代理类 DelegatingActionProxy,通过它在 Spring 的 bean-config.xml 配置文件中找到 ManagerAction 类。Bean-config.xml 的关键代码如下。 例程 20-31:光盘\mr\20\ enterpriseweb\WebRoot\WEB-INF\Bean-config.xml 上述的登录模块中,贯穿整个流程的就是/Manager_Login。用户在登录页面填写完登 录信息后,系统会根据这个请求找到 ManagerForm 类和 ManagerAction 类,执行验证操作。 验证完毕后,会根据ManagerAction 类的 mapping.findForward()方法中的参数在 struts- config.xml 中找到需要跳转的页面。 20.7.2 产品管理模块设计 用户登录成功之后单击“产品管理”连接,会出现其功能菜单,它主要包括产品添加、 产品修改和产品删除 3 个功能,功能菜单的运行效果如图 20.12 所示。 图 20.12 产品管理 505 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 下面介绍产品管理模块的具体实现过程。 1.编写 ProductForm 表单类 ProductForm 表单类用于保存公司产品信息,在这个类中定义了一些基本类型的属性, 以及用于设置这些属性的 get()/set()方法。关键代码如下。 例程 20-32:光盘\mr\20\ enterpriseweb\src\com\form\ProductForm.java package com.form; import org.apache.struts.action.ActionForm; import org.apache.struts.upload.FormFile; public class ProductForm extends ActionForm { private FormFile imageFile; private Integer id; private String pname; private String introduce; private Float temperature; private Float pressure; private String imagepath; public FormFile getImageFile(){ return imageFile; } public void setImageFile(FormFile imageFile){ this.imageFile = imageFile; } ⋯⋯//省略了其它属性的get/set方法 } 2.页面设计 产品管理模块包含 4 个页面,分别用于产品信息的添加、修改和删除操作,现分别介 绍如下。 (1)product_add.jsp 主要完成公司产品信息的录入存盘操作,其中定义了产品名称、 产品简介、适用温度、工作压力文本框和选择产品图片的文件选择框。关键代码如下。 例程 20-33:光盘\mr\20\enterpriseweb\WebRoot\WEB-INF\product_add.jsp
产品名称: *
产品简介: *
适用温度: (℃)   *
工作压力: (MPa)   *
产品图片:
 
509 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 3.控制器设计 (1)控制器 ProductsAction 继承了 DispatchAction 基类,在其中定义方法 addproduct()来执行公司产品信息的插入操作。在该方法中调用 dao 中的 executeSql 方法 来将数据插入到数据库的表中,最后返回 ActionForward 对象,并找到所对应的页面,其 代码如下。 例程 20-37:光盘\mr\20\enterpriseweb\src\com\action\ProductsAction.java public ActionForward addproduct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GB2312"); productform = (ProductForm) form; String dir = servlet.getServletContext().getRealPath("/uploadImage"); UploadBean upb = new UploadBean(); try { // 通过该方法上传图片并将图片名称及大小存放到form中 upb.upload(dir, productform, "productForm"); } catch (Exception ex) { System.out.println("------在上传图片时抛出异常,内容如下:"); ex.printStackTrace(); } String addSql = "insert into tb_products(pname,introduce,temperature,pressure,imagepath) values ("; addSql = addSql + "'" + productform.getPname().trim() + "','" + productform.getIntroduce().trim() + "'," + productform.getTemperature() + "," + productform.getPressure() + ",'" + upb.getFileName(productform, "ProductForm") + "')"; dao.executeSql(addSql); request.setAttribute("name", "add"); return mapping.findForward("addsucess"); (2)在执行删除和修改操作的时候,都要先把所有的公司产品信息罗列出来。这就要 用到 listAllproduct()方法。它调用 dao 的 getObjectList()方法来获取所有的公司产品 信息。它根据从页面上获取的 action 的值来判断所要跳转的页面。其关键代码如下。 例程 20-38:光盘\mr\20\enterpriseweb\src\com\action\ProductsAction.java public ActionForward listAllproduct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("lislistAllproduct"); String selectSql = "Select * from tb_products;"; List listproducts = dao.getObjectList(selectSql); System.out.println("数据库中的记录数 " + listproducts.size()); request.setAttribute("ListProducts", listproducts); String name = request.getParameter("name"); 510 第 4 篇 项 目 实 践 P A R T 4 if (name.equals("modi")){ return mapping.findForward("listallproductsmodi"); } else if (name.equals("dele")){ return mapping.findForward("listallproductsdele"); } else { return null; } (3)listProduct()方法是在修改公司产品信息的时候根据产品的 ID 来查询出所有的 产品的信息,其关键代码如下。 例程 20-39:光盘\mr\20\enterpriseweb\src\com\action\ProductsAction.java public ActionForward listProduct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("listProduct"); String id = request.getParameter("id"); String selectSql = "Select * from tb_products where ID = '" + id + "'"; System.out.println("selectSql:" + selectSql); List listproducts = dao.getObjectList(selectSql); request.setAttribute("Product", listproducts); return mapping.findForward("listproducts"); (4)updateproduct()方法是将修改后的产品信息进行保存的操作,其关键代码如下。 例程 20-40:光盘\mr\20\enterpriseweb\src\com\action\ProductsAction.java public Actiorward updateproduct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("GB2312"); productform = (ProductForm) form; String dir = servlet.getServletContext().getRealPath("/uploadImage"); UploadBean upb = new UploadBean(); try { // 通过该方法上传图片并将图片名称及大小存放到form中 upb.upload(dir, productform, "productForm"); } catch (Exception ex) { System.out.println("------在上传图片时抛出异常,内容如下:"); ex.printStackTrace(); } String addSql = "update tb_products set pname = '" + productform.getPname().trim() + "',introduce ='" + productform.getIntroduce().trim() + "',temperature = " + productform.getTemperature() + ",pressure = " + productform.getPressure() + ",imagepath = '" + upb.getFileName(productform, "ProductForm") + "' where ID = " + productform.getId(); System.out.println("addSql:" + addSql); dao.executeSql(addSql); 511 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 request.setAttribute("name", "update"); return mapping.findForward("modiupdateproducts"); (5)delProduct()方法执行删除操作。它根据从页面中传递过来的 ID 判断所要删除的 公司产品信息,其关键代码如下。 例程 20-41:光盘\mr\20\enterpriseweb\src\com\action\ProductsAction.java public ActionForward delProduct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String id = request.getParameter("id"); String selectSql = "delete from tb_products where ID = '" + id + "'"; System.out.println("selectSql:" + selectSql); dao.executeSql(selectSql); request.setAttribute("name", "dele"); return mapping.findForward("delelistproducts"); } 4.struts-config.xml 文件配置 在将一个请求分发给某个业务控制器的时候,ActionServlet 需要咨询处理器将请求 分配到对应的页面上。关键代码如下。 例程 20-42:光盘\mr\20\enterpriseweb\WebRoot\WEB-INF\struts-config.xml 在上面的配置中 type 属性的值为.DelegatingActionProxy,它是 Spring 的一个代理 类,要想找到 ProductAction 类,还需要在 bean-config.xml 文件中进行配置。关键配置 代码如下。 例程 20-43:光盘\mr\20\enterpriseweb\WebRoot\WEB-INF\bean-config.xml 512 第 4 篇 项 目 实 践 P A R T 4 5.文件上传的处理 在添加公司产品信息、公司荣誉、技术支持的时候都需要上传图片,所以笔者将对文 件上传做详细的介绍。所用的上传组件是 Struts 中自带的上传组件 fileupload.jar,读 者只要将 Struts 包引入到项目中即可使用此上传组件。 (1)在项目中新建一个 uploadImage 文件夹,用于存放上传的图片。 (2)执行上传的核心类为 UploadBean。下面将对这个核心类进行详细地分析。 UploadBean 类的 ToChinese()方法可以对字符进行转换,以防止出现因字符乱码而无 法将文件上传,其关键代码如下。 例程 20-44:光盘\mr\20\enterpriseweb\src\com\dao\UploadBean.java public class UploadBean { public String toChinese(String s) { String result = null; try { result = new String(s.trim().getBytes("ISO8859_1"), "GB2312"); } catch (UnsupportedEncodingException ex) { System.out.println("字符转码错误!!!"); ex.printStackTrace(); } return result; } } UploadBean 类的 getFileName()方法用于获取上传文件的名字。由于要将文件的名字 存入数据库中,为了避免上传的图片重名,需要在上传文件的名字中加入当前系统的时间, 所以在这个方法中执行了取得当前系统的时间的操作。其关键代码如下。 例程 20-45:光盘\mr\20\enterpriseweb\src\com\dao\UploadBean.java public String getFileName(Object object,String formname){ //取欲上传的文件 FormFile file = null; if (formname == "ProductForm"){ ProductForm form = (ProductForm)object; file = form.getImageFile(); } if(formname == "honourForm"){ HonourForm form = (HonourForm)object; file = form.getImageFile(); } if(formname == "instructorForm"){ InstructorForm form = (InstructorForm)object; 513 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站 file = form.getImageFile(); } //取系统时间 Calendar now = Calendar.getInstance(); int year = now.get(Calendar.YEAR); int month = now.get(Calendar.MONTH) + 1; int day = now.get(Calendar.DAY_OF_MONTH); int hour = now.get(Calendar.HOUR_OF_DAY); int minute = now.get(Calendar.MINUTE); int second = now.get(Calendar.SECOND); String date = year + ""; if (month < 10) { date = date + "0" + month; } else { date = date + month; } if (day < 10) { date = date + "0" + day; } else { date = date + day; } if (hour < 10) { date = date + "0" + hour; } else { date = date + hour; } if (minute < 10) { date = date + "0" + minute; } else { date = date + minute; } if (second < 10) { date = date + "0" + second; } else { date = date + second; } //取欲上传的文件的名字和长度 String fname = file.getFileName(); System.out.println("----------- 您上传的图片名称是:"+fname); int size = file.getFileSize(); //将上传时间加入文件名 int i = fname.indexOf("."); String name = fname.substring(0, i) + "[" + date + "]"; String type = fname.substring(i + 1); fname = name + "." + type; System.out.println("----------- 您上传的图片名称是:" + fname); return fname; 514 第 4 篇 项 目 实 践 P A R T 4 } upload()方法是执行上传操作的核心方法,它根据文件流的形式读取文件并将文件存 入新建的目录中。此方法中的参数 dir 是指上传的图片要存入的目录的相对路径,object 是指从 Action 类中传入的表单对象,formName 是指表单的名字。在这里要根据 formName 参数来判断表单对象。其关键代码如下。 例程 20-46:光盘\mr\20\enterpriseweb\src\com\dao\UploadBean.java public void upload(String dir, Object object,String formName) throws Exception { FormFile file = null; String fname = null; if (formName = ="productForm"){ ProductForm form = (ProductForm)object; file = form.getImageFile(); fname = this.getFileName(form,"ProductForm");//上传的文件 form.setImagepath(fname); } if(formName = = "honourForm"){ HonourForm form = (HonourForm)object; file = form.getImageFile(); fname = this.getFileName(form,"honourForm");//上传的文件 form.setImagepath(fname); } if(formName = = "instructorForm"){ InstructorForm form = (InstructorForm)object; file = form.getImageFile(); fname = this.getFileName(form, "instructorForm"); form.setImagepath(fname); } //form.setMerchandisePhotoSize(size); //创建读取用户上传文件的对象 InputStream streamIn = file.getInputStream(); BufferedInputStream bufferStreamIn = new BufferedInputStream(streamIn); //创建把上传数据写到目标文件的对象 File uploadFile = new File(dir); if (!uploadFile.exists() || uploadFile = = null){ uploadFile.mkdirs(); } OutputStream streamOut = new FileOutputStream(uploadFile.getPath() + "/" + fname); System.out.println("上传目录:" + uploadFile.getPath() + "/" + fname); BufferedOutputStream bufferStreamOut = new BufferedOutputStream(streamOut); int bytesRead = 0; byte[] buffer = new byte[8202]; while ((bytesRead = bufferStreamIn.read(buffer, 0, 8202)) != -1) { bufferStreamOut.write(buffer, 0, bytesRead); } bufferStreamOut.flush(); bufferStreamOut.close(); bufferStreamIn.close(); file.destroy(); } 515 C H A P T E R 2 0 第 20 章 企 业 门 户 网 站  注意 读者在分析上传操作的时候,请结合 Action 类和相对应的 jsp 页面一起分析,这样 有助于理解。 20.7.3 其他管理模块 用户登录成功后单击“公司荣誉管理”或“技术支持管理”连接,会出现相应的功能 菜单,它主要包括荣誉或技术支持的增加、删除和修改操作,功能菜单的运行效果分别如 图 20.13 和图 20.14 所示。 图 20.13 荣誉管理菜单页面 图 20.14 技术支持管理菜单页面 荣誉管理模块和技术支持管理模块的具体实现步骤和产品管理模块基本相同,实现的 功能都是相应模块的增加、删除和修改,所以本章不再对重复的技术多做讲解,读者可以 参考光盘内容,分析实例的相关代码。 20.8 发布与运行 将该项目导入到 Eclipse 中后,在 Eclipse 中发布完成后重新启动 Tomcat,然后打开 浏览器,在地址栏中输入 http://localhost:8080/enterpriseweb 单击回车,即可进入网 站首页。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档

相关文档