1. 程式人生 > 實用技巧 >SpringMVC核心技術

SpringMVC核心技術

目錄

一、請求轉發和重定向

當處理器對請求處理完畢後,向其它資源進行跳轉時,有兩種跳轉方式:請求轉發與重定向。而根據所要跳轉的資源型別,又可分為兩類:跳轉到頁面與跳轉到其它處理器

注意,對於請求轉發的頁面,可以是WEB-INF中頁面;而重定向的頁面,是不能為WEB-INF中頁的。因為重定向相當於使用者再次發出一次請求,而使用者是不能直接訪問 WEB-INF 中資源的

SpringMVC 框架把原來 Servlet 中的請求轉發和重定向操作進行了封裝。現在可以使用簡單的方式實現轉發和重定向。

forward:表示轉發,實現 request.getRequestDispatcher("xx.jsp").forward()

redirect:表示重定向,實現 response.sendRedirect("xxx.jsp")

1. 請求轉發

處理器方法返回 ModelAndView 時,需在 setViewName()指定的檢視前新增 forward:,且此時的檢視不再與檢視解析器一同工作,這樣可以在配置瞭解析器時指定不同位置的檢視。檢視頁面必須寫出相對於專案根的路徑。forward 操作不需要檢視解析器。
處理器方法返回 String,在檢視路徑前面加入 forward: 檢視完整路徑

還是第一個案例中

    /**
     * 處理器方法返回ModelAndView,實現轉發forward
     * 語法: setViewName("forward:檢視檔案完整路徑")
     * forward特點: 不和檢視解析器一同使用,就當專案中沒有檢視解析器,不受檢視解析器的限制了
     */

    @RequestMapping(value = "/doForward.do")
    public ModelAndView doForward(String name , Integer age){

        ModelAndView mv = new ModelAndView();

        mv.addObject("myname",name);
        mv.addObject("myage",age);

//        mv.setViewName("show");

        //顯式轉發
//        mv.setViewName("forward:/WEB-INF/view/show.jsp");


        // 配置了檢視解析器,但是這個檔案不在view目錄之下就可以使用這樣的方法,因為forward不受檢視解析器的限制
        mv.setViewName("forward:/hello.jsp");

        return mv;
    }

2. 請求重定向

在處理器方法返回的檢視字串的前面新增 redirect:,則可實現重定向跳轉

 /**
     * 處理器方法返回ModelAndView,實現重定向redirect
     * 語法:setViewName("redirect:檢視完整路徑")
     * redirect特點: 不和檢視解析器一同使用,就當專案中沒有檢視解析器
     *
     * 框架對重定向的操作:
     * 1.框架會把Model中的簡單型別的資料,轉為string使用,作為hello.jsp的get請求引數使用。
     *   目的是在 doRedirect.do 和 hello.jsp 兩次請求之間傳遞資料
     *
     * 2.在目標hello.jsp頁面可以使用引數集合物件 ${param}獲取請求引數值
     *    ${param.myname}
     *
     * 3.重定向不能訪問/WEB-INF資源
     */

    @RequestMapping(value = "/doRedirect.do")
    public ModelAndView doRedirect(String name , Integer age){

        ModelAndView mv = new ModelAndView();

        mv.addObject("myname",name);
        mv.addObject("myage",age);


        //重定向
        mv.setViewName("redirect:/hello.jsp");
        // 相當於這樣,內部作為get請求,通過param可以獲取的到
        //http://localhost:8080/08_forword/hello.jsp?myname=lisi&myage=22


        //重定向不能訪問/WEB-INF資源
//        mv.setViewName("redirect:/WEB-INF/view/show.jsp");

        return mv;
    }

二、異常處理

SpringMVC 框架處理異常的常用方式:使用@ExceptionHandler 註解處理異常

使用註解@ExceptionHandler 可以將一個方法指定為異常處理方法。該註解只有一個可選屬性 value,為一個 Class<?>陣列,用於指定該註解的方法所要處理的異常類,即所要匹配的異常

而被註解的方法,其返回值可以是 ModelAndView、String,或 void,方法名隨意,方法引數可以是 Exception 及其子類物件、HttpServletRequest、HttpServletResponse 等。系統會自動為這些方法引數賦值

1. 異常處理步驟:

  1. 新建maven web專案
  2. 加入依賴
  3. 新建一個自定義異常類 MyUserException , 再定義它的子類NameException ,AgeException
  4. 在controller丟擲NameException , AgeException
  5. 建立一個普通類,作用全域性異常處理類
    • 在類的上面加入@ControllerAdvice
    • 在類中定義方法,方法的上面加入@ExceptionHandler
  6. 建立處理異常的檢視頁面
  7. 建立springmvc的配置檔案
    • 元件掃描器 ,掃描@Controller註解
    • 元件掃描器,掃描@ControllerAdvice所在的包名
    • 宣告註解驅動

