- Spring 3.0就這么簡(jiǎn)單
- 陳雄華 林開(kāi)雄
- 565字
- 2019-01-01 22:41:16
第1章 快速入門(mén)
本章通過(guò)一個(gè)簡(jiǎn)單的例子展現(xiàn)開(kāi)發(fā)Spring Web應(yīng)用的整體過(guò)程,通過(guò)這個(gè)實(shí)例,讀者可以快速進(jìn)入Spring Web應(yīng)用的世界。實(shí)例應(yīng)用按持久層、業(yè)務(wù)層和展現(xiàn)層進(jìn)行組織,從底層DAO程序到Web展現(xiàn)程序逐層演進(jìn),一步步地搭建起一個(gè)完整的實(shí)例。通過(guò)本章的學(xué)習(xí),讀者可以獨(dú)立完成一個(gè)典型的基于Spring的Web應(yīng)用。
本章主要內(nèi)容:
· Spring概述
· 用戶(hù)登錄實(shí)例介紹
· 基于Spring JDBC的持久層實(shí)現(xiàn)
· 基于Spring聲明式事務(wù)的業(yè)務(wù)層實(shí)現(xiàn)
· 基于Spring MVC的展現(xiàn)層實(shí)現(xiàn)
· 在IntelliJ IDEA中開(kāi)發(fā)Web應(yīng)用的過(guò)程
· 運(yùn)行Web應(yīng)用
本章亮點(diǎn):
· 非傳統(tǒng)Hello World的快速入門(mén)實(shí)例
· 基于Maven模塊化來(lái)講解開(kāi)發(fā)過(guò)程
· 詳盡的開(kāi)發(fā)過(guò)程,使讀者快速上手
1.1 Spring概述
1.1.1 認(rèn)識(shí)Spring
Spring是眾多Java開(kāi)源項(xiàng)目中的一員,唯一不同的是:它秉承破除權(quán)威迷信,一切從實(shí)踐中來(lái)到實(shí)踐中去的信念,宛如阿基米德手中的杠桿,以一己之力撼動(dòng)了Java EE傳統(tǒng)重量級(jí)框架堅(jiān)如磐石的大廈。
要用一兩句話總結(jié)出Spring的所有內(nèi)涵確實(shí)有點(diǎn)困難,但是為了先給讀者一個(gè)基本的印象,我們嘗試進(jìn)行以下概括。
Spring是分層的Java SE/EE應(yīng)用一站式的輕量級(jí)開(kāi)源框架,以反轉(zhuǎn)控制(Inverse of Control,IoC)和面向切面編程(Aspect Oriented Programming,AOP)為內(nèi)核,提供了展現(xiàn)層Spring MVC、持久層Spring JDBC以及業(yè)務(wù)層事務(wù)管理等眾多的企業(yè)級(jí)應(yīng)用技術(shù),此外,Spring以海納百川的胸懷整合了開(kāi)源世界里眾多著名的第三方框架和類(lèi)庫(kù),逐漸成為使用最多的Java EE企業(yè)應(yīng)用開(kāi)源框架。
從2004年發(fā)布第一個(gè)版本以來(lái),Spring逐漸占據(jù)了Java開(kāi)發(fā)人員的視線,獲得了開(kāi)源社區(qū)的一片贊譽(yù)之聲。
1.1.2 Spring帶給我們什么
也許有很多的開(kāi)發(fā)者曾經(jīng)被EJB的過(guò)度宣傳所迷惑,成為EJB的擁躉,并因此擁有一段痛苦的開(kāi)發(fā)經(jīng)歷。EJB的復(fù)雜源于它對(duì)所有的企業(yè)應(yīng)用采用統(tǒng)一的標(biāo)準(zhǔn),它認(rèn)為所有的企業(yè)應(yīng)用都需要分布式對(duì)象、遠(yuǎn)程事務(wù),因此造就了EJB框架的極度復(fù)雜。這種復(fù)雜不僅造成陡峭的學(xué)習(xí)曲線,而且給開(kāi)發(fā)、測(cè)試、部署都造成了很多額外的要求和工作量。其中最大的詬病就是難于測(cè)試,因?yàn)檫@種測(cè)試不能脫離EJB容器,每次測(cè)試都需要進(jìn)行應(yīng)用部署并啟動(dòng)EJB容器,而部署和啟動(dòng)EJB是一項(xiàng)費(fèi)時(shí)費(fèi)力的重型操作,其結(jié)果是測(cè)試工作往往成為開(kāi)發(fā)工作的瓶頸。
Spring認(rèn)為Java EE的開(kāi)發(fā)應(yīng)該更容易、更簡(jiǎn)單。在實(shí)現(xiàn)這一目標(biāo)時(shí),Spring一直貫徹并遵守“好的設(shè)計(jì)優(yōu)于具體實(shí)現(xiàn),代碼應(yīng)易于測(cè)試”這一理念,并最終帶給我們一個(gè)易于開(kāi)發(fā)、便于測(cè)試而又功能齊全的開(kāi)發(fā)框架。概括起來(lái),Spring給我們帶來(lái)以下幾方面的好處。
· 方便解耦,簡(jiǎn)化開(kāi)發(fā)。
通過(guò)Spring提供的IoC容器,可以將對(duì)象之間的依賴(lài)關(guān)系交由Spring進(jìn)行控制,避免硬編碼所造成的過(guò)度程序耦合。有了Spring,用戶(hù)就不必再為單實(shí)例模式類(lèi)、屬性文件解析等這些很底層的需求編寫(xiě)代碼,而可以更加專(zhuān)注于上層的應(yīng)用。
· AOP編程的支持。
通過(guò)Spring提供的AOP功能,用戶(hù)可以方便地進(jìn)行面向切面編程,許多不容易用傳統(tǒng)面向?qū)ο缶幊蹋∣OP)實(shí)現(xiàn)的功能都可以通過(guò)AOP輕松應(yīng)對(duì)。
· 聲明式事務(wù)的支持。
在Spring中,用戶(hù)可以從單調(diào)煩悶的事務(wù)管理代碼中解脫出來(lái),通過(guò)聲明式事務(wù)靈活地進(jìn)行事務(wù)管理,提高開(kāi)發(fā)效率和質(zhì)量。
· 方便程序的測(cè)試。
可以用非容器依賴(lài)的編程方式進(jìn)行幾乎所有的測(cè)試工作,在Spring中,測(cè)試不再是昂貴的操作,而是隨手可做的事情。
· 方便集成各種優(yōu)秀的框架。
Spring不排斥各種優(yōu)秀的開(kāi)源框架,相反,Spring可以降低各種框架的使用難度,Spring提供了對(duì)各種優(yōu)秀框架(如Struts、Hibernate、Hessian、Quartz等)的直接支持。
· 降低Java EE API的使用難度。
Spring為很多難用的Java EE API(如JDBC、JavaMail、遠(yuǎn)程調(diào)用等)提供了一個(gè)薄薄的封裝層,通過(guò)Spring的簡(jiǎn)易封裝,大大降低了這些Java EE API的使用難度。
· Java源碼是經(jīng)典的學(xué)習(xí)范例。
Spring的源碼設(shè)計(jì)精妙、結(jié)構(gòu)清晰、匠心獨(dú)用,處處體現(xiàn)著大師對(duì)Java設(shè)計(jì)模式的靈活運(yùn)用以及對(duì)Java技術(shù)的高深造詣。Spring框架源碼無(wú)疑是Java技術(shù)的最佳實(shí)踐范例。如果想在短時(shí)間內(nèi)迅速提高自己的Java技術(shù)水平和應(yīng)用開(kāi)發(fā)水平,那么學(xué)習(xí)和研究Spring源碼就可以讓你獲得意想不到的效果。
1.1.3 Spring體系結(jié)構(gòu)
Spring框架由1400多個(gè)類(lèi)組成,整個(gè)框架按其所屬功能可以劃分為5個(gè)主要模塊,如圖1-1所示。

