- Spring 3.0就這么簡單
- 陳雄華 林開雄
- 694字
- 2019-01-01 22:41:17
第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框架的搭建,為新功能模塊的添加夯實地基,后繼的模塊開發僅需要在此基礎上進行添磚加瓦的工作,當新功能加入時,讀者就會發現在這里的投入是值得的。