1. 自定義異常類

MyUserException是父類

package com.md.exception;

/**
 * @author MD
 * @create 2020-08-14 20:31
 */
public class MyUserException extends Exception {
    public MyUserException() {
        super();
    }

    public MyUserException(String message) {
        super(message);
    }
}

//-------------------------------

package com.md.exception;

/**
 * 當用戶的年齡有異常丟擲AgeException
 * @author MD
 * @create 2020-08-14 20:33
 */
public class AgeException extends MyUserException {
    public AgeException() {
        super();
    }

    public AgeException(String message) {
        super(message);
    }
}
//----------------------------
package com.md.exception;

/**
 * 當用戶的姓名有異常丟擲NameException
 * @author MD
 * @create 2020-08-14 20:32
 */
public class NameException extends MyUserException {
    public NameException() {
        super();
    }

    public NameException(String message) {
        super(message);
    }
}

2. 修改 Controller 丟擲異常

    /**
     *此時丟擲的異常是下面兩個異常的父類
     */
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name , Integer age) throws MyUserException {

        ModelAndView mv = new ModelAndView();

        mv.addObject("myname",name);
        mv.addObject("myage",age);
        // 根據請求引數丟擲異常
        if (!"md".equals(name)){
            throw new NameException("姓名不正確");

        }

        if (age == null || age > 100){
            throw new AgeException("年齡有誤");
        }

        mv.setViewName("show");


        return mv;
    }

3. 定義全域性異常處理類

package com.md.handler;

import com.md.exception.AgeException;
import com.md.exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author MD
 * @create 2020-08-14 20:38
 */


/**
 * @ControllerAdvice : 控制器增強(也就是說給控制器類增加功能--異常處理功能)
 *  位置:在類的上面。
 *  特點:必須讓框架知道這個註解所在的包名,需要在springmvc配置檔案宣告元件掃描器。
 *  指定@ControllerAdvice所在的包名
 */
@ControllerAdvice
public class GlobalException {

    //定義方法,處理髮生的異常
    /*
        處理異常的方法和控制器方法的定義一樣, 可以有多個引數,可以有ModelAndView,
        String, void,物件型別的返回值

        形參:Exception,表示Controller中丟擲的異常物件。
        通過形參可以獲取發生的異常資訊。

        @ExceptionHandler(異常的class):表示異常的型別,當發生此型別異常時,
        由當前方法處理
     */


    @ExceptionHandler(value = NameException.class)
    public ModelAndView doNameException(Exception ex) {
        // 處理NameException異常

        /*
           異常發生處理邏輯:
           1.需要把異常記錄下來, 記錄到資料庫,日誌檔案。
             記錄日誌發生的時間,哪個方法發生的,異常錯誤內容。
           2.傳送通知,把異常的資訊通過郵件,簡訊,微信傳送給相關人員。
           3.給使用者友好的提示。
         */

        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "姓名是md,其他使用者不能訪問");
        mv.addObject("ex", ex);
        mv.setViewName("nameError");

        return mv;
    }




    @ExceptionHandler(value = AgeException.class)
    public ModelAndView doAgeException(Exception ex) {
        // 處理AgeException異常

        /*
           異常發生處理邏輯:
           1.需要把異常記錄下來, 記錄到資料庫,日誌檔案。
             記錄日誌發生的時間,哪個方法發生的,異常錯誤內容。
           2.傳送通知,把異常的資訊通過郵件,簡訊,微信傳送給相關人員。
           3.給使用者友好的提示。
         */

        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "年齡過大");
        mv.addObject("ex", ex);
        mv.setViewName("AgeError");

        return mv;
    }


    // 這個只能有一個,也就是萬能的異常處理方法
    // 處理其他異常,NameException、AgeException之外的異常,也就是除了自定義之外的異常都能處理
    @ExceptionHandler
    public ModelAndView doOtherException(Exception ex) {
        // 處理OtherException異常

        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "404");
        mv.addObject("ex", ex);
        mv.setViewName("defaultError");

        return mv;
    }

}

4. 定義異常響應頁面

nameError.jsp

<%--
  Created by IntelliJ IDEA.
  User: MD
  Date: 2020/8/14
  Time: 20:54
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>nameError.jsp</h1>

    <h2>提示:${msg}</h2>

    <h2>系統異常資訊:${ex.message}</h2>
</body>
</html>

<h1>ageError.jsp</h1>

<h2>提示:${msg}</h2>

<h2>系統異常資訊:${ex.message}</h2>

<%--和上面都一樣,這裡就省略了--%>

<h1>defaultError.jsp</h1>

<h2>提示:${msg}</h2>