圖1-1 Spring框架結(jié)構(gòu)
從整體看,這5個(gè)主要模塊幾乎為企業(yè)應(yīng)用提供了所需的一切,從持久層、業(yè)務(wù)層到展現(xiàn)層都擁有相應(yīng)的支持。就像呂布的赤兔馬和方天畫(huà)戟、秦瓊的黃驃馬和熟銅锏,IoC和AOP是Spring所依賴(lài)的根本。在此基礎(chǔ)上,Spring整合了各種企業(yè)應(yīng)用開(kāi)源框架和許多優(yōu)秀的第三方類(lèi)庫(kù),成為Java企業(yè)應(yīng)用full-stack的開(kāi)發(fā)框架。Spring框架的精妙之處在于:開(kāi)發(fā)者擁有自由的選擇權(quán),Spring不會(huì)將自己的意志強(qiáng)加給開(kāi)發(fā)者,因?yàn)獒槍?duì)某個(gè)領(lǐng)域問(wèn)題,Spring往往支持多種實(shí)現(xiàn)方案。當(dāng)希望選用不同的實(shí)現(xiàn)方案時(shí),Spring又能保證過(guò)渡的平滑性。
· IoC。
Spring核心模塊實(shí)現(xiàn)了IoC的功能,它將類(lèi)和類(lèi)之間的依賴(lài)從代碼中脫離出來(lái),用配置的方式進(jìn)行依賴(lài)關(guān)系描述,由IoC容器負(fù)責(zé)依賴(lài)類(lèi)之間的創(chuàng)建、拼接、管理、獲取等工作。BeanFactory接口是Spring框架的核心接口,它實(shí)現(xiàn)了容器的許多核心功能。
Context模塊構(gòu)建于核心模塊之上,擴(kuò)展了BeanFactory的功能,添加了i18n國(guó)際化、Bean生命周期控制、框架事件體系、資源加載透明化等多項(xiàng)功能。此外,該模塊還提供了許多企業(yè)級(jí)服務(wù)的支持,如郵件服務(wù)、任務(wù)調(diào)度、JNDI定位、EJB集成、遠(yuǎn)程訪問(wèn)等。ApplicationContext是Context模塊的核心接口。
表達(dá)式語(yǔ)言模塊是統(tǒng)一表達(dá)式語(yǔ)言(unified EL)的一個(gè)擴(kuò)展,該表達(dá)式語(yǔ)言用于查詢(xún)和管理運(yùn)行期的對(duì)象,支持設(shè)置和獲取對(duì)象屬性,調(diào)用對(duì)象方法,操作數(shù)組、集合等。還提供了邏輯表達(dá)式運(yùn)算、變量定義等功能。使用它就可以方便地通過(guò)表達(dá)式串和Spring IoC容器進(jìn)行交互。
· AOP模塊。
AOP是繼OOP之后,對(duì)編程設(shè)計(jì)思想影響最大的技術(shù)之一。AOP是進(jìn)行橫切邏輯編程的思想,它開(kāi)拓了人們考慮問(wèn)題的思路。在AOP模塊里,Spring提供了滿足AOP Alliance規(guī)范的實(shí)現(xiàn),此外,還整合了AspectJ這種AOP語(yǔ)言級(jí)的框架。在Spring里實(shí)現(xiàn)AOP編程有許多的選擇。Java 5.0引入java.lang.instrument,允許在JVM啟動(dòng)時(shí)啟用一個(gè)代理類(lèi),通過(guò)該代理類(lèi)在運(yùn)行期修改類(lèi)的字節(jié)碼,改變一個(gè)類(lèi)的功能,實(shí)現(xiàn)AOP的功能。
· 數(shù)據(jù)訪問(wèn)和集成。
任何應(yīng)用程序的核心問(wèn)題都是對(duì)數(shù)據(jù)的訪問(wèn)和操作。數(shù)據(jù)有很多表現(xiàn)形式,如數(shù)據(jù)表、XML、消息等,而每種數(shù)據(jù)形式又擁有不同的數(shù)據(jù)訪問(wèn)技術(shù)(如數(shù)據(jù)表的訪問(wèn)既可以直接通過(guò)JDBC,也可以通過(guò)Hibernate或iBatis)。
Spring站在DAO的抽象層面,建立了一套面向DAO層統(tǒng)一的異常體系,同時(shí)將各種訪問(wèn)數(shù)據(jù)的檢查型異常轉(zhuǎn)換為非檢查型異常,為整合各種持久層框架提供基礎(chǔ)。其次,Spring通過(guò)模板化技術(shù)對(duì)各種數(shù)據(jù)訪問(wèn)技術(shù)進(jìn)行了薄層的封裝,將模式化的代碼隱藏起來(lái),使數(shù)據(jù)訪問(wèn)的程序得到大幅簡(jiǎn)化。這樣,Spring就建立起了和數(shù)據(jù)形式及訪問(wèn)技術(shù)無(wú)關(guān)的統(tǒng)一的DAO層,借助AOP技術(shù),Spring提供了聲明式事務(wù)的功能。
· Web及遠(yuǎn)程操作。
該模塊建立在Application Context模塊之上,提供了Web應(yīng)用的各種工具類(lèi),如通過(guò)Listener或Servlet初始化Spring容器,將Spring容器注冊(cè)到Web容器中。其次,該模塊還提供了多項(xiàng)面向Web的功能,如透明化文件上傳,Velocity、FreeMarker、XSLT的支持。此外,Spring可以整合Struts、WebWork、Tapestry Web等MVC框架。
· Web及遠(yuǎn)程訪問(wèn)。
Spring提供了一個(gè)完整的類(lèi)似于Struts的MVC框架,稱(chēng)為Spring MVC。據(jù)說(shuō),Spring之所以也提供了一個(gè)MVC框架,是因?yàn)镽od Johnson想證明實(shí)現(xiàn)MVC其實(shí)是一項(xiàng)簡(jiǎn)單的工作。當(dāng)然,如果不希望使用Spring MVC,那么Spring對(duì)Struts、Tapestry等MVC框架的整合,一定也可以給你帶來(lái)方便。相對(duì)于Servlet的MVC,Spring在簡(jiǎn)化Portlet的開(kāi)發(fā)上也做了很多工作,開(kāi)發(fā)者可以從中受益。
此外,Spring在遠(yuǎn)程訪問(wèn)以及Web Service上提供了對(duì)很多著名框架的整合。由于Spring框架的擴(kuò)展性,特別是隨著Spring框架影響性的擴(kuò)大,越來(lái)越多框架主動(dòng)地支持Spring框架,讓Spring框架應(yīng)用的涵蓋面越來(lái)越寬廣。
1.2 實(shí)例功能概述
在進(jìn)行實(shí)例具體開(kāi)發(fā)步驟之前,有必要先了解實(shí)例的功能,以便對(duì)要實(shí)現(xiàn)的實(shí)例有一個(gè)整體性的認(rèn)識(shí)。
1.2.1 比Hello World更適用的實(shí)例
快速對(duì)Spring有一個(gè)切身的認(rèn)識(shí),沒(méi)有什么比通過(guò)一個(gè)實(shí)際的例子更適合的了。Hello World是比較經(jīng)典的入門(mén)實(shí)例,但Hello World太過(guò)簡(jiǎn)單,很難展現(xiàn)Spring的全貌,為了讓Spring的功能輪廓更加清晰,通過(guò)一個(gè)功能涵蓋面更廣的景區(qū)網(wǎng)站登錄模塊替換經(jīng)典的Hello World實(shí)例。選擇登錄功能模塊是出于以下3個(gè)原因。
· 大家對(duì)于登錄模塊的業(yè)務(wù)功能再熟悉不過(guò)了,無(wú)須在業(yè)務(wù)功能介紹上花費(fèi)時(shí)間。
· 登錄模塊麻雀雖小,五臟俱全,它涵蓋了持久層數(shù)據(jù)訪問(wèn)操作、業(yè)務(wù)層事務(wù)管理以及展現(xiàn)層MVC等企業(yè)應(yīng)用常見(jiàn)的功能。
· 本書(shū)希望通過(guò)一個(gè)景區(qū)網(wǎng)站貫穿始終,以便能夠由點(diǎn)及面,使讀者在單純技術(shù)性學(xué)習(xí)的酣戰(zhàn)中深刻理解應(yīng)用程序的整體開(kāi)發(fā)流程。
Spring擁有持久層、業(yè)務(wù)層和展現(xiàn)層的“原生技術(shù)”,分別是Spring JDBC、聲明式事務(wù)和Spring MVC。為了充分展現(xiàn)Spring本身的魅力,在本章中僅使用Spring的這些原生技術(shù),在以后的章節(jié)中,我們將學(xué)習(xí)其他的持久層和展現(xiàn)層技術(shù),只要用戶(hù)愿意,就可以平滑地將其過(guò)渡到其他技術(shù)實(shí)現(xiàn)中。
1.2.2 實(shí)例功能簡(jiǎn)介
景區(qū)網(wǎng)站登錄模塊的功能很簡(jiǎn)單,首先登錄頁(yè)面提供一個(gè)帶用戶(hù)名/密碼的輸入表單,用戶(hù)填寫(xiě)并提交表單后,服務(wù)端程序檢查是否有匹配的用戶(hù)名/密碼。如果用戶(hù)名/密碼不匹配,返回到登錄頁(yè)面,并給出提示。如果用戶(hù)名/密碼匹配,記錄用戶(hù)的成功登錄日志,更新用戶(hù)的最后登錄時(shí)間和IP地址,然后重定向到景區(qū)后臺(tái)歡迎頁(yè)面,如圖1-2所示。
在持久層擁有兩個(gè)DAO類(lèi),分別是UserDao和LoginLogDao,在業(yè)務(wù)層對(duì)應(yīng)一個(gè)業(yè)務(wù)類(lèi)UserService,在展現(xiàn)層擁有一個(gè)LoginController類(lèi)和兩個(gè)JSP頁(yè)面,分別是登錄頁(yè)面login.jsp和歡迎頁(yè)面main.jsp。
下面通過(guò)如圖1-3所示的時(shí)序圖來(lái)描述景區(qū)網(wǎng)站登錄模塊的整體交互流程。
(1)首先用戶(hù)訪問(wèn)login.jsp,返回帶用戶(hù)名/密碼表單的登錄頁(yè)面。

