Spring7:基於註解的Spring MVC(下篇)

五月的倉頡發表於2016-04-09

Model

上一篇文章《Spring6:基於註解的Spring MVC(上篇)》,講了Spring MVC環境搭建、@RequestMapping以及引數繫結,這是Spring MVC中最基礎也是最重要的內容,本篇文章繼續講講Spring MVC中其餘的知識點,先從Model開始。

前一篇文章比較詳細地解讀了資料從頁面請求到伺服器後臺的一些細節,那麼下一個要解決的問題就是資料如何從後臺再次傳回前臺,答案就是這裡要說的Model,關於Model在寫例子之前我特別先說明三點:

1、Model本身是一個介面,其實現類為ExtendedModelMap,除了使用Model之外還可以使用ModelAndView、ModelMap這些,不過要是沒有特殊需求,使用Model比較簡單,我個人也比較喜歡使用Model

2、Model的生命週期是Request,也就是說要通過Model傳值只能使用轉發而不能使用重定向

3、為什麼要使用Model而不是用Request,最主要的原因就是減少程式碼的侵入性或者說程式碼的耦合度也行。因為Model是Spring的元件,Request是J2EE的元件,使用Model而不去使用Request可以減少對J2EE的依賴,也便於除錯

OK,接下來看例子,總體的程式碼還是按照上一篇文章的來,先看後臺的程式碼:

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping
    public String dispatchTest(Test test, Model model)
    {
        model.addAttribute("modelKey", "modelValue");
        return "test";
    }
}

就往Model裡面塞一個Key-Value,然後轉發到test.jsp下,test.jsp頁面要取Model的值,可以通過JSTL(EL表示式也可以)獲取,反正直接在jsp頁面上通過"<% ... %>"寫Java指令碼是行不通的。test.jsp頁面這麼寫:

<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
      <head>
        <base href="<%=basePath%>">
        
        <title>test頁面</title>
        
        <meta http-equiv="pragma" content="no-cache">
        <meta http-equiv="cache-control" content="no-cache">
        <meta http-equiv="expires" content="0">    
        <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
        <meta http-equiv="description" content="This is my page">
        <!--
        <link rel="stylesheet" type="text/css" href="styles.css">
        -->
      </head>
  
      <body>
          <c:out value="${modelKey}" />
      </body>
</html>

OK,然後訪問一下"http://localhost:8080/SpringMVC/test"這個地址,頁面上"modelValue"這幾個字元就出來了。

之前說過了,Model的生命週期是Request,那麼如果頁面是重定向到test.jsp上面去,肯定是取不到"modelValue"的,可以自己試一下,因此重定向過去的話,要在後臺把資料設定到session中。

test.jsp頁面不變,Controller可以這麼改:

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping
    public String dispatchTest(Test test, HttpSession session)
    {
        session.setAttribute("modelKey", "modelValue");
        return "redirect:/test.jsp";
        //return "test";
    }
}

可以試一下,再訪問一下"http://localhost:8080/SpringMVC/test"這個地址,"modelValue"這幾個字元在頁面上就出來了。

在Spring MVC中,Request、Response、Session、InputStream、OutputStream這些物件是自動注入的,但是就像之前說的,為了減少程式碼的侵入性與耦合度,能不使用盡量還是不使用這些J2EE的物件的好。

 

攔截器(Interceptor)

SpringMVC中的攔截器相當於J2EE中的過濾器,是非常重要和相當有用的,它的主要作用就是攔截使用者的請求並進行相應的處理的,比如通過它來進行許可權驗證,或者是來判斷使用者是否登陸。

在SpringMVC中使用攔截器的方法比較簡單,首先實現HandlerInterceptor介面,實現afterCompletion、postHandle、preHandle三個抽象方法,這裡定義兩個Interceptor:

public class TestInterceptor1 implements HandlerInterceptor
{
    public void afterCompletion(HttpServletRequest arg0,
            HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception
    {
        System.out.println("TestInterceptor1.afterCompletion()");
    }

    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
            Object arg2, ModelAndView arg3) throws Exception
    {
        System.out.println("TestInterceptor1.postHandle()");
    }

    public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
            Object arg2) throws Exception
    {
        System.out.println("TestInterceptor1.preHandle()");
        return true;
    }
}
public class TestInterceptor2 implements HandlerInterceptor
{
    public void afterCompletion(HttpServletRequest arg0,
            HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception
    {
        System.out.println("TestInterceptor2.afterCompletion()");
    }

    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
            Object arg2, ModelAndView arg3) throws Exception
    {
        System.out.println("TestInterceptor2.postHandle()");
    }

    public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
            Object arg2) throws Exception
    {
        System.out.println("TestInterceptor2.preHandle()");
        return true;
    }
}

說明一下三個方法的作用:

1、afterCompletion:在整個檢視渲染完畢之後執行方法裡面的內容,主要用於釋放一些資源

2、postHandle:在Controller執行之後,檢視渲染之前執行方法裡面的內容,也就是說postHandle方法可以對Model進行操作

3、preHandle:在Controller執行之前,執行方法裡面的內容,注意該方法是有返回值的,當方法返回false時整個請求就結束了

然後在springmvc-servlet.xml裡面增加攔截器的配置:

<!-- 配置攔截器 -->
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/test" />
        <bean class="com.xrq.interceptor.TestInterceptor2" />
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/test" />
        <bean class="com.xrq.interceptor.TestInterceptor1" />
    </mvc:interceptor>
</mvc:interceptors>

假如有多個攔截器的話,"<mvc:interceptor>...</mvc:interceptor>"定義的順序就是攔截器執行的順序。

下面繼續訪問"http://localhost:8080/SpringMVC/test",程式碼執行的結果是:

TestInterceptor2.preHandle()
TestInterceptor1.preHandle()
TestInterceptor1.postHandle()
TestInterceptor2.postHandle()
TestInterceptor1.afterCompletion()
TestInterceptor2.afterCompletion()

也許有些朋友對這個執行結果不是很理解,我其實是懂的,但確實一下子也說不清楚。

如果不是很理解的朋友,可以去看一下Java設計模式裡面的責任鏈模式,攔截器的這種呼叫方法實際上是一種鏈式的呼叫法,TestInterceptor2呼叫TestInterceptor1,TestInterceptor1方法走了才會回到TestInterceptor2的方法裡面。

相關文章