<h2>系統異常資訊:${ex.message}</h2>

5. springmvc的配置檔案

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <!--宣告元件掃描器-->
    <context:component-scan base-package="com.md.controller"/>


    <!--宣告springmvc框架中的檢視解析器,幫助開發人員設定檢視檔案路徑-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--字首:檢視檔案的路徑-->
        <property name="prefix" value="/WEB-INF/view/" />

        <!--字尾:檢視檔案的副檔名-->
        <property name="suffix" value=".jsp"/>

    </bean>



    
    
    <!-- 處理異常的兩個步驟
	1. 註冊元件掃描器,也即是@ControllerAdvice註解所在的包名
	2. 註冊註解驅動,

-->
    <context:component-scan base-package="com.md.handler"/>
    <mvc:annotation-driven/>



</beans>

這樣就可以了

三、攔截器

SpringMVC 中的 Interceptor 攔截器是非常重要和相當有用的,它的主要作用是攔截指定的使用者請求,並進行相應的預處理與後處理。

  • 攔截器是springmvc中的一種,需要實現HandlerInterceptor介面。
  • 攔截器和過濾器類似,功能方向側重點不同。 過濾器是用來過濾器請求引數,設定編碼字符集等工作。
  • 攔截器是攔截使用者的請求,做請求做判斷處理的。
  • 攔截器是全域性的,可以對多個Controller做攔截, 一個專案中可以有0個或多個攔截器, 他們在一起攔截使用者的請求。
  • 攔截器常用在:使用者登入處理,許可權檢查, 記錄日誌。

1. 攔截器使用步驟

  1. 新建maven web專案
  2. 加入依賴
  3. 建立Controller類
  4. 建立一個普通類,作為攔截器使用
    • 實現HandlerInterceptor介面
    • 實現介面中的三個方法
  5. 建立show.jsp
  6. 建立springmvc的配置檔案
    • 元件掃描器 ,掃描@Controller註解
    • 宣告攔截器,並指定攔截的請求uri地址

2. 一個攔截器的執行

Controller類

    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name , Integer age) {
        System.out.println("-------MyController的doSome()");

        ModelAndView mv = new ModelAndView();

        mv.addObject("myname",name);
        mv.addObject("myage",age);

        mv.setViewName("show");

        return mv;
    }

還是在之前的專案中,定義個普通類,作為攔截器

package com.md.handler;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

/**
 * 攔截器類,攔截使用者的請求
 * @author MD
 * @create 2020-08-14 21:18
 */
public class MyInterceptor implements HandlerInterceptor {
// 快捷鍵:ctrl + O
    private long btime = 0;


    /*
     * preHandle叫做預處理方法。
     *   重要:是整個專案的入口,門戶。 當preHandle返回true 請求可以被處理。
     *        preHandle返回false,請求到此方法就截止。
     *
     * 引數:
     *  Object handler : 被攔截的控制器物件
     * 返回值boolean
     *   true:請求是通過了攔截器的驗證,可以執行處理器方法
     *   執行順序
            攔截器: preHandle()
            -------MyController的doSome()
            攔截器: postHandle()
            攔截器: afterCompletion()
         *
     *   false:請求沒有通過攔截器的驗證,請求到達攔截器就截止了。 請求沒有被處理
     *   僅僅輸出這一句話,
     *      攔截器: preHandle()
     *
     *
     *  特點:
     *   1.方法在控制器方法(MyController的doSome)之前先執行的。
     *     使用者的請求首先到達此方法
     *
     *   2.在這個 方法中可以獲取請求的資訊, 驗證請求是否符合要求。
     *     可以驗證使用者是否登入, 驗證使用者是否有許可權訪問某個連線地址(url)。
     *      如果驗證失敗,可以截斷請求,請求不能被處理。
     *      如果驗證成功,可以放行請求,此時控制器方法才能執行。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        btime = System.currentTimeMillis();
        System.out.println("攔截器: preHandle()");


        // 計算的業務邏輯,根據計算的結果,返回true或者false

        //  給瀏覽器一個反饋
//        request.getRequestDispatcher("/tips.jsp").forward(request,response);
//
//        return false;

        return true;
    }


    /*
         postHandle:後處理方法。
         引數:
          Object handler:被攔截的處理器物件MyController
          ModelAndView modelAndView:處理器方法的返回值

          特點:
           1.在處理器方法之後執行的(MyController.doSome())
           2.能夠獲取到處理器方法的返回值ModelAndView,可以修改ModelAndView中的
           資料和檢視,可以影響到最後的執行結果。
           3.主要是對原來的執行結果做二次修正,

           ModelAndView mv = MyController.doSome();
           postHandle(request,response,handler,mv);
       */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("攔截器: postHandle()");

