Spring 3.0就这么简单

978-7-115-29839-3
作者: 陈雄华 林开雄
译者:
编辑: 杜洁
分类: Spring

图书目录:

详情

本书内容包括Spring 概述、Spring 快速入门、Spring IOC、Spring AOP、Spring JDBC、集成Hibernate、Spring事务管理、Spring MVC、Spring单元测试、实训案例、敏捷开发。

图书摘要

Spring 3.0就这么简单
●陈雄华 林开雄 编著
人民邮电出版社

北京

前言

关于本书

Spring作为Java领域的第一开源项目,从其诞生到现在已有10个年头。10年的时间对于计算机业界来说是非常漫长的,在热闹的Java开源领域,无数个开源产品喧嚣登场,但又很快被人们淡忘。能够像Spring一样历经时间洗礼而历久弥香的开源框架真的是寥若星辰,Spring无疑是Java开源世界的一朵奇葩。

在Spring发展的10年中,不但Spring自身不断发展壮大,各种基于Spring的子项目也如雨后春笋一样成长起来,Spring的社区亦蓬勃发展。Rod就和他的骨干团队成立了SpringSource公司,以商业化的方式对开源的Spring进行运作。2009年,商业软件生产商VMWare宣布斥资4.2亿美元收购SpringSource公司:一个源于Spring开源框架的公司卖出了天价,这从商业价值上又一次证明了Spring强大的内在价值。

笔者在实际工作中使用Spring框架也有近10年的时间了,在2005年和2012年分别出版了《精通Spring 2.x——企业应用开发详解》和《Spring 3.x 企业应用开发实战》两本书籍,承蒙读者的垂爱,都取得了不错的销售成绩,对于作者来说,没有什么比获得读者肯定更值得欣慰的了,这大大激发了我深耕Spring的热情。在实际工作和图书撰写过程中,我多次研究了Spring框架的源码,使用Spring越深,越能体会到Spring的精妙。2012 年,因工作需要,我参照Spring MVC 的实现思路开发了一个基于Spring 的开放服务平台框架,即ROP(Rapid Open Platform),目前该项目已经在github中开源,也用在了我的实际工作中,并取得了很好的应用效果。

Spring 就像一座巨大的宝藏,越挖掘就越有惊奇的发现。从起初满足于使用 Spring提供的各项IoC、AOP等功能的喜悦,到后来沉醉在Spring源码审读的快乐里,再到现在汲取Spring思想精髓后,开发出自己的“很Spring”的开源产品。一步步走来,“春光无限”,受益良多,收获良多。从自己学习Spring的10年长征路来说,最大的一个体会就是:Spring不仅是工具,更是一部学习Java设计原理,活用Java技术的百科全书。所以,如果让我给有志于 Java 编程的开发者送一句寄语的话,我将把这句“用 Spring吧,研究Spring吧”送给他们。用Spring可以让你找到一份不错的工作,研究Spring可以让你成为一名系统架构师!

《Spring 3.x 企业应用开发实战》有近 800 页的篇幅,很多读者来信说,能否出一本内容精悍而不失深度的Spring书,只包含Spring最重要最核心的内容,使他们可以尽快上手,并沿着这个核心给出一些方向,让他们自行在工作和学习中不断拓展和深掘。

这样的建议无疑是非常有道理的,由于Spring内容的浩瀚性,任何一本有限篇幅的书都无法穷尽Spring的内涵。想学习好Spring并吸取Spring中饱含的设计原理、Java技艺、巧妙构思,并没有捷径,也许还得走我那条边学边用,边用边学的老路。所以,就有了本书,它只有11章,400页左右的篇幅。它不但涵盖了学习Spring所必须掌握的核心内容、使用 Spring 进行项目实战的内容,而且涵盖了如何活用 Spring 打造自主框架的内容。本书的主旨就是希望您尽快上手,掌握Spring核心内容,正确进行项目实战,汲取Spring的思想,并最终将这种思想活用到实际工作中。

本书面向的读者

首先,本书适合于所有的Java程序员,作为Java开源世界的第一框架,Java程序员没有理由不学习Spring。其次,本书适合于那些希望学习Spring编程的在校学生,由于学校的功课很多,而一本大部头的Spring书籍需要花费大量的时间研读,在学习效率上,本书可以快速带领你进入Spring的殿堂,同时又不会遗漏掉Spring核心的知识。

本书的结构

第1章通过一个简单的例子展现开发Spring的Web应用的整体过程,通过这个实例,读者可以快速进入Spring的Web应用开发的世界。

第2章讲解了Spring IoC 的知识,对Spring 框架的3 个最重要的框架级接口进行了剖析,并对 Bean 的生命周期进行分析,讲解如何在 Spring 配置文件中使用 Spring 3.0的Schema格式配置Bean的内容,并对各种配置项的意义进行了深入的说明。

第3章讲解了AOP的知识,对如何使用基于AspectJ配置AOP的知识进行了深入的分析,包括使用XMLSchema 和使用注解方式进行配置等内容。

第4章讲解了如何使用Spring JDBC 进行数据访问操作的内容,还重点讲述了LOB字段处理、主键产生和获取等难点知识。

在第5章中,读者将学习如何在Spring中集成Hibernate,以及DAO层设计的各种思路。

事务管理是Spring提供的最好用的功能之一,在第6章中,读者将学习Spring事务管理的工作机制,并学会通过XML、注解等多种方式进行事务管理的配置。

第7章对Spring MVC 框架进行详细介绍,对REST 风格编程方式进行重点讲解,同时还介绍了Spring3.0 的校验和格式化框架如何与Spring MVC 进行整合。

第8章展现了特别精彩的单元测试内容,以当前最具实战的 JUnit4+Unitils+Mockito组合测试框架来讲解单元测试,而不囿于Spring本身。

要学好 Spring,不但要有较好的 Java 基础,还需要了解多项敏捷开发的技术,如Maven、SVN、持续集成等,因为Spring框架本身就是采用这些敏捷技术开发出来的。第9章将会详细讨论这些内容。

第10章带领读者打造一个自主的服务开放平台框架,学习如何活用 Spring,如何为我所用。同时,也可以使用该框架开发WebService 服务。

第11章以一个实际的项目为蓝本,带领读者经历需求分析、概要设计、代码开发、单元测试直到应用部署的整体项目开发过程。

如何使用本书

首先,应该在计算机中安装 Maven,因为本书项目是以 Maven 组织的,可以从http://maven.apache.org/download.html下载Maven。此外,建议读者安装社区版的IntelliJIDEA,IDEA 不仅是对 Maven 支持最好的 IDE,我们甚至认为它是当前最出色的 JavaIDE,可以从http://www.jetbrains.com/idea/download下载。

尽量在自己的计算机中动手执行本书的所有代码,仅仅看书和自己动手存在天壤之别,动手运行代码和看书结合起来才能更深地理解Spring的各项功能。

如何下载代码

为了环保,本书不提供光盘,读者可以使用以下网址中的任意一个来下载本书的代码:

Github,https://github.com/downloads/itstamen/rop/sprProjects.zip;

360 云盘,http://l7.yunpan.cn/lk/QMwksThxzXYUD;

百度网盘,http://pan.baidu.com/share/link?shareid=123943&uk=1543829856。

如何与作者联系

由于Spring内容涵盖面较广,涉及的知识点非常多,且作者水平有限,本书不足之处在所难免。我们不但欢迎读者朋友来信交流,更期待各界高手、专家就不足之处给予赐教和斧正。您可以通过quickselect@yahoo.com.cn与笔者联系。

陈雄华 于厦门

第1章 快速入门

本章通过一个简单的例子展现开发Spring Web应用的整体过程,通过这个实例,读者可以快速进入Spring Web 应用的世界。实例应用按持久层、业务层和展现层进行组织,从底层DAO程序到Web展现程序逐层演进,一步步地搭建起一个完整的实例。通过本章的学习,读者可以独立完成一个典型的基于Spring的Web应用。