圖1-2 頁(yè)面流程

圖1-3 登錄模塊整體交互流程
(2)用戶(hù)在登錄頁(yè)面輸入用戶(hù)名/密碼,提交表單到服務(wù)器,Spring根據(jù)配置調(diào)用LoginController控制器來(lái)響應(yīng)登錄請(qǐng)求。
(3)LoginController調(diào)用UserService#hashMatchUser()方法,根據(jù)用戶(hù)名和密碼查詢(xún)是否存在匹配的用戶(hù),UserService內(nèi)部通過(guò)調(diào)用持久層的UserDao完成具體的數(shù)據(jù)庫(kù)訪問(wèn)操作。
(4)如果不存在匹配的用戶(hù),重定向到login.jsp頁(yè)面,并報(bào)告錯(cuò)誤,否則到下一步。
(5)LoginController調(diào)用UserService#findUserByUserName()方法,加載匹配的User對(duì)象并更新用戶(hù)最近一次的登錄時(shí)間和登錄IP地址。
(6)LoginController調(diào)用UserService#loginSuccess()方法,進(jìn)行登錄成功的業(yè)務(wù)處理,創(chuàng)建一個(gè)LoginLog對(duì)象,并利用LoginLogDao將其插入數(shù)據(jù)庫(kù)中。
(7)重定向到歡迎頁(yè)面main.jsp,歡迎頁(yè)面產(chǎn)生響應(yīng)返回給用戶(hù)。
實(shí)例的所有程序代碼都位于chapter1目錄下,本章后面的內(nèi)容將逐一實(shí)現(xiàn)以上步驟的功能,完成這個(gè)實(shí)例的所有細(xì)節(jié)。
1.3 環(huán)境準(zhǔn)備
在進(jìn)入實(shí)例的具體開(kāi)發(fā)之前,需要做一些環(huán)境的準(zhǔn)備工作,其中包括數(shù)據(jù)庫(kù)表的創(chuàng)建、項(xiàng)目工程的創(chuàng)建、規(guī)劃Spring配置文件等工作。本書(shū)使用MySQL 5.x數(shù)據(jù)庫(kù),如果用戶(hù)機(jī)器中還未安裝該數(shù)據(jù)庫(kù),可以從http://www.mysql.org/downloads下載并安裝。也可以直接使用熟悉的數(shù)據(jù)庫(kù),只需要調(diào)整創(chuàng)建表的腳本并對(duì)數(shù)據(jù)庫(kù)連接做相應(yīng)配置即可。
提示 MySQL 4.1.0以前的版本不支持事務(wù),MySQL 4.1.0本身也只對(duì)事務(wù)提供有限的支持,Spring的各種聲明式事務(wù)需要底層數(shù)據(jù)庫(kù)的支持,所以最好安裝MySQL 5.0或更高的版本。
1.3.1 創(chuàng)建庫(kù)表
(1)啟動(dòng)MySQL數(shù)據(jù)庫(kù)后,用DOS命令窗口登錄數(shù)據(jù)庫(kù)。
mysql>mysql -uroot -p1234--port 3309
分別指定用戶(hù)名和密碼,MySQL默認(rèn)運(yùn)行在3306端口,如果不是運(yùn)行在默認(rèn)端口,需要通過(guò)--port參數(shù)指定端口號(hào)。
(2)運(yùn)行以下腳本,創(chuàng)建實(shí)例對(duì)應(yīng)的數(shù)據(jù)庫(kù)。
mysql>DROP DATABASE IF EXISTS sampledb; mysql>CREATE DATABASE sampledb DEFAULT CHARACTER SET utf8; mysql>USE sampledb;
數(shù)據(jù)庫(kù)名為sampledb,默認(rèn)字符集采用UTF-8。
(3)創(chuàng)建實(shí)例所用的兩張表。
##創(chuàng)建用戶(hù)表
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;
##創(chuàng)建用戶(hù)登錄日志表
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表為用戶(hù)信息表,t_login_log為用戶(hù)登錄日志表。其中ENGINE=InnoDB指定表的引擎為InnoDB類(lèi)型,該類(lèi)型表支持事務(wù)。MySQL默認(rèn)采用MyISAM引擎,該類(lèi)型表不支持事務(wù),僅存儲(chǔ)數(shù)據(jù),優(yōu)點(diǎn)在于讀寫(xiě)很快。對(duì)于景區(qū)網(wǎng)站型應(yīng)用系統(tǒng)的表來(lái)說(shuō),大可使用不支持事務(wù)的MyISAM引擎,但本書(shū)出于演示事務(wù)的目的,所有表均采用支持事務(wù)的InnoDB引擎。
(4)初始化一條數(shù)據(jù),用戶(hù)名/密碼為admin/123456。
##插入初始化數(shù)據(jù)
mysql>INSERT INTO t_user (user_name,password) VALUES('admin','123456'); mysql>COMMIT;
用戶(hù)也可以直接運(yùn)行腳本文件來(lái)完成以上所有工作,創(chuàng)建數(shù)據(jù)庫(kù)表的腳本文件位于chapter1/src/main/schema/sampledb.sql,下面提供了兩種運(yùn)行腳本的方法。
· 直接通過(guò)mysql命令運(yùn)行。
假設(shè)從github.com下載本書(shū)示例代碼中的內(nèi)容并復(fù)制到D:\actionSpring目錄下,則在DOS命令窗口中運(yùn)行以下命令。
D:\> mysql -u root -p1234--port 3309 <D:\actionSpring\chapter1\schema\sampledb.sql
· 也可以在登錄MySQL后,通過(guò)source命令運(yùn)行腳本。
mysql>source D:\actionSpring\chapter1\src\main\schema\sampledb.sql
1.3.2 建立工程
考慮到IntelliJ IDEA是一款非常優(yōu)秀且強(qiáng)大的IDE工具,特別是對(duì)Maven等工具提供了良好支持,越來(lái)越受企業(yè)開(kāi)發(fā)人員的喜愛(ài)。本書(shū)的所有示例都采用IDEA(11.0版本)進(jìn)行開(kāi)發(fā)。將IDEA的工作空間設(shè)置于D:\actionSpring,為了避免因路徑不一致而引起的各種問(wèn)題,請(qǐng)盡量保證工作空間和本書(shū)的路徑一致。示例工程源文件和配置文件都使用UTF-8編碼格式,UTF-8可以很好地解決國(guó)際化問(wèn)題,同時(shí)避免不受歡迎的中文亂碼問(wèn)題,用戶(hù)可以執(zhí)行File→Settings→File Encodings命令將IDEA的工作空間編碼格式設(shè)置為UTF-8編碼格式。
在D:\actionSpring中建立一個(gè)名為chapter的Maven主工程項(xiàng)目,如圖1-4所示。
設(shè)置項(xiàng)目名稱(chēng)為chapter,項(xiàng)目路徑為D:\actionSpring\chapter,并選擇項(xiàng)目類(lèi)型為Maven Module,單擊Next按鈕,進(jìn)入Maven工程設(shè)置窗口,如圖1-5所示。
設(shè)置Maven的GroupId為com.smart,ArtifactId為chapter,Version版本號(hào)為3.1-SNAPSHOT之后,單擊Finish按鈕完成主工程的創(chuàng)建,之后所有章節(jié)的代碼示例將在這個(gè)主工程上以模塊的方式進(jìn)行創(chuàng)建。接下來(lái),就可以創(chuàng)建本章示例的模塊chapter1,可以通過(guò)File→New Module→Add Module對(duì)話框來(lái)實(shí)現(xiàn),如圖1-6所示。
選擇Create module from scratch選項(xiàng),并單擊Next按鈕進(jìn)入模塊設(shè)置窗口,如圖1-7所示。
設(shè)置模塊的名稱(chēng)為chapter1,選擇類(lèi)型為Maven Module,其他保持默認(rèn)值即可,單擊Next按鈕,完成模塊創(chuàng)建。后面所有章節(jié)的示例代碼都將以Maven模塊的方式進(jìn)行創(chuàng)建,如第2章,模塊的名稱(chēng)為chapter2,其他章節(jié)依次類(lèi)推。創(chuàng)建之后工程模塊的目錄結(jié)構(gòu)如圖1-8所示。

