《精通Spring4.x企業應用開發實戰》第二章

歡迎!發表於2020-11-10

  昨天聯絡了一下學長,學長說這個專案因為種種原因程式碼比較混亂,感覺最壞的打算是從頭開始寫。

  大概詢問了一下學長和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)也被稱為實體類,它代表了業務的狀態,且貫穿展現層、業務層和持久層,並最終被持久化到資料庫中。領域物件使資料庫表操作以物件導向的方式進行,為程式擴充套件帶來了更大的靈活性。領域物件不一定等同於資料庫表,不過對於簡單的應用來說,領域物件往往擁有對應的資料庫表。

  持久層的主要工作就是從資料庫表中載入資料並例項化領域物件,或將領域物件持久化到資料庫表中。論壇登入模組需要涉及兩個領域物件: UserLoginLog,前者代表使用者資訊,後者代表日誌資訊,分別對應t_usert_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業務層
  在論壇登入例項中,業務層僅有一個業務類,即 UserServiceUserService負責將持久層的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()進行轉發.

 

相關文章