第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以海纳百川的胸怀整合了开源世界里众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
从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等)的直接支持。
· 降低Java EE API的使用难度。
Spring为很多难用的Java EE API(如JDBC、JavaMail、远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,大大降低了这些Java EE API的使用难度。
· Java源码是经典的学习范例。
Spring的源码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式的灵活运用以及对Java技术的高深造诣。Spring框架源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用开发水平,那么学习和研究Spring源码就可以让你获得意想不到的效果。
1.1.3 Spring体系结构
Spring框架由1400多个类组成,整个框架按其所属功能可以划分为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→New Module→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领域对象
登录日志领域对象
用户每次登录成功后,记录一条登录日志,该登录日志包括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
在Spring 1.5以后,可以调用注解的方式定义Bean,较之于XML配置方式,注解配置方式的简单性非常明显,已经被广泛接受,成为一种趋势。所以除非没有办法,否则都应尽量采用注解的配置方式。
这里用@Repository定义了一个DAO Bean,使用@Autowired将Spring容器中的Bean注入进来。关于Spring的注解配置,将会在第2章详细讨论。
传统的JDBC API太底层,即使用户执行一条最简单的数据查询操作,都必须执行如下的过程:获取连接→创建Statement→执行数据操作→获取结果→关闭Statement→关闭结果集→关闭连接,除此之外还需要进行异常处理的操作。如果使用传统JDBC API进行数据访问操作,可能会有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另外两个方法
findUserByUserName()方法稍微有点复杂。这里使用到了JdbcTemplate#query()方法,该方法的签名为query(String sql,Object[] args, RowCallbackHandler rch),它有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
首先,我们在❶处通过@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事务管理器,该事务管理器负责声明式事务的管理。该管理器需要引用dataSource Bean。
在❹处通过AOP及tx命名空间的语法,以AOP的方式为com.smart.service包下所有类的所有方法都添加了事务增强,即它们都将工作于事务环境中。关于Spring事务的配置,详见本书的第6章。
这样,就完成了业务层的程序开发和配置工作,接下来,需要对该业务类进行简单的单元测试,以便检验业务方法的正确性。
1.5.3 单元测试
TestNG是一种基于注释的新一代单元测试框架,通过添加灵活的装置、测试分类、参数测试和依赖测试等特性来克服JUnit的不足之处。因此,本书的所有示例代码将采用TestNG 6.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
首先,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
在❶处通过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:url value= "/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所示。
图1-14 在浏览器中访问应用
如果输入错误的用户名/密码,登录模块将给出错误提示。这里,输入admin/123456,单击“登录”按钮后,就可以登录到后台管理页面中,如图1-15所示。
图1-15 登录成功后的欢迎页面
1.8 小结
本章概述了Spring的发展历程,并用Spring MVC、Spring JDBC以及Spring的声明式事务等技术实现了一个常见的景区网站登录模块,让大家对如何使用Spring框架构建Web应用拥有了切身的体验,同时还了解了开发一个简单的Web应用所需要经历的开发过程。
也许用户会抱怨该实例功能的简单性和开发过程的复杂性有点不成正比。但对于一个具有扩展性、灵活性的Web应用来说,这些步骤往往都是必需的,其实我们在完成实例开发的同时也完成了Web框架的搭建,为新功能模块的添加夯实地基,后继的模块开发仅需要在此基础上进行添砖加瓦的工作,当新功能加入时,读者就会发现在这里的投入是值得的。