        // 對原來的doSome執行的結果進行調整
        if (modelAndView != null){
            modelAndView.addObject("mydate" , new Date());
            modelAndView.setViewName("other");
        }
    }


    /*
          afterCompletion:最後執行的方法
          引數
            Object handler:被攔截器的處理器物件
            Exception ex:程式中發生的異常
          特點:
           1.在請求處理完成後執行的。框架中規定是當你的檢視處理完成後,對檢視執行了forward。就認為請求處理完成。
           2.一般做資源回收工作的, 程式請求過程中建立了一些物件,在這裡可以刪除,把佔用的記憶體回收。
         */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("攔截器: afterCompletion()");
        long etime = System.currentTimeMillis();
        System.out.println("計算從preHandle到請求處理完成的時間:"+(etime - btime ));
    }
}

建立springmvc的配置檔案

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <!--宣告元件掃描器-->
    <context:component-scan base-package="com.md.controller"/>




    <!--宣告springmvc框架中的檢視解析器,幫助開發人員設定檢視檔案路徑-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--字首:檢視檔案的路徑-->
        <property name="prefix" value="/WEB-INF/view/" />

        <!--字尾:檢視檔案的副檔名-->
        <property name="suffix" value=".jsp"/>

    </bean>




    <!-- 宣告攔截器: 攔截器可以一個或者多個-->
    <mvc:interceptors>
        <!--宣告第一個攔截器-->
        <mvc:interceptor>
            <!-- 指定攔截的請求url地址, page就是url地址,可以使用萬用字元 ** , 表示任意的字元,檔案或者多級目錄-->
            <!-- http://localhost:8080/user/xxx-->

            <!--<mvc:mapping path="/user/**"/>-->


            <mvc:mapping path="/**"/>
            <!--宣告攔截器物件-->
            <bean class="com.md.handler.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

攔截器中方法與處理器方法的執行順序

3. 多個攔截器的執行

再定義一個攔截器

package com.md.handler;

public class MyInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("111111111111攔截器: preHandle()");


        return true;
    }



    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("11111111111111111攔截器: postHandle()");


    }



    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("111111111111111攔截器: afterCompletion()");

    }
}
//---------------------------------------

package com.md.handler;
public class MyInterceptor2 implements HandlerInterceptor {
// 快捷鍵:ctrl + O

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("2222222222222222攔截器: preHandle()");


        return false;
    }



    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("2222222222222222攔截器: postHandle()");


    }



    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("122222222222222222攔截器: afterCompletion()");

    }
}

//---------------------------------------

多個攔截器的註冊,在springmvc的配置檔案

    <!-- 宣告攔截器: 攔截器可以一個或者多個

        在框架中儲存是在一個ArrayList  , 先宣告的在前面,
    -->
    <mvc:interceptors>
        <!--宣告第一個攔截器-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!--宣告攔截器物件-->
            <bean class="com.md.handler.MyInterceptor"/>
        </mvc:interceptor>



        <!--宣告第二個攔截器-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!--宣告攔截器物件-->
            <bean class="com.md.handler.MyInterceptor2"/>
        </mvc:interceptor>

    </mvc:interceptors>


    <!-- 返回的都是true,執行的順序-->

    <!--111111111111攔截器: preHandle()-->
    <!--2222222222222222攔截器: preHandle()-->
    <!-- -&#45;&#45;&#45;&#45;&#45;&#45;MyController的doSome()-->
    <!--2222222222222222攔截器: postHandle()-->
    <!--11111111111111111攔截器: postHandle()-->
    <!--122222222222222222攔截器: afterCompletion()-->
    <!--111111111111111攔截器: afterCompletion()-->


    <!-- 第一個true,第一個false,執行順序-->
    <!--111111111111攔截器: preHandle()-->
    <!--2222222222222222攔截器: preHandle()-->
    <!--111111111111111攔截器: afterCompletion()-->


    <!-- 返回的都是false,執行的順序-->
    <!--111111111111攔截器: preHandle()-->

</beans>

當有多個攔截器時,形成攔截器鏈。攔截器鏈的執行順序,與其註冊順序一致。需要再次強調一點的是,當某一個攔截器的 preHandle()方法返回 true 並被執行到時,會向一個專門的方法棧中放入該攔截器的 afterCompletion()方法

4. 攔截器和過濾器的區別

  1. 過濾器是servlet中的物件, 攔截器是框架中的物件
  2. 過濾器實現Filter介面的物件, 攔截器是實現HandlerInterceptor
  3. 過濾器是用來設定request,response的引數,屬性的,側重對資料過濾的,
  4. 攔截器是用來驗證請求的,能截斷請求。
  5. 過濾器是在攔截器之前先執行的。
  6. 過濾器是tomcat伺服器建立的物件、攔截器是springmvc容器中建立的物件