本章主要内容:

• Spring概述

• 用户登录实例介绍

• 基于Spring JDBC的持久层实现

• 基于Spring声明式事务的业务层实现

• 基于Spring MVC的展现层实现

• 在IntelliJ IDEA中开发Web应用的过程

• 运行Web应用

本章亮点:

• 非传统Hello World的快速入门实例

• 基于Maven模块化来讲解开发过程

• 详尽的开发过程,使读者快速上手

1.1 Spring概述

1.1.1 认识Spring

Spring是众多Java开源项目中的一员,唯一不同的是:它秉承破除权威迷信,一切从实践中来到实践中去的信念,宛如阿基米德手中的杠杆,以一己之力撼动了Java EE传统重量级框架坚如磐石的大厦。

要用一两句话总结出 Spring 的所有内涵确实有点困难,但是为了先给读者一个基本的印象,我们尝试进行以下概括。

Spring是分层的Java SE/EE应用一站式的轻量级开源框架,以反转控制(Inverse of Control,IoC)和面向切面编程(Aspect Oriented Programming,AOP)为内核,提供了展现层Spring MVC、持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,此外,Spring以海纳百川的胸怀整合了开源世界里众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。

从2004年发布第一个版本以来,Spring逐渐占据了Java开发人员的视线,获得了开源社区的一片赞誉之声。

1.1.2 Spring带给我们什么

也许有很多的开发者曾经被 EJB 的过度宣传所迷惑,成为 EJB 的拥趸,并因此拥有一段痛苦的开发经历。EJB的复杂源于它对所有的企业应用采用统一的标准,它认为所有的企业应用都需要分布式对象、远程事务,因此造就了EJB框架的极度复杂。这种复杂不仅造成陡峭的学习曲线,而且给开发、测试、部署都造成了很多额外的要求和工作量。其中最大的诟病就是难于测试,因为这种测试不能脱离EJB容器,每次测试都需要进行应用部署并启动EJB容器,而部署和启动EJB是一项费时费力的重型操作,其结果是测试工作往往成为开发工作的瓶颈。

Spring认为Java EE的开发应该更容易、更简单。在实现这一目标时,Spring一直贯彻并遵守“好的设计优于具体实现,代码应易于测试”这一理念,并最终带给我们一个易于开发、便于测试而又功能齐全的开发框架。概括起来,Spring给我们带来以下几方面的好处。

• 方便解耦,简化开发。

通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户就不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,而可以更加专注于上层的应用。

• AOP编程的支持。

通过Spring提供的AOP功能,用户可以方便地进行面向切面编程,许多不容易用传统面向对象编程(OOP)实现的功能都可以通过AOP轻松应对。

• 声明式事务的支持。

在Spring中,用户可以从单调烦闷的事务管理代码中解脱出来,通过声明式事务灵活地进行事务管理,提高开发效率和质量。

• 方便程序的测试。

可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring中,测试不再是昂贵的操作,而是随手可做的事情。

• 方便集成各种优秀的框架。

Spring 不排斥各种优秀的开源框架,相反,Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts、Hibernate、Hessian、Quartz等)的直接支持。

• 降低JavaEEAPI的使用难度。