圖1-4 創(chuàng)建工程

圖1-5 設(shè)置Maven工程

圖1-6 創(chuàng)建Maven Module

圖1-7 配置Module

圖1-8 Maven工程目錄結(jié)構(gòu)
創(chuàng)建項(xiàng)目工程后,修改主工程模塊的pom.xml文件,設(shè)置主工程模塊標(biāo)識(shí)、子模塊公共依賴(lài)庫(kù)
等信息(關(guān)于pom.xm詳細(xì)配置將在第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之后,就開(kāi)始配置在第1 章模塊chapter1 中生成的pom.xml文件中配置項(xiàng)目工程中需要的依賴(lài)相關(guān)類(lèi)庫(kù),如代碼清單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快速入門(mén)</description> <packaging>war</packaging> <dependencies> <!--依賴(lài)的Spring模塊類(lèi)庫(kù) --> <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> <!-- 依賴(lài)的持久化類(lèi)庫(kù)--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!--依賴(lài)的公共模塊類(lèi)庫(kù)--> <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> <!--依賴(lài)的Web模塊類(lèi)庫(kù)--> <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選項(xiàng)卡,彈出Maven項(xiàng)目的管理窗口。IDEA提供了非常友好的Maven項(xiàng)目管理界面,如圖1-9 所示,單擊管理窗口中的刷新按鈕,可以列出當(dāng)前所有的Maven模塊。每個(gè)模塊都包含兩個(gè)子節(jié)點(diǎn)Lifecycle及Dependencies,其中Lifecycle子節(jié)點(diǎn)下提供常用的Maven操作命令,如清理、驗(yàn)證、編譯、測(cè)試、打包、部署等,Dependencies子節(jié)點(diǎn)列出當(dāng)前模塊所有依賴(lài)的類(lèi)庫(kù)。

圖1-9 Maven項(xiàng)目的管理界面
1.3.3 類(lèi)包及Spring配置文件規(guī)劃
類(lèi)包規(guī)劃
類(lèi)包以分層的方式進(jìn)行組織,共劃分為4個(gè)包,分別是dao、domain、service和web。領(lǐng)域?qū)ο髲膰?yán)格意義上講屬于業(yè)務(wù)層,但由于領(lǐng)域?qū)ο罂赡苓€同時(shí)被持久層和展現(xiàn)層共享,所以一般將其單獨(dú)劃分到一個(gè)包中,如圖1-10所示。
單元測(cè)試的類(lèi)包和程序的類(lèi)包對(duì)應(yīng),但放置在不同的文件夾下,如圖1-11所示。本實(shí)例僅對(duì)業(yè)務(wù)層的業(yè)務(wù)類(lèi)進(jìn)行單元測(cè)試。將程序類(lèi)和測(cè)試類(lèi)放置在物理不同的文件夾下,方便將來(lái)程序的打包和分發(fā),因?yàn)闇y(cè)試類(lèi)僅在開(kāi)發(fā)時(shí)有用,無(wú)須包含到部署包中。在本章的后續(xù)內(nèi)容中,讀者將會(huì)看到如何用Maven工具進(jìn)行程序打包,體會(huì)這種包及目錄的設(shè)計(jì)結(jié)構(gòu)所帶來(lái)的好處。
實(shí)戰(zhàn)經(jīng)驗(yàn)
隨著項(xiàng)目規(guī)模的增大,這種僅以分層思想規(guī)劃的包結(jié)構(gòu)馬上就會(huì)顯示出它的不足,一般情況下需要在業(yè)務(wù)模塊包下,進(jìn)一步按分層模塊劃分子包,如user/dao、user/service、viewspace/dao、viewspace/service等。對(duì)于由若干獨(dú)立的子系統(tǒng)組成的大型應(yīng)用,在業(yè)務(wù)分層包的前面一般還需要加上子系統(tǒng)的前綴。包的規(guī)劃對(duì)于大型的應(yīng)用特別重要,它直接關(guān)系到應(yīng)用部署和分發(fā)的方便性。

圖1-10 主程序包的規(guī)劃

圖1-11 測(cè)試包的規(guī)劃
Spring配置文件規(guī)劃
Spring可以將所有的配置信息統(tǒng)一到一個(gè)文件中,也可以放置到多個(gè)文件中。對(duì)于簡(jiǎn)單的應(yīng)用來(lái)說(shuō),由于配置信息少,僅用一個(gè)配置文件就足以應(yīng)付。隨著應(yīng)用規(guī)模的擴(kuò)大,配置信息量的增多,僅僅使用一個(gè)配置文件往往難以滿足要求,如果不進(jìn)行仔細(xì)規(guī)劃,就會(huì)給配置信息的查看和團(tuán)隊(duì)協(xié)作帶來(lái)負(fù)面影響。
配置文件在團(tuán)隊(duì)協(xié)作時(shí)是資源爭(zhēng)用的焦點(diǎn),對(duì)于大型的應(yīng)用一般要按模塊進(jìn)行劃分,以在一定程度上降低爭(zhēng)用,減少團(tuán)隊(duì)協(xié)作的版本控制沖突。因此,應(yīng)用比較小時(shí),直接采用一個(gè)applicationContext.xml配置文件即可。
1.4 持久層
持久層負(fù)責(zé)數(shù)據(jù)的訪問(wèn)和操作,DAO類(lèi)被上層的業(yè)務(wù)類(lèi)調(diào)用。Spring本身支持多種流行的ORM框架。這里使用Spring JDBC作為持久層的實(shí)現(xiàn)技術(shù),關(guān)于Spring JDBC的詳細(xì)內(nèi)容,請(qǐng)參見(jiàn)第4章的內(nèi)容。為方便閱讀,會(huì)對(duì)本章涉及的相關(guān)知識(shí)點(diǎn)進(jìn)行必要的介紹,所以相信讀者在不了解Spring JDBC的情況下,也可以輕松閱讀以下的內(nèi)容。
1.4.1 建立領(lǐng)域?qū)ο?/h3>
領(lǐng)域?qū)ο?/span>(Domain Object)也稱(chēng)為實(shí)體類(lèi),它代表了業(yè)務(wù)的狀態(tài),一般來(lái)說(shuō),領(lǐng)域?qū)ο髮儆跇I(yè)務(wù)層,但它貫穿展現(xiàn)層、業(yè)務(wù)層和持久層,并最終被持久化到數(shù)據(jù)庫(kù)中。領(lǐng)域?qū)ο笫箶?shù)據(jù)庫(kù)表操作以面向?qū)ο蟮姆绞竭M(jìn)行,為程序的擴(kuò)展帶來(lái)了更大的靈活性。領(lǐng)域?qū)ο蟛灰欢ǖ韧跀?shù)據(jù)庫(kù)表,不過(guò)對(duì)于簡(jiǎn)單的應(yīng)用來(lái)說(shuō),領(lǐng)域?qū)ο笸紦碛袑?duì)應(yīng)的數(shù)據(jù)庫(kù)表。
持久層的主要工作就是從數(shù)據(jù)庫(kù)表中加載數(shù)據(jù)并實(shí)例化領(lǐng)域?qū)ο螅驅(qū)㈩I(lǐng)域?qū)ο蟪志没綌?shù)據(jù)表中。由于持久層需要用到領(lǐng)域?qū)ο螅詫⒈緦儆跇I(yè)務(wù)層的領(lǐng)域?qū)ο筇崆暗匠志脤觼?lái)說(shuō)明。
景區(qū)網(wǎng)站登錄模塊需要涉及兩個(gè)領(lǐng)域?qū)ο螅篣ser和LoginLog,前者代表用戶(hù)信息,后者代表日志信息,分別對(duì)應(yīng)t_user和t_login_log數(shù)據(jù)表,領(lǐng)域?qū)ο箢?lèi)的包為com.smart.domain。
用戶(hù)領(lǐng)域?qū)ο?/p>
用戶(hù)信息領(lǐng)域?qū)ο蠛芎?jiǎn)單,可以看成是對(duì)t_user表的對(duì)象翻譯,每個(gè)字段對(duì)應(yīng)一個(gè)對(duì)象屬性。User類(lèi)主要有兩類(lèi)信息,分別為用戶(hù)名/密碼(userName/password)以及最后一次登錄的信息(lastIp、lastVisit),其代碼如代碼清單1-3所示。
代碼清單1-3 User.java領(lǐng)域?qū)ο?/p>

