昨天聯絡了一下學長,學長說這個專案因為種種原因程式碼比較混亂,感覺最壞的打算是從頭開始寫。
大概詢問了一下學長和xianhua學姐的建議,又看了看網上的資料,這個專案開發的技術棧基本就是SpringBoot + vue + D3,SpringBoot做後端的東西,vue寫個前端的東西,D3用來做知識圖譜那個圖比較好。
資料庫的話應該要用Neo4j,應該還要加一個關聯式資料庫。
先花幾天時間突擊一下Spring,知乎上推薦這個書的比較多,原始碼先跑起來看看。廢話不多說,從第二章開始看。
第二章主要要求做一個Spring的登入模組,首先要建立相應的資料庫和表相關。
drop database if exists sampledb; create database sampledb default character set utf8; use sampledb; #建立使用者表 create table t_user ( user_id int auto_increment primary key, user_name varchar(30), credits int, password varchar(32), last_visit datetime, last_ip varchar(23) )engine = InnoDB; #建立使用者登入日誌表 create table t_login_log ( login_log_id int auto_increment primary key, user_id int, ip varchar(23), login_datatime datetime )engine=InnoDB; #插入初始化資料 insert into t_user (user_name, password) values ('admin', '123456'); commit;
為了編碼的統一,將IDEA設定成統一的utf8編碼。
之後就是配置maven和建立專案的詳細內容,由於這個部落格的主要目的不在於詳細記錄每一個步驟(具體步驟可以參考書籍中內容),所以只對我覺得關鍵的步驟和內容進行筆記。
類包以分層的方式進行組織,共劃分為4個包,分別是dao(持久層)、 domain(領域物件)、 service(業務層)和 web(展現層)。領域物件從嚴格意義上講屬於業務層,但由於領域物件可能同時被持久層和展現層共享,所以一般將其單獨劃分到一個包中
spring-context.xml是配置檔案。
2.3持久層
2.3.1 建立領域物件
持久層負責資料的訪問和操作,DAO類被上層的業務類呼叫。Spring 本身支援多種流行的ORM框架(第14章對此進行專門的講解),這裡使用Spring JDBC作為持久層的實現技術(關於Spring JDBC的詳細內容,請參見第13章)。為了方便閱讀,我們會對本章涉及的相關知識點進行必要的介紹,所以在不瞭解Spring JDBC的情況下,相信讀者也可以輕鬆閱讀本章的內容。
領域物件(Domain Object)也被稱為實體類,它代表了業務的狀態,且貫穿展現層、業務層和持久層,並最終被持久化到資料庫中。領域物件使資料庫表操作以物件導向的方式進行,為程式擴充套件帶來了更大的靈活性。領域物件不一定等同於資料庫表,不過對於簡單的應用來說,領域物件往往擁有對應的資料庫表。
持久層的主要工作就是從資料庫表中載入資料並例項化領域物件,或將領域物件持久化到資料庫表中。論壇登入模組需要涉及兩個領域物件: User 和 LoginLog,前者代表使用者資訊,後者代表日誌資訊,分別對應t_user和t_login_log 資料庫表,領域物件類的包為com.smart.domain。
2.3.2 UserDao
首先來定義訪問User的 DAO,它包括3個方法。
getMatchCount():根據使用者名稱和密碼獲取匹配的使用者數。等於1表示使用者名稱/密碼正確;等於0表示使用者名稱或密碼錯誤(這是最簡單的使用者身份認證方法,在實際應用中需要採用諸如密碼加密等安全策略)。
findUserByUserName():根據使用者名稱獲取User物件。
updateLoginInfo():更新使用者積分、最後登入IP及最後登入時間。下面通過Spring JDBC技術實現這個DAO類,如程式碼所示。
@Repository public class UserDao { private JdbcTemplate jdbcTemplate; private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM t_user " + " WHERE user_name =? and password=? "; public int getMatchCount(String userName, String password) { return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[]{userName, password}, Integer.class); } ... @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
這裡說明了兩個註釋的作用@Repository //通過Spring 註解定義一個DAO、@Autowired//自動注入 JdbcTemplate的 Bean .
傳統的JDBC API太底層,即使使用者執行一條最簡單的資料查詢操作,都必須執行如下過程:獲取連線→建立Statement→執行資料操作→獲取結果→關閉Statement→關閉結果集→關閉連線,除此之外還需要進行異常處理的操作。如果使用傳統的JDBC API進行資料訪問操作,則可能會產生1/3以上單調乏味的重複性程式碼。
Spring JDBC對傳統的JDBC API進行了薄層封裝,將樣板式的程式碼和那些必不可少的程式碼進行了分離,使用者僅需要編寫那些必不可少的程式碼,剩餘的那些單調乏味的重複性工作則交由Spring JDBC框架處理。簡單來說,Spring JDBC通過一個模板類org.springframework.jdbc.core.JdbcTemplate封裝了樣板式的程式碼,使用者通過模板類就可以輕鬆地完成大部分資料訪問操作。
如getMatchCount()方法,我們僅提供了一個查詢SQL語句,直接呼叫模板的queryForInt()方法就可以獲取查詢,使用者不用擔心獲取連線、關閉連線、異常處理等煩瑣的事務。
@Repository public class UserDao { private JdbcTemplate jdbcTemplate; private final static String UPDATE_LOGIN_INFO_SQL = " UPDATE t_user SET " + " last_visit=?,last_ip=?,credits=? WHERE user_id =?"; public User findUserByUserName(final String userName) { String sqlStr = " SELECT user_id,user_name,credits " + " FROM t_user WHERE user_name =? "; final User user = new User(); jdbcTemplate.query(sqlStr, new Object[] { userName }, new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { user.setUserId(rs.getInt("user_id")); user.setUserName(userName); user.setCredits(rs.getInt("credits")); } }); return user; } public void updateLoginInfo(User user) { jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(), user.getLastIp(),user.getCredits(),user.getUserId()}); } @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
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()方法比較簡單,主要通過JdbcTemplatc#update(String sql,Object[])進行資料的更新操作。
2.3.4在 Spring 中裝配DAO
在編寫DAO介面的實現類時,大家也許會有一個問題:在以上兩個DAO 實現類中都沒有開啟/釋放Connection的程式碼,DAO類究竟如何訪問資料庫呢﹖前面說過,樣板式的操作都被JdbcTemplate封裝起來了,JdbcTemplate本身需要一個DataSource,這樣它就可以根據需要從DataSource中獲取或返回連線。UserDao和 LoginLog 都提供了一個帶@Autowired註解的JdbcTemplate變數,所以我們必須事先宣告一個資料來源,然後定義一個JdbcTemplate Bean,通過Spring 的容器上下文自動繫結機制進行Bean的注入。
在專案工程的src\resources(在Maven工程中,資原始檔統一放置在resources資料夾中)目錄下建立一個名為smart-context.xml的Spring配置檔案,該配置檔案的基本結構如下:
<?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" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> ... </beans>
<?xml version="1.0" encoding="UTF-8" ?> <beans ...> <!-- 掃描類包,將標註Spring註解的類自動轉化Bean,同時完成Bean的注入 --> <context:component-scan base-package="com.smart.dao"/> <context:component-scan base-package="com.smart.service"/> <!-- 配置資料來源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/sampledb" p:username="root" p:password="123456" /> <!-- 配置Jdbc模板 --> <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。
配置了JdbcTemplate Bean,將宣告的dataSource注入JdbcTemplate 中,而這個JdbcTemplate Bean將通過@Autowired自動注入LoginLog和UserDao 的Bean中,可見Spring可以很好地將註解配置和XML配置統一起來。
這樣就完成了登入模組持久層所有的開發工作,接下來將著手業務層的開發和配置工作。我們將對業務層的業務類方法進行單元測試,到時就可以看到DAO的實際執行效果了,現在暫時把這兩個DAO放在一邊。
總結一下,訪問User的 DAO,它包括3個方法:getMatchCount(),findUserByUserName(),updateLoginInfo(),LoginLogDao負責記錄使用者的登入日誌,它僅有一個insertLoginLog()介面方法。
2.4業務層
在論壇登入例項中,業務層僅有一個業務類,即 UserService。UserService負責將持久層的UserDao和LoginLoginDao組織起來,完成使用者/密碼認證、登入日誌記錄等操作。
2.4.1 UserService
UserService 業務介面有3個業務方法,其中,hasMatchUser()方法用於檢查使用者名稱/密碼的正確性;findUserByUserName()方法以使用者名稱為條件載入User物件;loginSuccess(方法在使用者登入成功後呼叫,更新使用者最後登入時間和P資訊,同時記錄使用者登入日誌。
下面我們來實現這個業務類。UserService的實現類需要呼叫DAO層的兩個DAO完成業務邏輯操作,如程式碼所示。
@Service public class UserService { private UserDao userDao; private LoginLogDao loginLogDao; public boolean hasMatchUser(String userName, String password) { int matchCount =userDao.getMatchCount(userName, password); return matchCount > 0; } public User findUserByUserName(String userName) { return userDao.findUserByUserName(userName); } @Transactional public void loginSuccess(User user) { user.setCredits( 5 + user.getCredits()); LoginLog loginLog = new LoginLog(); loginLog.setUserId(user.getUserId()); loginLog.setIp(user.getLastIp()); loginLog.setLoginDate(user.getLastVisit()); userDao.updateLoginInfo(user); loginLogDao.insertLoginLog(loginLog); } @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Autowired public void setLoginLogDao(LoginLogDao loginLogDao) { this.loginLogDao = loginLogDao; } }
首先通過@Service註解將UserService標註為一個服務層的Bean;然後通過@Autowired注入userDao和 loginLogDao 這兩個DAO層的Bean;接著通過 hasMatchUser()和findUserByUserName()業務方法簡單地呼叫DAO 完成對應的功能;最後為loginSuccess()方法標註@Transactional事務註解,讓該方法執行在事務環境中(因為我們在 Spring事務管理器攔截切入表示式上加入了@Transactional過濾),否則該方法將在無事務方法中執行。loginSuccess()方法根據入參user物件構造出 LoginLog 物件並將user.credits遞增5,即使用者每登入一次賺取5個積分,然後呼叫userDao更新到t_user中,再呼叫loginLogDao向t_login_log表中新增一條記錄。
loginSuccess()方法將兩個DAO組織起來,共同完成一個事務性的資料操作:更新t_user表記錄並新增t_login_log表記錄。但我們從UserService中卻看不出任何事務操作的影子,這正是Spring的高明之處,它讓我們從事務操作單調、機械的程式碼中解脫出來,專注完成那些不可或缺的業務工作。通過Spring宣告式事務配置即可讓業務類享受EJB宣告式事務的好處,下一節我們將瞭解如何賦予業務類事務管理的能力。
2.4.2 在 Spring 中裝配Service
事務管理的程式碼雖然無須出現在程式程式碼中,但我們必須以某種方式告訴Spring哪些業務類需要工作在事務環境下及事務的規則等內容,以便Spring根據這些資訊自動為目標業務類新增事務管理的功能。
開啟原來的smart-context.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"
<!-- 1 -- > 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 掃描類包,將標註Spring註解的類自動轉化Bean,同時完成Bean的注入 -->
<context:component-scan base-package="com.smart.dao"/>
<!-- 2 -->
<context:component-scan base-package="com.smart.service"/>
...
<!-- 配置事務管理器 -->
<!-- 3 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" />
<!-- 4 --> <!-- 通過AOP配置提供事務增強,讓service包下所有Bean的所有方法擁有事務 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceMethod" expression="(execution(* com.smart.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" /> <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包下所有類的所有標註@Transactional註解的方法都新增了事務增強,即它們都將工作在事務環境中(關於Spring事務的配置,詳見第11章)。
這樣就完成了業務層的程式開發和配置工作,接下來需要對該業務類進行簡單的單元測試,以便檢驗業務方法的正確性。
2.4.3單元測試
TestNG和JUnit 相比有了重大的改進,本書示例所有的單元測試統一採用TestNG框架。請確保已經將TestNG依賴包新增到根模組pom.xml檔案中。在 chapter2\srcltest測試目錄下建立與UserService一致的包結構,即com.smart.service,並建立UserService對應的測試類UserServiceTest,編寫如程式碼所示的測試程式碼。
@ContextConfiguration("classpath*:/smart-context.xml") public class UserServiceTest extends AbstractTransactionalTestNGSpringContextTests { @Autowired private UserService userService; @Test public void testHasMatchUser() { boolean b1 = userService.hasMatchUser("admin", "123456"); boolean b2 = userService.hasMatchUser("admin", "1111"); assertTrue(b1); assertTrue(!b2); } @Test public void testFindUserByUserName()throws Exception{ for(int i =0; i< 100;i++) { User user = userService.findUserByUserName("admin"); assertEquals(user.getUserName(), "admin"); } } @Test public void testAddLoginLog() { User user = userService.findUserByUserName("admin"); user.setUserId(1); user.setUserName("admin"); user.setLastIp("192.168.12.7"); user.setLastVisit(new Date()); userService.loginSuccess(user); } }
Spring 4.0的測試框架很好地整合了TestNG單元測試框架,示例UserServiceTest通過擴充套件Spring測試框架提供測試基類 AbstractTransactionalTestNGSpringContextTests來啟動測試執行器。 @ContextConfiguration也是Spring 提供的註解,用於指定Spring的配置檔案。
可以使用Spring 的@Autowired 將Spring容器中的Bean注入測試類中。在測試方法前通過TestNG的@Test註解即可將方法標註為測試方法
在 IDEA 中執行當前測試類,通過右鍵選單Run ‘UserServiceTest'來執行該測試用例,以檢驗業務類方法的正確性。
2.5展現層
業務層和持久層的開發任務已經完成,該是為程式提供介面的時候了。Spring4.0對MVC進行了全面增強,支援跨域註解@CrossOrigin 配置,Groovy Web整合,Gson、Jackson、Protobuf的 HttpMessageConverter訊息轉換器等,Spring MVC的功能更加豐富、強大(讀者將在第18章學習到Spring MVC的詳細內容)。
2.5.1配置Spring MVC框架
首先需要對web.xml檔案進行配置,以便Web容器啟動時能夠自動啟動Spring容器,如程式碼所示。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.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">
<!-- 1--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:smart-context.xml</param-value> </context-param>
<!-- 2--> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
<!-- 3 --> <servlet> <servlet-name>smart</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>3</load-on-startup> </servlet> <!-- 4 --> <servlet-mapping> <servlet-name>smart</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>
然後通過Web容器上下文引數指定Spring 配置檔案的地址,如①所示。多個配置檔案可用逗號或空格分隔,建議採用逗號分隔的方式。然後在②處指定Spring所提供的ContextLoaderListener的Web容器監聽器,該監聽器在 Web容器啟動時自動執行,它會根據contextConfigLocation Web容器引數獲取 Spring配置檔案,並啟動Spring容器。注意,需要將log4J.propertis日誌配置檔案放置在類路徑下,以便日誌引擎自動生效。
最後需要配置Spring MVC 相關的資訊。Spring MVC像 Struts一樣,也通過一個Servlet來截獲URL 請求,然後再進行相關的處理.
在3處宣告瞭一個 Servlet, Spring MVC 也擁有一個Spring配置檔案(稍後將涉及),該配置檔案的檔名和此處定義的Servlet名有一個契約,即使用<Servlet 名>-servlet.xml的形式。在這裡,Servlet名為smart,則在/WEB-INF目錄下必須提供一個名為smart-servlet.xml 的Spring MVC配置檔案,但這個配置檔案無須通過web.xml的contextConfigLocation上下文引數進行宣告,因為Spring MVC 的 Servlet會自動將smart-servlet.xml檔案和Spring 的其他配置檔案(smart-dao.xml、smart-service.xml)進行拼裝。
在4處對這個Servlet的URL路徑對映進行定義,在這裡讓所有以.html為字尾的URL都能被smart Servlet截獲,進而轉由Spring MVC框架進行處理。我們知道,在Struts框架中一般將URL字尾配置為*.do,而在 WebWork 中一般配置為*.action。其實,框架本身和URL模式沒有任何關係,使用者大可使用喜歡的任何字尾。使用.html字尾,一方面,使用者不能通過URL直接知道我們採用了何種伺服器端技術;另一方面,.html是靜態網頁的字尾,可以騙過搜尋引擎,增加被收錄的概率,所以我們推薦採用這種字尾。對於那些真正無須任何動態處理的靜態網頁,則可以使用.htm字尾加以區分,以避免被框架截獲。
請求被Spring MVC截獲後,首先根據請求的URL查詢到目標的處理控制器,並將請求引數封裝“命令”物件一起傳給控制器處理;然後,控制器呼叫Spring容器中的業務Bean完成業務處理工作並返回結果檢視。
2.5.2處理登入請求
1. POJO控制器類。
首先需要編寫的是LoginController,它負責處理登入請求,完成登入業務,並根據登入成功與否轉向歡迎頁面或失敗頁面。
//1
@Controller public class LoginController{ private UserService userService;
//2 @RequestMapping(value = "/index.html") public String loginPage(){ return "login"; }
//3 @RequestMapping(value = "/loginCheck.html") public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){ boolean isValidUser = userService.hasMatchUser(loginCommand.getUserName(), loginCommand.getPassword()); if (!isValidUser) { return new ModelAndView("login", "error", "使用者名稱或密碼錯誤。"); } else { User user = userService.findUserByUserName(loginCommand .getUserName()); user.setLastIp(request.getLocalAddr()); user.setLastVisit(new Date()); userService.loginSuccess(user); request.getSession().setAttribute("user", user); return new ModelAndView("main"); } } @Autowired public void setUserService(UserService userService) { this.userService = userService; } }
在①處通過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 類僅包括使用者/密碼這兩個屬性(和請求的使用者/密碼引數名稱一樣)。
在程式碼的②和③處,控制器根據登入處理結果分別返回 ModelAndView("login" , " error", "使用者名稱或密碼錯誤。")和 ModelAndView("main")。ModelAndView的第一個引數代表檢視的邏輯名,第二、第三個引數分別為資料模型名稱和資料模型物件,資料模型物件將以資料模型名稱為引數名放置到request的屬性中。
編寫好LoginCommand後,需要在smart-servlet.xml中宣告該控制器,掃描Web路徑,指定Spring MVC的檢視解析器。
<?xml version="1.0" encoding="UTF-8" ?> <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="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/p "> <!-- 掃描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>
Spring MVC為檢視名到具體檢視的對映提供了許多可供選擇的方法。在這裡,我們使用InternalResourceViewResolver,它通過為檢視邏輯名新增前、字尾的方式進行解析。如檢視邏輯名為“login”,將解析為/WEB-INFjsp/login.jsp;檢視邏輯名為“main”,將解析為/WEB-INF/jsp/main.jsp。
2.5.3JSP檢視頁面
論壇登入模組共包括兩個JSP頁面,分別是登入頁面login.jsp和歡迎頁面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> <title>小春論壇登入</title> </head> <body>
<!-- 1 --> <c:if test="${!empty error}"> <font color="red"><c:out value="${error}" /></font> </c:if>
<!-- 2 --> <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 前自動加上應用部署根目錄。假設應用部署在網站的 bbt目錄下,則<c:url>標籤將輸出/bbt/loginController.html。通過<c:url>標籤很好地解決了開發和應用部署目錄不一致的問題。
由於login.jsp放置在 WEB-INF/jsp目錄下,無法直接通過URL進行呼叫,所以它由LoginController控制類中標註了@RequestMapping(value= "/index.html")的 loginPage()進行轉發.