Spring为很多难用的Java EE API(如JDBC、JavaMail、远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,大大降低了这些Java EE API的使用难度。

• Java 源码是经典的学习范例。

Spring的源码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式的灵活运用以及对Java技术的高深造诣。Spring框架源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用开发水平,那么学习和研究Spring源码就可以让你获得意想不到的效果。

1.1.3 Spring体系结构

Spring 框架由 1 400 多个类组成,整个框架按其所属功能可以划分为 5 个主要模块,如图1-1所示。

图1-1 Spring框架结构

从整体看,这5个主要模块几乎为企业应用提供了所需的一切,从持久层、业务层到展现层都拥有相应的支持。就像吕布的赤兔马和方天画戟、秦琼的黄骠马和熟铜锏,IoC 和 AOP是Spring所依赖的根本。在此基础上,Spring整合了各种企业应用开源框架和许多优秀的第三方类库,成为Java企业应用full-stack的开发框架。Spring框架的精妙之处在于:开发者拥有自由的选择权,Spring不会将自己的意志强加给开发者,因为针对某个领域问题,Spring往往支持多种实现方案。当希望选用不同的实现方案时,Spring又能保证过渡的平滑性。

• IoC。

Spring核心模块实现了IoC的功能,它将类和类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由 IoC 容器负责依赖类之间的创建、拼接、管理、获取等工作。BeanFactory接口是Spring框架的核心接口,它实现了容器的许多核心功能。

Context模块构建于核心模块之上,扩展了BeanFactory的功能,添加了i18n国际化、Bean生命周期控制、框架事件体系、资源加载透明化等多项功能。此外,该模块还提供了许多企业级服务的支持,如邮件服务、任务调度、JNDI定位、EJB集成、远程访问等。ApplicationContext是Context模块的核心接口。

表达式语言模块是统一表达式语言(unified EL)的一个扩展,该表达式语言用于查询和管理运行期的对象,支持设置和获取对象属性,调用对象方法,操作数组、集合等。还提供了逻辑表达式运算、变量定义等功能。使用它就可以方便地通过表达式串和Spring IoC容器进行交互。

• AOP模块。

AOP是继OOP之后,对编程设计思想影响最大的技术之一。AOP是进行横切逻辑编程的思想,它开拓了人们考虑问题的思路。在AOP 模块里,Spring 提供了满足AOP Alliance 规范的实现,此外,还整合了AspectJ这种AOP语言级的框架。在Spring里实现AOP编程有许多的选择。Java 5.0 引入java.lang.instrument,允许在JVM 启动时启用一个代理类,通过该代理类在运行期修改类的字节码,改变一个类的功能,实现AOP的功能。

• 数据访问和集成。

任何应用程序的核心问题都是对数据的访问和操作。数据有很多表现形式,如数据表、XML、消息等,而每种数据形式又拥有不同的数据访问技术(如数据表的访问既可以直接通过JDBC,也可以通过Hibernate或iBatis)。

Spring站在DAO的抽象层面,建立了一套面向DAO层统一的异常体系,同时将各种访问数据的检查型异常转换为非检查型异常,为整合各种持久层框架提供基础。其次,Spring通过模板化技术对各种数据访问技术进行了薄层的封装,将模式化的代码隐藏起来,使数据访问的程序得到大幅简化。这样,Spring就建立起了和数据形式及访问技术无关的统一的DAO层,借助AOP技术,Spring提供了声明式事务的功能。

• Web 及远程操作。

该模块建立在 Application Context 模块之上,提供了 Web 应用的各种工具类,如通过Listener或Servlet初始化Spring容器,将Spring容器注册到Web容器中。其次,该模块还提供了多项面向Web的功能,如透明化文件上传,Velocity、FreeMarker、XSLT的支持。此外, Spring可以整合Struts、WebWork、Tapestry Web等MVC框架。

• Web 及远程访问。

Spring提供了一个完整的类似于Struts的MVC框架,称为Spring MVC。据说,Spring之所以也提供了一个MVC 框架,是因为Rod Johnson 想证明实现MVC 其实是一项简单的工作。当然,如果不希望使用Spring MVC,那么Spring对Struts、Tapestry 等MVC框架的整合,一定也可以给你带来方便。相对于Servlet的MVC,Spring在简化Portlet的开发上也做了很多工作,开发者可以从中受益。

此外,Spring 在远程访问以及 Web Service 上提供了对很多著名框架的整合。由于 Spring框架的扩展性,特别是随着Spring框架影响性的扩大,越来越多框架主动地支持Spring框架,让Spring框架应用的涵盖面越来越宽广。

1.2 实例功能概述

在进行实例具体开发步骤之前,有必要先了解实例的功能,以便对要实现的实例有一个整体性的认识。

1.2.1 比Hello World更适用的实例

快速对 Spring 有一个切身的认识,没有什么比通过一个实际的例子更适合的了。Hello World是比较经典的入门实例,但Hello World太过简单,很难展现Spring的全貌,为了让Spring的功能轮廓更加清晰,通过一个功能涵盖面更广的景区网站登录模块替换经典的 Hello World实例。选择登录功能模块是出于以下3个原因。

• 大家对于登录模块的业务功能再熟悉不过了,无须在业务功能介绍上花费时间。

• 登录模块麻雀虽小,五脏俱全,它涵盖了持久层数据访问操作、业务层事务管理以及展现层MVC等企业应用常见的功能。

• 本书希望通过一个景区网站贯穿始终,以便能够由点及面,使读者在单纯技术性学习的酣战中深刻理解应用程序的整体开发流程。

Spring拥有持久层、业务层和展现层的“原生技术”,分别是Spring JDBC、声明式事务和Spring MVC。为了充分展现Spring 本身的魅力,在本章中仅使用Spring 的这些原生技术,在以后的章节中,我们将学习其他的持久层和展现层技术,只要用户愿意,就可以平滑地将其过渡到其他技术实现中。

1.2.2 实例功能简介

景区网站登录模块的功能很简单,首先登录页面提供一个带用户名/密码的输入表单,用户填写并提交表单后,服务端程序检查是否有匹配的用户名/密码。如果用户名/密码不匹配,返回到登录页面,并给出提示。如果用户名/密码匹配,记录用户的成功登录日志,更新用户的最后登录时间和IP地址,然后重定向到景区后台欢迎页面,如图1-2所示。

在持久层拥有两个DAO类,分别是UserDao和LoginLogDao,在业务层对应一个业务类UserService,在展现层拥有一个LoginController类和两个JSP页面,分别是登录页面login.jsp和欢迎页面main.jsp。

下面通过如图1-3所示的时序图来描述景区网站登录模块的整体交互流程。

(1)首先用户访问login.jsp,返回带用户名/密码表单的登录页面。

图1-2 页面流程
图1-3 登录模块整体交互流程

(2)用户在登录页面输入用户名/密码,提交表单到服务器, Spring 根据配置调用LoginController控制器来响应登录请求。

(3)LoginController 调用 UserService#hashMatchUser()方法,根据用户名和密码查询是否存在匹配的用户,UserService内部通过调用持久层的UserDao完成具体的数据库访问操作。

(4)如果不存在匹配的用户,重定向到login.jsp页面,并报告错误,否则到下一步。

(5)LoginController 调用 UserService#findUserByUserName()方法,加载匹配的 User 对象并更新用户最近一次的登录时间和登录IP地址。

(6)LoginController调用UserService#loginSuccess()方法,进行登录成功的业务处理,创建一个LoginLog对象,并利用LoginLogDao将其插入数据库中。

(7)重定向到欢迎页面main.jsp,欢迎页面产生响应返回给用户。

实例的所有程序代码都位于 chapter1 目录下,本章后面的内容将逐一实现以上步骤的功能,完成这个实例的所有细节。

1.3 环境准备

在进入实例的具体开发之前,需要做一些环境的准备工作,其中包括数据库表的创建、项目工程的创建、规划 Spring 配置文件等工作。本书使用 MySQL 5.x 数据库,如果用户机器中还未安装该数据库,可以从http://www.mysql.org/downloads下载并安装。也可以直接使用熟悉的数据库,只需要调整创建表的脚本并对数据库连接做相应配置即可。

提示 MySQL 4.1.0以前的版本不支持事务,MySQL 4.1.0本身也只对事务提供有限的支持,Spring的各种声明式事务需要底层数据库的支持,所以最好安装MySQL 5.0 或更高的版本。

1.3.1 创建库表

(1)启动MySQL数据库后,用DOS命令窗口登录数据库。

mysql>mysql –uroot –p1234 --port 3309

分别指定用户名和密码,MySQL默认运行在3306端口,如果不是运行在默认端口,需要通过--port参数指定端口号。

(2)运行以下脚本,创建实例对应的数据库。

mysql>DROP DATABASE IF EXISTS sampledb;

mysql>CREATE DATABASE sampledb DEFAULT CHARACTER SET utf8;

mysql>USE sampledb;

数据库名为sampledb,默认字符集采用UTF-8。

(3)创建实例所用的两张表。

##创建用户表

mysql>CREATE TABLE t_user (

user_id INT AUTO_INCREMENT PRIMARY KEY,

user_name VARCHAR(30),

password VARCHAR(32),

last_visit datetime,

last_ip VARCHAR(23)

)ENGINE=InnoDB;

##创建用户登录日志表

mysql>CREATE TABLE t_login_log (

login_log_id INT AUTO_INCREMENT PRIMARY KEY,

user_id INT,

ip VARCHAR(23),

login_datetime datetime

)ENGINE=InnoDB;

t_user表为用户信息表,t_login_log为用户登录日志表。其中ENGINE=InnoDB指定表的引擎为InnoDB类型,该类型表支持事务。MySQL默认采用MyISAM引擎,该类型表不支持事务,仅存储数据,优点在于读写很快。对于景区网站型应用系统的表来说,大可使用不支持事务的MyISAM引擎,但本书出于演示事务的目的,所有表均采用支持事务的InnoDB引擎。(4)初始化一条数据,用户名/密码为admin/123456。

##插入初始化数据

mysql>INSERT INTO t_user (user_name,password) VALUES('admin','123456');

mysql>COMMIT;

用户也可以直接运行脚本文件来完成以上所有工作,创建数据库表的脚本文件位于chapter1/src/main/schema/sampledb.sql,下面提供了两种运行脚本的方法。

• 直接通过mysql 命令运行。

假设从github.com下载本书示例代码中的内容并复制到D:\actionSpring目录下,则在DOS命令窗口中运行以下命令。

D:\> mysql -u root -p1234 --port 3309 <D:\actionSpring\chapter1\schema\sampledb.sql

• 也可以在登录MySQL 后,通过source 命令运行脚本。

mysql>source D:\actionSpring\chapter1\src\main\schema\sampledb.sql

1.3.2 建立工程

考虑到IntelliJ IDEA是一款非常优秀且强大的IDE工具,特别是对Maven等工具提供了良好支持,越来越受企业开发人员的喜爱。本书的所有示例都采用IDEA(11.0版本)进行开发。将IDEA 的工作空间设置于 D:\actionSpring,为了避免因路径不一致而引起的各种问题,请尽量保证工作空间和本书的路径一致。示例工程源文件和配置文件都使用UTF-8编码格式,UTF-8可以很好地解决国际化问题,同时避免不受欢迎的中文乱码问题,用户可以执行 File→Settings→File Encodings命令将IDEA的工作空间编码格式设置为UTF-8编码格式。

在D:\actionSpring中建立一个名为chapter的Maven主工程项目,如图1-4所示。

设置项目名称为 chapter,项目路径为 D:\actionSpring\chapter,并选择项目类型为 Maven Module,单击Next按钮,进入Maven工程设置窗口,如图1-5所示。

设置Maven的GroupId为com.smart,ArtifactId为chapter,Version版本号为3.1-SNAPSHOT之后,单击Finish按钮完成主工程的创建,之后所有章节的代码示例将在这个主工程上以模块的方式进行创建。接下来,就可以创建本章示例的模块chapter1,可以通过File→NewModule→Add Module对话框来实现,如图1-6所示。

选择Create module from scratch选项,并单击Next按钮进入模块设置窗口,如图1-7所示。

设置模块的名称为chapter1,选择类型为Maven Module,其他保持默认值即可,单击Next按钮,完成模块创建。后面所有章节的示例代码都将以 Maven 模块的方式进行创建,如第2章,模块的名称为chapter2,其他章节依次类推。创建之后工程模块的目录结构如图1-8所示。

图1-4 创建工程
图1-5 设置Maven工程
图1-6 创建Maven Module
图1-7 配置Module
图1-8 Maven工程目录结构

创建项目工程后,修改主工程模块的pom.xml 文件,设置主工程模块标识、子模块公共依赖库等信息(关于pom.xm详细配置将在第9章中介绍),作为所有子模块父模块,如代码清单1-1所示。

代码清单1-1 主工程模块pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.smart</groupId>

<artifactId>chapter</artifactId>

<packaging>pom</packaging>

<version>3.1-SNAPSHOT</version>

<name>Spring3.1</name>

<description>Spring3.1</description>

<properties>

<file.encoding>UTF-8</file.encoding>

<java.version>1.6</java.version>

<org.springframework.version>3.1.1.RELEASE</org.springframework.version>

<mysql.version>5.1.6</mysql.version>

</properties>

<modules>

<module>chapter1</module>

</modules>

</project>

配置了 chapter 主工程模块 pom.xml 之后,就开始配置在第1章模块 chapter1 中生成的pom.xml文件中配置项目工程中需要的依赖相关类库,如代码清单1-2所示。

代码清单1-2 chapter1模块pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/

xsd/maven-4.0.0.xsd">

<parent>

<groupId>actionspring</groupId>

<artifactId>chapter </artifactId>

<version>3.1-SNAPSHOT</version>

</parent><!--引用父模块-->

<modelVersion>4.0.0</modelVersion>

<artifactId>chapter1</artifactId>

<version>3.1-SNAPSHOT</version>

<name>第一章示例</name>

<description>Spring 快速入门</description>

<packaging>war</packaging>

<dependencies>

<!--依赖的Spring模块类库-->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-beans</artifactId>

<version> ${org.springframework.version} </version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>${org.springframework.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-web</artifactId>

<version> ${org.springframework.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>${org.springframework.version}</version>

</dependency>

<!--依赖的持久化类库-->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>${mysql.version}</version>

</dependency>

<!--依赖的公共模块类库-->

<dependency>

<groupId>commons-dbcp</groupId>

<artifactId>commons-dbcp</artifactId>

<version>${commons-dbcp.version}</version>

</dependency>

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

<version>${aspectjweaver.version}</version>

</dependency>

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>com.springsource.org.apache.commons.logging</artifactId>

<version>${apache.commons.version}</version>

</dependency>

<dependency>

<groupId>fakepath</groupId>

<artifactId>com.springsource.net.sf.cglib</artifactId>

<version>1.1.3</version>

</dependency>

<!--依赖的Web模块类库-->

<dependency>

<groupId>javax.servlet.jsp</groupId>

<artifactId>jsp-api</artifactId>

<version>${jsp-api.version}</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>taglibs</groupId>

<artifactId>standard</artifactId>

<version>${standard.version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>servlet-api</artifactId>

<version>${servlet-api.version}</version>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<version>${jstl.version}</version>

</dependency>

</dependencies>

</project>

配置了主工程模块 chapter 及第1章子模块chapter1 的模块信息之后,单击 IDEA 工程右边的Maven Projects选项卡,弹出Maven项目的管理窗口。IDEA 提供了非常友好的 Maven 项目管理界面,如图1-9所示,单击管理窗口中的刷新按钮,可以列出当前所有的 Maven 模块。每个模块都包含两个子节点Lifecycle及Dependencies,其中Lifecycle子节点下提供常用的Maven操作命令,如清理、验证、编译、测试、打包、部署等,Dependencies 子节点列出当前模块所有依赖的类库。

图1-9 Maven项目的管理界面

1.3.3 类包及Spring配置文件规划

类包规划

类包以分层的方式进行组织,共划分为4个包,分别是dao、domain、service和web。领域对象从严格意义上讲属于业务层,但由于领域对象可能还同时被持久层和展现层共享,所以一般将其单独划分到一个包中,如图1-10所示。

单元测试的类包和程序的类包对应,但放置在不同的文件夹下,如图1-11所示。本实例仅对业务层的业务类进行单元测试。将程序类和测试类放置在物理不同的文件夹下,方便将来程序的打包和分发,因为测试类仅在开发时有用,无须包含到部署包中。在本章的后续内容中,读者将会看到如何用Maven工具进行程序打包,体会这种包及目录的设计结构所带来的好处。

实战经验

随着项目规模的增大,这种仅以分层思想规划的包结构马上就会显示出它的不足,一般情况下需要在业务模块包下,进一步按分层模块划分子包,如 user/dao、user/service、viewspace/dao、viewspace/service等。对于由若干独立的子系统组成的大型应用,在业务分层包的前面一般还需要加上子系统的前缀。包的规划对于大型的应用特别重要,它直接关系到应用部署和分发的方便性。

图1-10 主程序包的规划
图1-11 测试包的规划

Spring配置文件规划

Spring可以将所有的配置信息统一到一个文件中,也可以放置到多个文件中。对于简单的应用来说,由于配置信息少,仅用一个配置文件就足以应付。随着应用规模的扩大,配置信息量的增多,仅仅使用一个配置文件往往难以满足要求,如果不进行仔细规划,就会给配置信息的查看和团队协作带来负面影响。

配置文件在团队协作时是资源争用的焦点,对于大型的应用一般要按模块进行划分,以在一定程度上降低争用,减少团队协作的版本控制冲突。因此,应用比较小时,直接采用一个applicationContext.xml配置文件即可。

1.4 持久层

持久层负责数据的访问和操作,DAO 类被上层的业务类调用。Spring 本身支持多种流行的ORM框架。这里使用Spring JDBC 作为持久层的实现技术,关于Spring JDBC的详细内容,请参见第4章的内容。为方便阅读,会对本章涉及的相关知识点进行必要的介绍,所以相信读者在不了解Spring JDBC的情况下,也可以轻松阅读以下的内容。

1.4.1 建立领域对象

领域对象(Domain Object)也称为实体类,它代表了业务的状态,一般来说,领域对象属于业务层,但它贯穿展现层、业务层和持久层,并最终被持久化到数据库中。领域对象使数据库表操作以面向对象的方式进行,为程序的扩展带来了更大的灵活性。领域对象不一定等同于数据库表,不过对于简单的应用来说,领域对象往往都拥有对应的数据库表。

持久层的主要工作就是从数据库表中加载数据并实例化领域对象,或将领域对象持久化到数据表中。由于持久层需要用到领域对象,所以将本属于业务层的领域对象提前到持久层来说明。

景区网站登录模块需要涉及两个领域对象:User 和 LoginLog,前者代表用户信息,后者代表日志信息,分别对应t_user和t_login_log数据表,领域对象类的包为com.smart.domain。

用户领域对象

用户信息领域对象很简单,可以看成是对t_user表的对象翻译,每个字段对应一个对象属性。User 类主要有两类信息,分别为用户名/密码(userName/password)以及最后一次登录的信息(lastIp、lastVisit),其代码如代码清单1-3所示。

代码清单1-3 User.java领域对象

package com.smart.domain;

import java.io.Serializable;

import java.util.Date;

public class User implements Serializable{①

领域对象一般要实现Serializable接口,以便可以序列化

private int userId;

private String userName;

private String password;

private String lastIp;

private Date lastVisit;

//省略get/setXxx方法

}

登录日志领域对象

用户每次登录成功后,记录一条登录日志,该登录日志包括3个信息,分别是用户ID、登录 IP 地址和登录时间。一般情况下,还包括退出时间。为了简化实例,仅记录登录时间,登录日志的领域对象如代码清单1-4所示。

代码清单1-4 LoginLog.java

package com.smart.domain;

import java.io.Serializable;

import java.util.Date;

public class LoginLog implements Serializable {

private int loginLogId;

private int userId;

private String ip;

private Date loginDate;

//省略get/setXxx方法

}

1.4.2 UserDao

首先定义访问User的DAO,它包括以下3个方法。

• getMatchCount():根据用户名和密码获取匹配的用户数。等于1表示用户名/密码正确,等于 0 表示用户名或密码错误(这是最简单的用户身份认证方法,在实际应用中需要采用诸如密码加密等安全策略)。

• findUserByUserName():根据用户名获取User 对象。

• updateLoginInfo():更新用户积分、最后登录IP地址以及最后登录时间。

下面通过Spring JDBC技术实现这个DAO类,如代码清单1-5所示。

代码清单1-5 UserDao

package com.smart.dao;

import java.sql.ResultSet;

import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.core.RowCallbackHandler;

import org.springframework.stereotype.Repository;

import com.smart.domain.User;

@Repository ①

通过Spring注解定义一个DAO

public class UserDao {

@Autowired ②

自动注入JdbcTemplate的Bean

private JdbcTemplate jdbcTemplate;

public int getMatchCount(String userName, String password) {

String sqlStr = " SELECT count(*) FROM t_user "

+ " WHERE user_name =? and password=? ";

return jdbcTemplate.queryForInt(sqlStr, new Object[] { userName, password });

}

}

在Spring 1.5以后,可以调用注解的方式定义Bean,较之于XML 配置方式,注解配置方式的简单性非常明显,已经被广泛接受,成为一种趋势。所以除非没有办法,否则都应尽量采用注解的配置方式。

这里用@Repository 定义了一个DAO Bean,使用@Autowired 将Spring 容器中的Bean 注入进来。关于Spring的注解配置,将会在第2章详细讨论。

传统的JDBC API太底层,即使用户执行一条最简单的数据查询操作,都必须执行如下的过程:获取连接→创建 Statement→执行数据操作→获取结果→关闭 Statement→关闭结果集→关闭连接,除此之外还需要进行异常处理的操作。如果使用传统JDBCAPI进行数据访问操作,可能会有1/3以上单调乏味的重复性代码像苍蝇一样驱之不散。

Spring JDBC对传统的JDBC API进行了薄层的封装,将样板式的代码和那些必不可少的代码进行了分离,用户仅需要编写那些必不可少代码,剩余的单调乏味的重复性工作则交由Spring JDBC框架处理。简单来说,Spring JDBC通过一个模板类org.springframework. jdbc.core.JdbcTemplate封装了样板式的代码,用户通过模板类就可以轻松地完成大部分数据访问的操作。

例如,对于 getMatchCount()方法,仅提供了一个查询 SQL 语句,直接调用模板的queryForInt()方法就可获取查询,用户不用担心获取连接、关闭连接、异常处理等繁琐的事情。

通过JdbcTemplate的支持,可以轻松地实现UserDao的另外两个接口,如代码清单1-6所示。

代码清单1-6 UserDao 另外两个方法

package com.smart.dao.jdbc;

@Repository

public class UserDao {

public User findUserByUserName(final String userName) {

String sqlStr = " SELECT user_id,user_name " ①

+ " FROM t_user WHERE user_name =? ";

根据用户名查询用户的SQL语句

final User user = new User();

jdbcTemplate.query(sqlStr, new Object[] { userName },

new RowCallbackHandler() {②

匿名类方式实现的回调函数

public void processRow(ResultSet rs) throws SQLException {

user.setUserId(rs.getInt("user_id"));

user.setUserName(userName);

}

});

return user;

}

public void updateLoginInfo(User user) {

String sqlStr = " UPDATE t_user SET last_visit=?,last_ip=? "

+ " WHERE user_id =? ";

jdbcTemplate.update(sqlStr, new Object[] { user.getLastVisit(),

user.getLastIp(),user.getUserId()});

}

}

findUserByUserName()方法稍微有点复杂。这里使用到了JdbcTemplate#query()方法,该方法的签名为query(String sql,Object[]args, RowCallbackHandlerrch),它有3个入参。

• sqlStr:查询的SQL语句,允许使用带“?”的参数占位符。

• args:SQL语句中占位符对应的参数数组。

• RowCallbackHandler:查询结果的处理回调接口,该回调接口有一个方法 processRow(ResultSet rs),负责将查询的结果从ResultSet 装载到类似于领域对象的对象实例中。

在② 处,findUserByUserName()通过匿名内部类的方式定义了一个RowCallbackHandler回调接口实例,将ResultSet转换为User对象。

updateLoginInfo()方法比较简单,主要通过 JdbcTemplate#update(String sql,Object[])进行数据的更新操作。

实战经验

在编写SQL语句时,由于SQL语句比较长,一般会采用多行字符串的方式进行构造,如代码清单1-6的① 处所示。在编写多行SQL语句时,由于上下行最终会组成一行完整的SQL语句,这种拼接方式很容易产生错误的SQL组合语句:假设在① 处第一行的user_name后不加空格,第二行的FROM之前也无空格,组合的SQL将为“...user_nameFROM...”,由于FROM保留字和user_name连在一起,就产生了非法的SQL语句。以下是一个值得推荐的编程习惯:在每一行SQL语句的句前和句尾都加一个空格,这样就可以避免分行SQL语句组合后的错误。

1.4.3 LoginLogDao

LoginLogDao负责记录用户的登录日志,它仅有一个insertLoginLog()接口方法,与UserDao相似,其实现类也通过JdbcTemplate#update(String sql ,Object[]args)方法完成插入登录日志的操作,如代码清单1-7所示。

代码清单1-7 LoginLogDao

package com.smart.dao;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.stereotype.Repository;

import com.smart.domain.LoginLog;

@Repository

public class LoginLogDao {

@Autowired

private JdbcTemplate jdbcTemplate;

public void insertLoginLog(LoginLog loginLog) {

String sqlStr = "INSERT INTO t_login_log(user_id,ip,login_datetime) "

+ "VALUES(?,?,?)";

Object[] args = { loginLog.getUserId(), loginLog.getIp(),loginLog.getLoginDate() };

jdbcTemplate.update(sqlStr, args);

}

}

1.4.4 在Spring中装配DAO

在编写 DAO 接口的实现类时,大家也许有一个问题:在以上两个 DAO 实现类中都没有打开/释放Connection的代码,DAO类究竟如何访问数据库呢?前面说过,样板式的操作都被JdbcTemplate 封装起来了,JdbcTemplate 本身需要一个 DataSource,这样它就可以根据需要从DataSource 中获取或返回连接。UserDao 和 LoginLog 都提供了一个带@Autowired 注解的JdbcTemplate 变量。所以必须事先声明一个数据源,然后定义一个 JdbcTemplate Bean,通过Spring的容器上下文自动绑定机制进行Bean的注入。

在项目工程的src\main\resources目录下创建一个名为applicationContext.xml的Spring配置文件,配置文件的基本结构如下所示。

<?xml version="1.0" encoding="UTF-8" ?>

<!--引用Spring的多个Schema空间的格式定义文件-->

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.1.xsd">

</beans>

在IDEA中,刷新工程目录树,在src\main\resources文件夹下即可看到该配置文件。双击applicationContext.xml文件,添加如代码清单1-8所示的配置信息。

代码清单1-8 DAO Bean 的配置

<beans …>

<!-- ① 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入-->

<context:component-scan base-package="com.smart.dao"/>

<!--② 定义一个使用DBCP实现的数据源-->

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close"

p:driverClassName="com.mysql.jdbc.Driver"

p:url="jdbc:mysql://localhost:3309/sampledb"

p:username="root"

p:password="1234" />

<!--③ 定义JDBC模板Bean-->

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"

p:dataSource-ref="dataSource" />

</beans>

在① 处,我们使用Spring的<context:component-scan>扫描指定类包下的所有类,这样在类中定义的Spring注解(如@Repository、@Autowired等)才能产生作用。

在② 处,我们使用Jakarta的DBCP开源数据源实现方案定义了一个数据源,数据库驱动器类为com.mysql.jdbc.Driver,由于我们设置的MySQL数据库的服务端口为3309,而非默认的3306,所以数据库URL中显式指定了3309端口的信息。

在③ 处配置了JdbcTemplate Bean,将② 处声明的dataSource注入JdbcTemplate中,而这个JdbcTemplate Bean将通过@Autowired自动注入LoginLog和UserDao 的Bean中,可见Spring可以很好地将注解配置和XML配置统一起来。

这样,我们就完成了登录模块持久层所有的开发工作,接下来将着手业务层的开发和配置工作,我们将对业务层的业务类方法进行单元测试,到时就可以看到DAO的实际运行效果了,现在暂时把这两个DAO放在一边。

1.5 业务层

在景区网站登录实例中,业务层仅有一个业务类,即 UserService。UserService 负责将持久层的UserDao和LoginLoginDao组织起来完成用户/密码认证、登录日志记录等操作。

1.5.1 UserService

UserService业务接口有3个业务方法,其中hasMatchUser()用于检查用户名/密码的正确性;findUserByUserName()以用户名为条件加载User对象;loginSuccess()方法在用户登录成功后调用,更新用户最后登录时间和IP信息同时记录用户登录日志。

下面,我们来实现这个业务类,UserService的实现类需要调用DAO层的两个DAO完成业务逻辑操作,如代码清单1-9所示。

代码清单1-9 UserService

package com.smart.service;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import com.smart.dao.LoginLogDao;

import com.smart.dao.UserDao;

import com.smart.domain.LoginLog;

import com.smart.domain.User;

@Service①

将UserService标注为一个服务层的Bean

public class UserService {

@Autowired //②

private UserDao userDao;

@Autowired //③

private LoginLogDao loginLogDao;

public boolean hasMatchUser(String userName, String password) {

int matchCount =userDao.getMatchCount(userName, password);

return matchCount > 0;

}

public User findUserByUserName(String userName) {

return userDao.findUserByUserName(userName);

}

public void loginSuccess(User user) {

LoginLog loginLog = new LoginLog();

loginLog.setUserId(user.getUserId());

loginLog.setIp(user.getLastIp());

loginLog.setLoginDate(user.getLastVisit());

loginLogDao.insertLoginLog(loginLog);

}

}

首先,我们在① 处通过@Service注解,将UserService标注为一个服务层的Bean,然后在② 和③ 处注入 userDao 和 loginLogDao 这两个 DAO 层的 Bean。hasMatchUser()和findUserByUserName()业务方法简单地调用DAO来完成对应的功能;loginSuccess()方法根据入参user对象构造出LoginLog对象,并调用loginLogDao向t_login_log表中添加一条记录。

实战经验

在实际应用中,一般不会直接在数据库中以明文的方式保存用户的密码,因为这样很容易造成密码泄密的问题。所以需要将密码加密后以密文的方式进行保存;另外一种更有效的办法是仅保存密码的MD5摘要,由于相等的两字符串摘要值也相等,在登录验证时,通过比较摘要的方式就可以判断用户所输入的密码是否正确。由于不能通过密码摘要反推出原来的密码,即使内部人员可以查看用户信息表也无法知道用户的密码。所以,摘要存储方式已经成为大部分系统密码存储的通用方式。此外,为了防止黑客通过工具进行密码的暴力破解,目前大多数Web应用都使用了图片验证码功能,验证码具有一次性消费的特征,每次登录都不相同,这样工具暴力破解就无用武之地了。

loginSuccess()将两个DAO组织起来共同完成一个事务性的数据操作:更新t_user表记录并添加t_login_log表记录。但从UserService中却看不出任何事务操作的影子,这正是Spring的高明之处,它让用户从事务操作单调机械的代码中解脱出来,专注完成那些不可或缺的业务工作。通过 Spring 声明式事务配置,即可让业务类享受 EJB 声明式事务的好处。下一节将了解如何赋予业务类事务管理的能力。

1.5.2 在Spring中装配Service

事务管理的代码虽然不用出现在程序代码中,但必须以某种方式告诉 Spring 哪些业务类需要工作于事务环境下以及事务的规则等内容,以便Spring根据这些信息自动为目标业务类添加事务管理的功能。

打开原来的applicationContext.xml文件,更改代码如代码清单1-10所示。

代码清单1-10 applicationContext.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!--① 引入aop及tx命名空间所对应的Schema文件-->

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.1.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.1.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

<context:component-scan base-package="com.smart.dao"/>

<!--② 扫描service类包,应用Spring的注解配置-->

<context:component-scan base-package="com.smart.service"/>

<!--③ 配置事务管理器-->

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="dataSource" />

<!--④ 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务-->

<aop:config proxy-target-class="true">

<aop:pointcut id="serviceMethod"

expression=" execution(* com.smart.service..*(..))" />

<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />

</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="*" />

</tx:attributes>

</tx:advice>

</beans>

在① 处<beans>的声明处再添加aop和tx命名空间的schema定义文件的说明,这样在配置文件中就可以使用这两个空间中的配置标签了。

在② 处将com.smart.service添加到上下文扫描路径中,以便使service包中类的Spring注解生效。

在③ 处定义了一个基于数据源的DataSourceTransactionManager事务管理器,该事务管理器负责声明式事务的管理。该管理器需要引用dataSourceBean。

在④ 处通过AOP及tx命名空间的语法,以AOP的方式为com.smart.service包下所有类的所有方法都添加了事务增强,即它们都将工作于事务环境中。关于Spring事务的配置,详见本书的第6章。

这样,就完成了业务层的程序开发和配置工作,接下来,需要对该业务类进行简单的单元测试,以便检验业务方法的正确性。

1.5.3 单元测试

TestNG是一种基于注释的新一代单元测试框架,通过添加灵活的装置、测试分类、参数测试和依赖测试等特性来克服JUnit的不足之处。因此,本书的所有示例代码将采用TestNG6.3.1作为测试基础框架。首先在pom.xml文件中配置TestNG、Spring-test两个类库依赖,如代码清单1-11所示。

代码清单1-11 pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/

xsd/maven-4.0.0.xsd">

...

<dependencies>

...

<!--依赖的测试类库-->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>${org.springframework.version}</version>

</dependency>

<dependency>

<groupId>org.testng</groupId>

<artifactId>testng</artifactId>

<version>${testng.version}</version>

</dependency>

</dependencies>

</project>

选中单元测试所在的包文件夹 service 并单击鼠标右键,执行 New→Java Class 命令创建UserService的单元测试类,单击OK按钮,创建UserServiceTest的单元用例,并编写如代码清单1-12所示的测试代码。

代码清单1-12 UserServiceTest

package com.smart.service;

import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;

import org.testng.annotations.*;

import static org.testng.Assert.*;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import com.smart.domain.User;

@ContextConfiguration(locations={"/applicationContext.xml"}) ①

启动Spring容器

public class UserServiceTest

extends AbstractTestNGSpringContextTests{ ②

基于TestNG的Spring测试框架

@Autowired

private UserService userService; ③

注入Spring容器中的Bean

@Test ④

标注测试方法

public void hasMatchUser() {

boolean b1 = userService.hasMatchUser("admin", "123456");

boolean b2 = userService.hasMatchUser("admin", "1111");

assertTrue(b1);

assertTrue(!b2);

}

@Test

public void findUserByUserName() {

User user = userService.findUserByUserName("admin");

assertEquals(user.getUserName(), "admin");

}

}

首先,Spring 3.1 的测试框架可以和 TestNG 整合,通过 Spring 提供的测试基类AbstractTestNGSpringContextTests ,可以将 Spring 容器和 TestNG 测试框架整合。@ContextConfiguration也是Spring提供的注解,它用于指定Spring的配置文件。

在测试类中可以使用Spring的@Autowired将Spring容器中的Bean注入测试类中。在测试方法前通过TestNG的@Test注解即可将方法标注为测试方法。

在IDEA中,选中UserServiceTest测试用例,单击鼠标右键,选择Run“UserServiceTest”菜单项,运行该测试用例以检验业务类方法的正确性。

从单元测试的运行结果(见图1-12)可以看到3个业务方法已经成功执行,在后台数据库中,用户将发现已经有一条新的登录日志添加到t_login_log表中。关于Spring应用测试的内容,可以参见本书第8章的内容。

图1-12 TestUserService的运行结果

1.6 展现层

业务层和持久层的开发任务已经完成,该是为程序提供界面的时候了。Struts MVC框架由于抢尽天时地利,成为当下最流行的展现层框架。但也有很多人认为 Spring MVC 相比较于Struts 更简单、更强大、更优雅。此外,由于 Spring MVC 出自于 Spring 之手,因此和 Spring容器没有任何不兼容性,显得天衣无缝。

Spring 1.5 新增了基于注解的MVC,而且Spring 3.1还提供了REST风格的MVC,Spring MVC已经变得轻便、强大、易用。我们将会在本书的第8章中学习Spring MVC的详细内容。

1.6.1 配置Spring MVC框架

首先需要对web.xml文件进行配置,以便Web容器启动时能够自动启动Spring容器,如代码清单1-13所示。

代码清单1-13 自动启动Spring 容器的配置

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="1.5"

xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<!--① 从类路径下加载Spring配置文件,classpath关键字特指在类路径下加载-->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

classpath:applicationContext.xml

</param-value>

</context-param>

<!--② 负责启动Spring容器的监听器,它将引用① 处的上下文参数获得Spring配置文件地址-->

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

</web-app>

首先,通过Web容器上下文参数指定Spring配置文件的地址,如① 所示。多个配置文件可用逗号或空格分隔,建议采用逗号分隔的方式。在② 处指定 Spring 所提供的 ContextLoaderListener的Web容器监听器,该监听器在Web容器启动时自动运行,它会根据contextConfigLocation Web容器参数获取Spring配置文件,并启动Spring容器。注意需要将log4J.propertis日志配置文件放置在类路径下,以便日志引擎自动生效。

接下来,需要配置Spring MVC相关的信息,Spring MVC像Struts一样,也通过一个Servlet截获URL请求,然后再进行相关的处理,如代码清单1-14所示。

代码清单1-14 Spring MVC地址映射

<!-- Spring MVC的主控Servlet -->

<servlet> ①

<servlet-name>viewspace</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

<!-- Spring MVC处理的URL -->

<servlet-mapping>②

<servlet-name>viewspace</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

在① 处声明了一个Servlet,Spring MVC也拥有一个Spring配置文件(稍后会涉及),该配置文件的文件名和此处定义的Servlet名有一个契约:即采用<Servlet名>-servlet.xml的形式。在这里,因为 Servlet 名为 viewspace ,所以在/WEB-INF 目录下必须提供一个 viewspace-servlet.xml 的Spring MVC 配置文件,但这个配置文件无须通过web.xml 的contextConfigLocation上下文参数进行声明,因为Spring MVC的Servlet会自动将viewspace-servlet.xml和Spring其他的配置文件进行拼装。

在② 处对这个Servlet的URL路径映射进行定义,在这里让所有以.html为后缀的URL都能被viewspace Servlet截获,进而转由Spring MVC框架进行处理。我们知道,在Struts框架中,一般将URL后缀配置为*.do,在Webwork中一般配置为*.action,其实,框架本身和URL模式没有任何关系,用户大可使用喜欢的任何后缀。使用.html 后缀,一方面,用户不能通过 URL直接知道开发者采用了何种服务端技术;另一方面,.html 是静态网页的后缀,可以骗过搜索引擎,增加被收录的概率,所以推荐采用这种后缀。对于那些真正的无须任何动态处理的静态网页,则可以使用.htm后缀加以区分,以避免被框架截获。

请求被Spring MVC截获后,首先根据请求的URL查找到目标的处理控制器,并将请求参数封装成一个“命令”对象一起传给控制器处理,控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。

1.6.2 处理登录请求

POJO控制器类

首先要编写的是LoginController,它负责处理登录请求,完成登录业务,并根据登录成功与否转向欢迎页面或失败页面,如代码清单1-15所示。

代码清单1-15 LoginController.java

package com.smart.web;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.servlet.ModelAndView;

import com.smart.domain.User;

import com.smart.service.UserService;

@Controller ①

标注成为一个Spring MVC的Controller

@RequestMapping(value = "/admin"

public class LoginController{

@Autowired

private UserService userService;

@RequestMapping(value = " /login.html") ②

负责处理/login.html的请求

public String loginPage(){

return "login";

}

@RequestMapping(value = "/loginCheck.html") ③

负责处理/loginCheck.html的请求

public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){

boolean isValidUser =

userService.hasMatchUser(loginCommand.getUserName(),

loginCommand.getPassword());

if (!isValidUser) {

return new ModelAndView("login", "error", "用户名或密码错误。");

} else {

User user = userService.findUserByUserName(loginCommand

.getUserName());

user.setLastIp(request.getRemoteAddr());

user.setLastVisit(new Date());

userService.loginSuccess(user);

request.getSession().setAttribute("user", user);

return new ModelAndView("main");

}

}

}

在① 处通过Spring MVC的@Controller注解可以将任何一个POJO的类标注为Spring MVC的控制器,处理HTTP的请求。当然标注了@Controller的类首先会是一个Bean,所以可以使用@Autowired进行Bean的注入。

一个控制器可以拥有多个对应不同HTTP请求路径的处理方法,通过@RequestMapping指定方法如何映射请求路径,如② 和③ 所示。

请求的参数会根据参数名称默认契约自动绑定到响应方法的入参中,在③ 处的loginCheck (HttpServletRequest request,LoginCommand loginCommand)方法中,请求参数会按名称匹配绑定到loginCommand的入参中。

请求响应方法可以返回一个ModelAndView,或直接返回一个字符串,Spring MVC会解析之并转向目标响应页面。

ModelAndView 对象既包括了视图信息又包括了视图渲染所需的模型数据信息,在这里用户仅需要了解它代表一个视图就可以了,在后面的内容中,读者将了解到Spring MVC如何根据这个对象转向真正的页面。

前面使用到的LoginCommand对象是一个POJO,它没有继承于特定的父类或实现特定的接口。LoginCommand类仅包括用户/密码这两个属性(和请求的用户/密码参数名称一样),如代码清单1-16所示。

代码清单1-16 LoginCommand

package com.smart.web;

public class LoginCommand {

private String userName;

private String password;

//省略get/setter方法

}

Spring MVC配置文件

编写好LoginCommand后,需要在viewspace-servlet.xml中声明该控制器,扫描Web路径,指定Spring MVC的视图解析器,如代码清单1-17所示。

代码清单1-17 viewspace-servlet.xml

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<!--① 扫描web包,应用Spring的注解-->

<context:component-scan base-package="com.smart.web"/>

<!--② 配置视图解析器,将ModelAndView及字符串解析为具体的页面-->

<bean

class="org.springframework.web.servlet.view.InternalResourceViewResolver"

p:viewClass="org.springframework.web.servlet.view.JstlView"

p:prefix="/WEB-INF/jsp/"

p:suffix=".jsp" />

</beans>

ModelAndView的解析配置

在代码清单 1-15 的③ 处,控制器根据登录处理结果分别返回 ModelAndView ("login","error", "用户名或密码错误。")和ModelAndView("main")。ModelAndView的第一个参数代表视图的逻辑名,第二个和第三个参数分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数名放置到request的属性中。

Spring MVC 如何将视图逻辑名解析为具体的视图页面呢?解决的思路也和上面的方法类似,需要在viewspace-servlet.xml中提供一个定义解析规则的Bean,如代码清单1-18所示。

代码清单1-18 viewspace-servlet.xml视图解析规则

<!--通过prefix指定在视图名前所添加的前缀,通过suffix指定在视图名后添加的后缀-->

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"

p:viewClass="org.springframework.web.servlet.view.JstlView"

p:prefix="/WEB-INF/jsp/"

p:suffix=".jsp" />

Spring MVC 为视图名到具体视图的映射提供了许多可供选择的方法。在这里,使用了InternalResourceViewResolver,它通过为视图逻辑名添加前后缀的方式进行解析。如视图逻辑名“login”将解析为/WEB-INF/jsp/login.jsp;视图逻辑名“main”将解析为/WEB-INF/jsp/main.jsp。

1.6.3 JSP视图页面

景区网站登录模块共包括两个JSP页面,分别是登录页面login.jsp和管理主页面main.jsp,下面将完成这两个页面的开发工作。

登录页面login.jsp

登录页面login.jsp的代码如代码清单1-19所示。

代码清单1-19 login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<html>

<head>

<title>景区网站登录</title>

</head>

<body>

<c:if test="${!empty error}">①

<font color="red"><c:out value="${error}" /></font>

</c:if>

<form action="<c:url value="/ loginCheck.html "/>" method= "post">②

用户名:

<input type="text" name="userName">

<br>

密 码:

<input type="password" name="password">

<br>

<input type="submit" value="登录" />

<input type="reset" value="重置" />

</form>

</body>

</html>

login.jsp页面既作为登录页面又作为登录失败后的响应页面。因此在① 处使用JSTL标签对登录错误返回的信息进行处理。JSTL标签中引用了error变量,这个变量正是LoginController中返回的ModelAndView("login", "error", "用户名或密码错误。") 对象所声明的error 参数。

login.jsp的登录表单提交到/loginController.html,如② 所示。<c:urlvalue="/loginController.html"/>的JSTL标签会在URL前自动加上应用程序部署根目录,假设应用部署在网站的viewspace目录下,<c:url/>标签将输出/viewspace/loginController.html。通过<c:url/>标签很好地解决了开发和应用部署目录不一致的问题。

由于 login.jsp 放置在 WEB-INF/jsp 目录下,无法直接通过 URL 进行调用,它由LoginController 控制类中标注了@RequestMapping(value = "/login.html")的 loginPage()进行转发,见代码清单1-15。

景区管理主页面main.jsp

登录成功的欢迎页面很简单,仅使用JSTL标签显示一条欢迎信息即可,如代码清单1-20所示。

代码清单1-20 main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title>景区后台管理主页面</title>

</head>

<body>

${user.userName},欢迎您进入景区后台管理!①

</body>

</html>

① 处访问Session域中的user对象,显示用户名和积分信息。这样,就完成了实例所有的开发任务。

1.7 运行Web应用

在IDEA中运行Web应用前,首先需要配置好Web应用服务器,这里使用Maven的Web容器插件运行应用。打开当前模块pom.xml文件,添加构建插件配置信息,如代码清单1-21所示。

代码清单1-21 pom.xml

<build>

<finalName>chapter1</finalName>

<plugins>

<!-- Jetty插件 -->

<plugin>

<groupId>org.mortbay.jetty</groupId>

<artifactId>maven-jetty-plugin</artifactId>

<version>6.1.5</version>

<configuration>

<webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>

<scanIntervalSeconds>3</scanIntervalSeconds>

<contextPath>/chapter1</contextPath>

<connectors>

<connector

implementation="org.mortbay.jetty.nio.SelectChannelConnector">

<port>8080</port>

</connector>

</connectors>

</configuration>

</plugin>

</plugins>

</build>

单击IDEA右则的Maven Projects选项卡,进入当前模块Maven管理界面,如图1-13所示。然后双击 jetty:run-expolded 就可以启动jetty容器。

图1-13 Jetty运行目录

启动Jetty后,在浏览器中输入http://localhost:8080/chapter1/admin/login.html进入景区网站管理员登录页面,如图1-14所示。

如果输入错误的用户名/密码,登录模块将给出错误提示。这里,输入 admin/123456,单击“登录”按钮后,就可以登录到后台管理页面中,如图1-15所示。

图1-14 在浏览器中访问应用
图1-15 登录成功后的欢迎页面

1.8 小结

本章概述了Spring的发展历程,并用Spring MVC、Spring JDBC以及Spring的声明式事务等技术实现了一个常见的景区网站登录模块,让大家对如何使用Spring框架构建Web应用拥有了切身的体验,同时还了解了开发一个简单的Web应用所需要经历的开发过程。

也许用户会抱怨该实例功能的简单性和开发过程的复杂性有点不成正比。但对于一个具有扩展性、灵活性的Web应用来说,这些步骤往往都是必需的,其实我们在完成实例开发的同时也完成了 Web 框架的搭建,为新功能模块的添加夯实地基,后继的模块开发仅需要在此基础上进行添砖加瓦的工作,当新功能加入时,读者就会发现在这里的投入是值得的。

图书在版编目(CIP)数据

Spring 3.0就这么简单/陈雄华,林开雄编著.--北京:人民邮电出版社,2013.1

ISBN 978-7-115-29839-3

Ⅰ.① S… Ⅱ.① 陈…② 林… Ⅲ.① JAVA语言—程序设计 Ⅳ.① TP312

中国版本图书馆CIP数据核字(2012)第259942号

内容提要

本书的主旨就是帮助读者尽快上手,掌握 Spring 3.0 的核心内容,正确地进行项目实战,同时汲取Spring的思想,并最终将这种思想灵活运用到实际工作中。

本书主要介绍了Spring 3.0的核心内容,不仅讲解了Spring 3.0的基础知识,还深入讨论了Spring IoC容器、Spring AOP、使用Spring JDBC 访问数据库、集成Hibernate、Spring 的事务管理、Spring MVC、单元测试、敏捷开发技术等内容,帮助读者快速入门并可以立刻使用 Spring进行项目实战。本书展示了如何使用Spring自己动手打造服务平台框架,并在本书的最后给出一个开发实战案例。

本书语言简洁,实例丰富,可帮助读者迅速掌握使用 Spring 3.0 进行开发所需的各种技能。本书适合于具有一定Java编程基础的读者,以及在Java平台下进行各类软件开发的开发人员和测试人员等。

Spring 3.0就这么简单

◆编著 陈雄华 林开雄

责任编辑 杜洁

◆人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京鑫正大印刷有限公司印刷

◆开本:800×1000 1/16

印张:24.5

字数:530千字  2013年1月第1版

印数:1-3000册  2013年1月北京第1次印刷

ISBN 978-7-115-29839-3

定价:59.00元

读者服务热线:(010)67132692 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
云原生Spring实战Spring Boot与?Kubernetes实践
云原生Spring实战Spring Boot与?Kubernetes实践
Spring实战(第6版)
Spring实战(第6版)
Java研发自测入门与进阶
Java研发自测入门与进阶
Spring核心技术和案例实战
Spring核心技术和案例实战
Java EE企业级应用开发实战(Spring Boot+Vue+Element)
Java EE企业级应用开发实战(Spring Boot+Vue+Element)

相关文章

相关课程