登錄日志領(lǐng)域?qū)ο?/p>
用戶(hù)每次登錄成功后,記錄一條登錄日志,該登錄日志包括3個(gè)信息,分別是用戶(hù)ID、登錄IP地址和登錄時(shí)間。一般情況下,還包括退出時(shí)間。為了簡(jiǎn)化實(shí)例,僅記錄登錄時(shí)間,登錄日志的領(lǐng)域?qū)ο笕绱a清單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
首先定義訪問(wèn)User的DAO,它包括以下3個(gè)方法。
· getMatchCount():根據(jù)用戶(hù)名和密碼獲取匹配的用戶(hù)數(shù)。等于1表示用戶(hù)名/密碼正確,等于0 表示用戶(hù)名或密碼錯(cuò)誤(這是最簡(jiǎn)單的用戶(hù)身份認(rèn)證方法,在實(shí)際應(yīng)用中需要采用諸如密碼加密等安全策略)。
· findUserByUserName():根據(jù)用戶(hù)名獲取User對(duì)象。
· updateLoginInfo():更新用戶(hù)積分、最后登錄IP地址以及最后登錄時(shí)間。
下面通過(guò)Spring JDBC技術(shù)實(shí)現(xiàn)這個(gè)DAO類(lèi),如代碼清單1-5所示。
代碼清單1-5 UserDao

在Spring 1.5以后,可以調(diào)用注解的方式定義Bean,較之于XML配置方式,注解配置方式的簡(jiǎn)單性非常明顯,已經(jīng)被廣泛接受,成為一種趨勢(shì)。所以除非沒(méi)有辦法,否則都應(yīng)盡量采用注解的配置方式。
這里用@Repository定義了一個(gè)DAO Bean,使用@Autowired將Spring容器中的Bean注入進(jìn)來(lái)。關(guān)于Spring的注解配置,將會(huì)在第2章詳細(xì)討論。
傳統(tǒng)的JDBC API太底層,即使用戶(hù)執(zhí)行一條最簡(jiǎn)單的數(shù)據(jù)查詢(xún)操作,都必須執(zhí)行如下的過(guò)程:獲取連接→創(chuàng)建Statement→執(zhí)行數(shù)據(jù)操作→獲取結(jié)果→關(guān)閉Statement→關(guān)閉結(jié)果集→關(guān)閉連接,除此之外還需要進(jìn)行異常處理的操作。如果使用傳統(tǒng)JDBC API進(jìn)行數(shù)據(jù)訪問(wèn)操作,可能會(huì)有1/3以上單調(diào)乏味的重復(fù)性代碼像蒼蠅一樣驅(qū)之不散。
Spring JDBC對(duì)傳統(tǒng)的JDBC API進(jìn)行了薄層的封裝,將樣板式的代碼和那些必不可少的代碼進(jìn)行了分離,用戶(hù)僅需要編寫(xiě)那些必不可少代碼,剩余的單調(diào)乏味的重復(fù)性工作則交由Spring JDBC框架處理。簡(jiǎn)單來(lái)說(shuō),Spring JDBC通過(guò)一個(gè)模板類(lèi)org.springframework. jdbc.core.JdbcTemplate封裝了樣板式的代碼,用戶(hù)通過(guò)模板類(lèi)就可以輕松地完成大部分?jǐn)?shù)據(jù)訪問(wèn)的操作。
例如,對(duì)于getMatchCount()方法,僅提供了一個(gè)查詢(xún)SQL語(yǔ)句,直接調(diào)用模板的queryForInt()方法就可獲取查詢(xún),用戶(hù)不用擔(dān)心獲取連接、關(guān)閉連接、異常處理等繁瑣的事情。
通過(guò)JdbcTemplate的支持,可以輕松地實(shí)現(xiàn)UserDao的另外兩個(gè)接口,如代碼清單1-6所示。
代碼清單1-6 UserDao另外兩個(gè)方法

findUserByUserName()方法稍微有點(diǎn)復(fù)雜。這里使用到了JdbcTemplate#query()方法,該方法的簽名為query(String sql,Object[] args, RowCallbackHandler rch),它有3個(gè)入?yún)ⅰ?/p>
· sqlStr:查詢(xún)的SQL語(yǔ)句,允許使用帶“?”的參數(shù)占位符。
· args:SQL語(yǔ)句中占位符對(duì)應(yīng)的參數(shù)數(shù)組。
· RowCallbackHandler:查詢(xún)結(jié)果的處理回調(diào)接口,該回調(diào)接口有一個(gè)方法processRow (ResultSet rs),負(fù)責(zé)將查詢(xún)的結(jié)果從ResultSet裝載到類(lèi)似于領(lǐng)域?qū)ο蟮膶?duì)象實(shí)例中。
在?處,findUserByUserName()通過(guò)匿名內(nèi)部類(lèi)的方式定義了一個(gè)RowCallbackHandler回調(diào)接口實(shí)例,將ResultSet轉(zhuǎn)換為User對(duì)象。
updateLoginInfo()方法比較簡(jiǎn)單,主要通過(guò)JdbcTemplate#update(String sql,Object[])進(jìn)行數(shù)據(jù)的更新操作。
實(shí)戰(zhàn)經(jīng)驗(yàn)
在編寫(xiě)SQL語(yǔ)句時(shí),由于SQL語(yǔ)句比較長(zhǎng),一般會(huì)采用多行字符串的方式進(jìn)行構(gòu)造,如代碼清單1-6的?處所示。在編寫(xiě)多行SQL語(yǔ)句時(shí),由于上下行最終會(huì)組成一行完整的SQL語(yǔ)句,這種拼接方式很容易產(chǎn)生錯(cuò)誤的SQL組合語(yǔ)句:假設(shè)在?處第一行的user_name后不加空格,第二行的FROM之前也無(wú)空格,組合的SQL將為“... user_nameFROM ...”,由于FROM保留字和user_name連在一起,就產(chǎn)生了非法的SQL語(yǔ)句。以下是一個(gè)值得推薦的編程習(xí)慣:在每一行SQL語(yǔ)句的句前和句尾都加一個(gè)空格,這樣就可以避免分行SQL語(yǔ)句組合后的錯(cuò)誤。
1.4.3 LoginLogDao
LoginLogDao負(fù)責(zé)記錄用戶(hù)的登錄日志,它僅有一個(gè)insertLoginLog()接口方法,與UserDao相似,其實(shí)現(xiàn)類(lèi)也通過(guò)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
在編寫(xiě)DAO接口的實(shí)現(xiàn)類(lèi)時(shí),大家也許有一個(gè)問(wèn)題:在以上兩個(gè)DAO實(shí)現(xiàn)類(lèi)中都沒(méi)有打開(kāi)/釋放Connection的代碼,DAO類(lèi)究竟如何訪問(wèn)數(shù)據(jù)庫(kù)呢?前面說(shuō)過(guò),樣板式的操作都被JdbcTemplate封裝起來(lái)了,JdbcTemplate本身需要一個(gè)DataSource,這樣它就可以根據(jù)需要從DataSource中獲取或返回連接。UserDao和LoginLog都提供了一個(gè)帶@Autowired注解的JdbcTemplate變量。所以必須事先聲明一個(gè)數(shù)據(jù)源,然后定義一個(gè)JdbcTemplate Bean,通過(guò)Spring的容器上下文自動(dòng)綁定機(jī)制進(jìn)行Bean的注入。
在項(xiàng)目工程的src\main\resources目錄下創(chuàng)建一個(gè)名為applicationContext.xml的Spring配置文件,配置文件的基本結(jié)構(gòu)如下所示。
<?xml version="1.0" encoding="UTF-8" ?> <!-- 引用Spring的多個(gè)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中,刷新工程目錄樹(shù),在src\main\resources文件夾下即可看到該配置文件。雙擊applicationContext.xml文件,添加如代碼清單1-8所示的配置信息。
代碼清單1-8 DAO Bean的配置
… < beans …> <!-- ?掃描類(lèi)包,將標(biāo)注Spring注解的類(lèi)自動(dòng)轉(zhuǎn)化Bean,同時(shí)完成Bean的注入--> <context:component-scan base-package="com.smart.dao"/> <!--?定義一個(gè)使用DBCP實(shí)現(xiàn)的數(shù)據(jù)源--> <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>掃描指定類(lèi)包下的所有類(lèi),這樣在類(lèi)中定義的Spring注解(如@Repository、@Autowired等)才能產(chǎn)生作用。
在?處,我們使用Jakarta的DBCP開(kāi)源數(shù)據(jù)源實(shí)現(xiàn)方案定義了一個(gè)數(shù)據(jù)源,數(shù)據(jù)庫(kù)驅(qū)動(dòng)器類(lèi)為com.mysql.jdbc.Driver,由于我們?cè)O(shè)置的MySQL數(shù)據(jù)庫(kù)的服務(wù)端口為3309,而非默認(rèn)的3306,所以數(shù)據(jù)庫(kù)URL中顯式指定了3309端口的信息。
在?處配置了JdbcTemplate Bean,將?處聲明的dataSource注入JdbcTemplate中,而這個(gè)JdbcTemplate Bean將通過(guò)@Autowired自動(dòng)注入LoginLog和UserDao的Bean中,可見(jiàn)Spring可以很好地將注解配置和XML配置統(tǒng)一起來(lái)。
這樣,我們就完成了登錄模塊持久層所有的開(kāi)發(fā)工作,接下來(lái)將著手業(yè)務(wù)層的開(kāi)發(fā)和配置工作,我們將對(duì)業(yè)務(wù)層的業(yè)務(wù)類(lèi)方法進(jìn)行單元測(cè)試,到時(shí)就可以看到DAO的實(shí)際運(yùn)行效果了,現(xiàn)在暫時(shí)把這兩個(gè)DAO放在一邊。
1.5 業(yè)務(wù)層
在景區(qū)網(wǎng)站登錄實(shí)例中,業(yè)務(wù)層僅有一個(gè)業(yè)務(wù)類(lèi),即UserService。UserService負(fù)責(zé)將持久層的UserDao和LoginLoginDao組織起來(lái)完成用戶(hù)/密碼認(rèn)證、登錄日志記錄等操作。
1.5.1 UserService
UserService業(yè)務(wù)接口有3個(gè)業(yè)務(wù)方法,其中hasMatchUser()用于檢查用戶(hù)名/密碼的正確性;findUserByUserName()以用戶(hù)名為條件加載User對(duì)象;loginSuccess()方法在用戶(hù)登錄成功后調(diào)用,更新用戶(hù)最后登錄時(shí)間和IP信息同時(shí)記錄用戶(hù)登錄日志。
下面,我們來(lái)實(shí)現(xiàn)這個(gè)業(yè)務(wù)類(lèi),UserService的實(shí)現(xiàn)類(lèi)需要調(diào)用DAO層的兩個(gè)DAO完成業(yè)務(wù)邏輯操作,如代碼清單1-9所示。
代碼清單1-9 UserService


首先,我們?cè)?處通過(guò)@Service注解,將UserService標(biāo)注為一個(gè)服務(wù)層的Bean,然后在?和?處注入userDao和loginLogDao這兩個(gè)DAO層的Bean。hasMatchUser()和findUserByUserName()業(yè)務(wù)方法簡(jiǎn)單地調(diào)用DAO來(lái)完成對(duì)應(yīng)的功能;loginSuccess()方法根據(jù)入?yún)ser對(duì)象構(gòu)造出LoginLog對(duì)象,并調(diào)用loginLogDao向t_login_log表中添加一條記錄。
實(shí)戰(zhàn)經(jīng)驗(yàn)
在實(shí)際應(yīng)用中,一般不會(huì)直接在數(shù)據(jù)庫(kù)中以明文的方式保存用戶(hù)的密碼,因?yàn)檫@樣很容易造成密碼泄密的問(wèn)題。所以需要將密碼加密后以密文的方式進(jìn)行保存;另外一種更有效的辦法是僅保存密碼的MD5摘要,由于相等的兩字符串摘要值也相等,在登錄驗(yàn)證時(shí),通過(guò)比較摘要的方式就可以判斷用戶(hù)所輸入的密碼是否正確。由于不能通過(guò)密碼摘要反推出原來(lái)的密碼,即使內(nèi)部人員可以查看用戶(hù)信息表也無(wú)法知道用戶(hù)的密碼。所以,摘要存儲(chǔ)方式已經(jīng)成為大部分系統(tǒng)密碼存儲(chǔ)的通用方式。此外,為了防止黑客通過(guò)工具進(jìn)行密碼的暴力破解,目前大多數(shù)Web應(yīng)用都使用了圖片驗(yàn)證碼功能,驗(yàn)證碼具有一次性消費(fèi)的特征,每次登錄都不相同,這樣工具暴力破解就無(wú)用武之地了。
loginSuccess()將兩個(gè)DAO組織起來(lái)共同完成一個(gè)事務(wù)性的數(shù)據(jù)操作:更新t_user表記錄并添加t_login_log表記錄。但從UserService中卻看不出任何事務(wù)操作的影子,這正是Spring的高明之處,它讓用戶(hù)從事務(wù)操作單調(diào)機(jī)械的代碼中解脫出來(lái),專(zhuān)注完成那些不可或缺的業(yè)務(wù)工作。通過(guò)Spring聲明式事務(wù)配置,即可讓業(yè)務(wù)類(lèi)享受EJB聲明式事務(wù)的好處。下一節(jié)將了解如何賦予業(yè)務(wù)類(lèi)事務(wù)管理的能力。
1.5.2 在Spring中裝配Service
事務(wù)管理的代碼雖然不用出現(xiàn)在程序代碼中,但必須以某種方式告訴Spring哪些業(yè)務(wù)類(lèi)需要工作于事務(wù)環(huán)境下以及事務(wù)的規(guī)則等內(nèi)容,以便Spring根據(jù)這些信息自動(dòng)為目標(biāo)業(yè)務(wù)類(lèi)添加事務(wù)管理的功能。
打開(kāi)原來(lái)的applicationContext.xml文件,更改代碼如代碼清單1-10所示。
代碼清單1-10 applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?> <!--? 引入aop及tx命名空間所對(duì)應(yīng)的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類(lèi)包,應(yīng)用Spring的注解配置--> <context:component-scan base-package="com.smart.service"/> … <!--? 配置事務(wù)管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" /> <!--? 通過(guò)AOP配置提供事務(wù)增強(qiáng),讓service包下所有Bean的所有方法擁有事務(wù)--> <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定義文件的說(shuō)明,這樣在配置文件中就可以使用這兩個(gè)空間中的配置標(biāo)簽了。
在?處將com.smart.service添加到上下文掃描路徑中,以便使service包中類(lèi)的Spring注解生效。
在?處定義了一個(gè)基于數(shù)據(jù)源的DataSourceTransactionManager事務(wù)管理器,該事務(wù)管理器負(fù)責(zé)聲明式事務(wù)的管理。該管理器需要引用dataSource Bean。
在?處通過(guò)AOP及tx命名空間的語(yǔ)法,以AOP的方式為com.smart.service包下所有類(lèi)的所有方法都添加了事務(wù)增強(qiáng),即它們都將工作于事務(wù)環(huán)境中。關(guān)于Spring事務(wù)的配置,詳見(jiàn)本書(shū)的第6章。
這樣,就完成了業(yè)務(wù)層的程序開(kāi)發(fā)和配置工作,接下來(lái),需要對(duì)該業(yè)務(wù)類(lèi)進(jìn)行簡(jiǎn)單的單元測(cè)試,以便檢驗(yàn)業(yè)務(wù)方法的正確性。
1.5.3 單元測(cè)試
TestNG是一種基于注釋的新一代單元測(cè)試框架,通過(guò)添加靈活的裝置、測(cè)試分類(lèi)、參數(shù)測(cè)試和依賴(lài)測(cè)試等特性來(lái)克服JUnit的不足之處。因此,本書(shū)的所有示例代碼將采用TestNG 6.3.1作為測(cè)試基礎(chǔ)框架。首先在pom.xml文件中配置TestNG、Spring-test兩個(gè)類(lèi)庫(kù)依賴(lài),如代碼清單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> ... <!-- 依賴(lài)的測(cè)試類(lèi)庫(kù)--> <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>
選中單元測(cè)試所在的包文件夾service并單擊鼠標(biāo)右鍵,執(zhí)行New→Java Class命令創(chuàng)建UserService的單元測(cè)試類(lèi),單擊OK按鈕,創(chuàng)建UserServiceTest的單元用例,并編寫(xiě)如代碼清單1-12所示的測(cè)試代碼。
代碼清單1-12 UserServiceTest


首先,Spring 3.1的測(cè)試框架可以和TestNG整合,通過(guò)Spring提供的測(cè)試基類(lèi)AbstractTestNGSpringContextTests,可以將Spring容器和TestNG測(cè)試框架整合。@ContextConfiguration也是Spring提供的注解,它用于指定Spring的配置文件。
在測(cè)試類(lèi)中可以使用Spring的@Autowired將Spring容器中的Bean注入測(cè)試類(lèi)中。在測(cè)試方法前通過(guò)TestNG的@Test注解即可將方法標(biāo)注為測(cè)試方法。
在IDEA中,選中UserServiceTest測(cè)試用例,單擊鼠標(biāo)右鍵,選擇Run“UserServiceTest”菜單項(xiàng),運(yùn)行該測(cè)試用例以檢驗(yàn)業(yè)務(wù)類(lèi)方法的正確性。
從單元測(cè)試的運(yùn)行結(jié)果(見(jiàn)圖1-12)可以看到3個(gè)業(yè)務(wù)方法已經(jīng)成功執(zhí)行,在后臺(tái)數(shù)據(jù)庫(kù)中,用戶(hù)將發(fā)現(xiàn)已經(jīng)有一條新的登錄日志添加到t_login_log表中。關(guān)于Spring應(yīng)用測(cè)試的內(nèi)容,可以參見(jiàn)本書(shū)第8章的內(nèi)容。

圖1-12 TestUserService的運(yùn)行結(jié)果
1.6 展現(xiàn)層
業(yè)務(wù)層和持久層的開(kāi)發(fā)任務(wù)已經(jīng)完成,該是為程序提供界面的時(shí)候了。Struts MVC框架由于搶盡天時(shí)地利,成為當(dāng)下最流行的展現(xiàn)層框架。但也有很多人認(rèn)為Spring MVC相比較于Struts更簡(jiǎn)單、更強(qiáng)大、更優(yōu)雅。此外,由于Spring MVC出自于Spring之手,因此和Spring容器沒(méi)有任何不兼容性,顯得天衣無(wú)縫。
Spring 1.5新增了基于注解的MVC,而且Spring 3.1還提供了REST風(fēng)格的MVC,Spring MVC已經(jīng)變得輕便、強(qiáng)大、易用。我們將會(huì)在本書(shū)的第8章中學(xué)習(xí)Spring MVC的詳細(xì)內(nèi)容。
1.6.1 配置Spring MVC框架
首先需要對(duì)web.xml文件進(jìn)行配置,以便Web容器啟動(dòng)時(shí)能夠自動(dòng)啟動(dòng)Spring容器,如代碼清單1-13所示。
代碼清單1-13自動(dòng)啟動(dòng)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"> <!--?從類(lèi)路徑下加載Spring配置文件,classpath關(guān)鍵字特指在類(lèi)路徑下加載--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml </param-value> </context-param> <!--?負(fù)責(zé)啟動(dòng)Spring容器的監(jiān)聽(tīng)器,它將引用?處的上下文參數(shù)獲得Spring配置文件地址--> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> … </web-app>
首先,通過(guò)Web容器上下文參數(shù)指定Spring配置文件的地址,如?所示。多個(gè)配置文件可用逗號(hào)或空格分隔,建議采用逗號(hào)分隔的方式。在?處指定Spring所提供的ContextLoaderListener的Web容器監(jiān)聽(tīng)器,該監(jiān)聽(tīng)器在Web容器啟動(dòng)時(shí)自動(dòng)運(yùn)行,它會(huì)根據(jù)contextConfigLocation Web容器參數(shù)獲取Spring配置文件,并啟動(dòng)Spring容器。注意需要將log4J.propertis日志配置文件放置在類(lèi)路徑下,以便日志引擎自動(dòng)生效。
接下來(lái),需要配置Spring MVC相關(guān)的信息,Spring MVC像Struts一樣,也通過(guò)一個(gè)Servlet截獲URL請(qǐng)求,然后再進(jìn)行相關(guān)的處理,如代碼清單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>
在?處聲明了一個(gè)Servlet,Spring MVC也擁有一個(gè)Spring配置文件(稍后會(huì)涉及),該配置文件的文件名和此處定義的Servlet名有一個(gè)契約:即采用<Servlet名>-servlet.xml的形式。在這里,因?yàn)镾ervlet名為viewspace,所以在/WEB-INF目錄下必須提供一個(gè)viewspace-servlet.xml的Spring MVC配置文件,但這個(gè)配置文件無(wú)須通過(guò)web.xml的contextConfigLocation上下文參數(shù)進(jìn)行聲明,因?yàn)镾pring MVC的Servlet會(huì)自動(dòng)將viewspace -servlet.xml和Spring其他的配置文件進(jìn)行拼裝。
在?處對(duì)這個(gè)Servlet的URL路徑映射進(jìn)行定義,在這里讓所有以.html為后綴的URL都能被viewspace Servlet截獲,進(jìn)而轉(zhuǎn)由Spring MVC框架進(jìn)行處理。我們知道,在Struts框架中,一般將URL后綴配置為*.do,在Webwork中一般配置為*.action,其實(shí),框架本身和URL模式?jīng)]有任何關(guān)系,用戶(hù)大可使用喜歡的任何后綴。使用.html后綴,一方面,用戶(hù)不能通過(guò)URL直接知道開(kāi)發(fā)者采用了何種服務(wù)端技術(shù);另一方面,.html是靜態(tài)網(wǎng)頁(yè)的后綴,可以騙過(guò)搜索引擎,增加被收錄的概率,所以推薦采用這種后綴。對(duì)于那些真正的無(wú)須任何動(dòng)態(tài)處理的靜態(tài)網(wǎng)頁(yè),則可以使用.htm后綴加以區(qū)分,以避免被框架截獲。
請(qǐng)求被Spring MVC截獲后,首先根據(jù)請(qǐng)求的URL查找到目標(biāo)的處理控制器,并將請(qǐng)求參數(shù)封裝成一個(gè)“命令”對(duì)象一起傳給控制器處理,控制器調(diào)用Spring容器中的業(yè)務(wù)Bean完成業(yè)務(wù)處理工作并返回結(jié)果視圖。
1.6.2 處理登錄請(qǐng)求
POJO控制器類(lèi)
首先要編寫(xiě)的是LoginController,它負(fù)責(zé)處理登錄請(qǐng)求,完成登錄業(yè)務(wù),并根據(jù)登錄成功與否轉(zhuǎn)向歡迎頁(yè)面或失敗頁(yè)面,如代碼清單1-15所示。
代碼清單1-15 LoginController.java


在?處通過(guò)Spring MVC的@Controller注解可以將任何一個(gè)POJO的類(lèi)標(biāo)注為Spring MVC的控制器,處理HTTP的請(qǐng)求。當(dāng)然標(biāo)注了@Controller的類(lèi)首先會(huì)是一個(gè)Bean,所以可以使用@Autowired進(jìn)行Bean的注入。
一個(gè)控制器可以擁有多個(gè)對(duì)應(yīng)不同HTTP請(qǐng)求路徑的處理方法,通過(guò)@RequestMapping指定方法如何映射請(qǐng)求路徑,如?和?所示。
請(qǐng)求的參數(shù)會(huì)根據(jù)參數(shù)名稱(chēng)默認(rèn)契約自動(dòng)綁定到響應(yīng)方法的入?yún)⒅校?處的loginCheck(HttpServletRequest request,LoginCommand loginCommand)方法中,請(qǐng)求參數(shù)會(huì)按名稱(chēng)匹配綁定到loginCommand的入?yún)⒅小?/p>
請(qǐng)求響應(yīng)方法可以返回一個(gè)ModelAndView,或直接返回一個(gè)字符串,Spring MVC會(huì)解析之并轉(zhuǎn)向目標(biāo)響應(yīng)頁(yè)面。
ModelAndView對(duì)象既包括了視圖信息又包括了視圖渲染所需的模型數(shù)據(jù)信息,在這里用戶(hù)僅需要了解它代表一個(gè)視圖就可以了,在后面的內(nèi)容中,讀者將了解到Spring MVC如何根據(jù)這個(gè)對(duì)象轉(zhuǎn)向真正的頁(yè)面。
前面使用到的LoginCommand對(duì)象是一個(gè)POJO,它沒(méi)有繼承于特定的父類(lèi)或?qū)崿F(xiàn)特定的接口。LoginCommand類(lèi)僅包括用戶(hù)/密碼這兩個(gè)屬性(和請(qǐng)求的用戶(hù)/密碼參數(shù)名稱(chēng)一樣),如代碼清單1-16所示。
代碼清單1-16 LoginCommand
package com.smart.web; public class LoginCommand { private String userName; private String password; //省略get/setter方法 }
Spring MVC配置文件
編寫(xiě)好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包,應(yīng)用Spring的注解--> <context:component-scan base-package="com.smart.web"/> <!--? 配置視圖解析器,將ModelAndView及字符串解析為具體的頁(yè)面--> <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 的?處,控制器根據(jù)登錄處理結(jié)果分別返回ModelAndView ("login","error", "用戶(hù)名或密碼錯(cuò)誤。")和ModelAndView("main")。ModelAndView的第一個(gè)參數(shù)代表視圖的邏輯名,第二個(gè)和第三個(gè)參數(shù)分別為數(shù)據(jù)模型名稱(chēng)和數(shù)據(jù)模型對(duì)象,數(shù)據(jù)模型對(duì)象將以數(shù)據(jù)模型名稱(chēng)為參數(shù)名放置到request的屬性中。
Spring MVC如何將視圖邏輯名解析為具體的視圖頁(yè)面呢?解決的思路也和上面的方法類(lèi)似,需要在viewspace-servlet.xml中提供一個(gè)定義解析規(guī)則的Bean,如代碼清單1-18所示。
代碼清單1-18 viewspace-servlet.xml視圖解析規(guī)則
… <!--通過(guò)prefix指定在視圖名前所添加的前綴,通過(guò)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,它通過(guò)為視圖邏輯名添加前后綴的方式進(jìn)行解析。如視圖邏輯名“l(fā)ogin”將解析為/WEB-INF/jsp/login.jsp;視圖邏輯名“main”將解析為/WEB-INF/jsp/main.jsp。
1.6.3 JSP視圖頁(yè)面
景區(qū)網(wǎng)站登錄模塊共包括兩個(gè)JSP頁(yè)面,分別是登錄頁(yè)面login.jsp和管理主頁(yè)面main.jsp,下面將完成這兩個(gè)頁(yè)面的開(kāi)發(fā)工作。
登錄頁(yè)面login.jsp
登錄頁(yè)面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>景區(qū)網(wǎng)站登錄</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">? 用戶(hù)名: <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頁(yè)面既作為登錄頁(yè)面又作為登錄失敗后的響應(yīng)頁(yè)面。因此在?處使用JSTL標(biāo)簽對(duì)登錄錯(cuò)誤返回的信息進(jìn)行處理。JSTL標(biāo)簽中引用了error變量,這個(gè)變量正是LoginController中返回的ModelAndView("login", "error", "用戶(hù)名或密碼錯(cuò)誤。") 對(duì)象所聲明的error參數(shù)。
login.jsp的登錄表單提交到/loginController.html,如?所示。<c:url value= "/loginController.html"/>的JSTL標(biāo)簽會(huì)在URL前自動(dòng)加上應(yīng)用程序部署根目錄,假設(shè)應(yīng)用部署在網(wǎng)站的viewspace目錄下,<c:url/>標(biāo)簽將輸出/viewspace/loginController.html。通過(guò)<c:url/>標(biāo)簽很好地解決了開(kāi)發(fā)和應(yīng)用部署目錄不一致的問(wèn)題。
由于login.jsp放置在WEB-INF/jsp目錄下,無(wú)法直接通過(guò)URL進(jìn)行調(diào)用,它由LoginController控制類(lèi)中標(biāo)注了@RequestMapping(value = "/login.html")的loginPage()進(jìn)行轉(zhuǎn)發(fā),見(jiàn)代碼清單1-15。
景區(qū)管理主頁(yè)面main.jsp
登錄成功的歡迎頁(yè)面很簡(jiǎn)單,僅使用JSTL標(biāo)簽顯示一條歡迎信息即可,如代碼清單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>景區(qū)后臺(tái)管理主頁(yè)面</title> </head> <body> ${user.userName},歡迎您進(jìn)入景區(qū)后臺(tái)管理!? </body> </html>
?處訪問(wèn)Session域中的user對(duì)象,顯示用戶(hù)名和積分信息。這樣,就完成了實(shí)例所有的開(kāi)發(fā)任務(wù)。
1.7 運(yùn)行Web應(yīng)用
在IDEA中運(yùn)行Web應(yīng)用前,首先需要配置好Web應(yīng)用服務(wù)器,這里使用Maven的Web容器插件運(yùn)行應(yīng)用。打開(kāi)當(dāng)前模塊pom.xml文件,添加構(gòu)建插件配置信息,如代碼清單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選項(xiàng)卡,進(jìn)入當(dāng)前模塊Maven管理界面,如圖1-13所示。然后雙擊jetty:run-expolded就可以啟動(dòng)jetty容器。

圖1-13 Jetty運(yùn)行目錄
啟動(dòng)Jetty后,在瀏覽器中輸入http://localhost:8080/chapter1/admin/login.html進(jìn)入景區(qū)網(wǎng)站管理員登錄頁(yè)面,如圖1-14所示。

圖1-14 在瀏覽器中訪問(wèn)應(yīng)用
如果輸入錯(cuò)誤的用戶(hù)名/密碼,登錄模塊將給出錯(cuò)誤提示。這里,輸入admin/123456,單擊“登錄”按鈕后,就可以登錄到后臺(tái)管理頁(yè)面中,如圖1-15所示。

圖1-15 登錄成功后的歡迎頁(yè)面
1.8 小結(jié)
本章概述了Spring的發(fā)展歷程,并用Spring MVC、Spring JDBC以及Spring的聲明式事務(wù)等技術(shù)實(shí)現(xiàn)了一個(gè)常見(jiàn)的景區(qū)網(wǎng)站登錄模塊,讓大家對(duì)如何使用Spring框架構(gòu)建Web應(yīng)用擁有了切身的體驗(yàn),同時(shí)還了解了開(kāi)發(fā)一個(gè)簡(jiǎn)單的Web應(yīng)用所需要經(jīng)歷的開(kāi)發(fā)過(guò)程。
也許用戶(hù)會(huì)抱怨該實(shí)例功能的簡(jiǎn)單性和開(kāi)發(fā)過(guò)程的復(fù)雜性有點(diǎn)不成正比。但對(duì)于一個(gè)具有擴(kuò)展性、靈活性的Web應(yīng)用來(lái)說(shuō),這些步驟往往都是必需的,其實(shí)我們?cè)谕瓿蓪?shí)例開(kāi)發(fā)的同時(shí)也完成了Web框架的搭建,為新功能模塊的添加夯實(shí)地基,后繼的模塊開(kāi)發(fā)僅需要在此基礎(chǔ)上進(jìn)行添磚加瓦的工作,當(dāng)新功能加入時(shí),讀者就會(huì)發(fā)現(xiàn)在這里的投入是值得的。
- OpenNI體感應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)
- App草圖+流程圖+交互原型設(shè)計(jì)教程
- 軟件需求分析實(shí)戰(zhàn)
- 深度學(xué)習(xí):21天實(shí)戰(zhàn)Caffe
- BERT基礎(chǔ)教程:Transformer大模型實(shí)戰(zhàn)
- Unity手機(jī)游戲開(kāi)發(fā):從搭建到發(fā)布上線全流程實(shí)戰(zhàn)
- 區(qū)塊鏈核心算法解析
- TensorFlow+Android經(jīng)典模型從理論到實(shí)戰(zhàn)(微課視頻版)
- Android驅(qū)動(dòng)開(kāi)發(fā)與移植實(shí)戰(zhàn)詳解
- 自然語(yǔ)言理解與行業(yè)知識(shí)圖譜:概念、方法與工程落地
- 微信小程序?qū)崙?zhàn)入門(mén)(內(nèi)含完整實(shí)例解析)
- 基于EEG的腦源定位與腦功能網(wǎng)絡(luò)
- 大數(shù)據(jù)測(cè)試技術(shù)與實(shí)踐
- Unity游戲案例開(kāi)發(fā)大全
- 看透Spring MVC:源代碼分析與實(shí)踐