4、SpringMVC 轉發&重定向&異常處理&攔截器


學習資源:動力節點的2020最新SpringMVC教程【IDEA版】-springmvc從入門到精通


1、請求轉發與重定向

當控制器對請求處理完畢后,向其它資源進行跳轉時,有兩種跳轉方式:請求轉發與重定向。框架默認采用重定向的方式。

而根據所要跳轉的資源類型,又可分為兩類:跳轉到頁面與跳轉到其它控制器。

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

image-20200905164313422

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

  • forward:表示轉發,實現 request.getRequestDispatcher("xx.jsp").forward()
  • redirect:表示重定向,實現 response.sendRedirect("xxx.jsp")

共同點:

  • forward 和 redirect 都不會與視圖解析器一同工作,這樣可以在配置了視圖解析器同時指定其他不同位置的視圖。
  • forward 和 redirect 視圖時,都要寫出視圖相對於項目根的路徑
  • forward 和 redirect 都可以訪問視圖文件,也可以訪問其他的控制器。
  • 控制器方法返回 ModelAndView、String、void 都可以使用forward,redirect。

1.1、請求轉發

RequestMapping(value="/doForward")
public Mode1AndView doForward(Integer age,String name){
    
    Mode1AndView mv = new Mode1AndView();
    mv.addobject("myname", name);
    mv.addobject("myage", age);
    
    // 轉發到 WEB-INF 下的視圖
    // mv.setViewName("forward:/WEB-INF/view/show.jsp");
    
    // 轉發到根目錄下的視圖
    mv.setViewName("forward:/other.jsp");
    
    // 轉發到其他控制器
    mv.setViewName("forward:/doSome");
    return mv;
}

RequestMapping(value="/doSome")
public String doSome(Integer age,String name){
    
}

1.2、請求重定向

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

    ModelAndView mv = new ModelAndView();
    
    //重定向不能訪問受保護的WEB-INF下面的資源
    //mv.setViewName ( "redirect:/WEB-INF/view/show.jsp");
    
    // 重定向到視圖
    mv.setViewName ("redirect:/other.jsp");
    
	// 重定向到其他控制器
    mv.setViewName("forward:/doSome");
    return mv;
}

RequestMapping(value="/doSome")
public String doSome(Integer age,String name){
    
}

2、異常處理

SpringMVC 框架采用的是統一的、全局的異常處理,把 controller 中的所有異常處理集中到一個地方集中處理,采用的是 aop 的思想,把業務邏輯和異常處理的代碼分開,實現了解耦合。

框架對異常處理的實現步驟:

  1. 自定義 Exception 類
    控制器中發生的異常,它可能是已知類型的,如輸入的參數不符合要求,對於已知類型的錯誤,我們可以並在適當的時機在控制器中主動拋出這個類型異常。

  2. 控制器拋出異常,方法上使用 @ExceptionHandler
    在可能拋出異常的(自定義類型+未知類型)控制器方法上使用,該注解只有一個可選屬性 value,為一個 Class<?> 數組,用於指定該注解的方法所要處理的異常類,即所要匹配的異常。
    被注解的方法,其返回值可以是 ModelAndViewString ,或 void ,方法名隨意,方法參數可以是 Exception 及其子類對象、 HttpServletRequestHttpServletResponse 等,系統會自動為這些方法參數賦值。

  3. 創建全局異常處理類

    1. 類的上面使用 @ControllerAdvice
    2. 類中定義處理特定類型異常的方法,方法的上面使用 @ExceptionHandler(value=異常的類型) ,表示當控制器發生此類型的異常時,由當前的方法處理。
      1. 處理異常的方法,其返回值可以是 ModelAndViewString ,或 void ,方法名隨意,方法參數可以是 Exception 及其子類對象、 HttpServletRequestHttpServletResponse 等,系統會自動為這些方法參數賦值。
      2. 異常的處理邏輯是:
        1. 記錄異常的信息,到數據庫、日志文件等
        2. 發送通知,將異常信息通過郵件、短信等形式發送給相關人員
        3. 在頁面給用戶發生錯誤的友好提示
    3. 類中再定義處理其他異常的方法,該方法的上面,直接使用 @ExceptionHandler() 即可
  4. 注冊組件掃描器和注解驅動

    1. 注冊 @Controller 掃描器
    <context:component-scan base-package="com.chen.controller"
    
    1. 注冊 @ControllerAdvice 掃描器
    <context:component-scan base-package="com.chen.exceptionHandler"
    
    1. 注解驅動
    <mvc:annotation-driven/>
    

2.1、自定義異常類

定義三個異常類: NameExceptionAgeExceptionMyUserException ,其中 MyUserException 是另外兩個異常的父類。

package com.chen.exception;

public class MyException extends Exception{

    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}
package com.chen.exception;

public class NameException extends MyException {
    public NameException() {
        super();
    }

    public NameException(String message) {
        super(message);
    }
}
package com.chen.exception;

public class AgeException extends MyException {
    public AgeException() {
        super();
    }

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

2.2、controller 拋出異常

@Controller
@RequestMapping("/student")
public class StudentController {    
    
	@RequestMapping(value="/register")
    public ModelAndView doRegister(Integer age,String name) throws MyException {

        ModelAndView mv = new ModelAndView();

        if(! "張三".equals(name)){
            throw new NameException("不收張三!!!");
        }
        else if(age==null || age > 80){
            throw new AgeException("年齡太大了,不收!!!");
        }
        mv.addObject( "myname", name);
        mv.addObject( "myage",age);
        mv.setViewName( "show" );
        return mv;
    }
}

2.3、創建全局異常處理類

package com.chen.exceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

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

        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","姓名必須是zs,其它用戶不能訪問");
        mv.addObject("ex",exception);
        mv.setViewName("nameError");
        return mv;
    }

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

        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","你的年齡不能大於80");
        mv.addObject("ex",exception);
        mv.setViewName("ageError");
        return mv;
    }

    //處理其它異常, NameException, AgeException以外,不知類型的異常
    @ExceptionHandler
    public ModelAndView doOtherException(Exception exception){

        //處理其它異常
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","你的年齡不能大於80");
        mv.addObject("ex",exception);
        mv.setViewName("otherError");
        return mv;
    }
}

2.4、定義異常響應頁面

<%--nameError.jsp--%>
<body>
nameErrors page<br>
<hr>
${ex.message }<br>
</body>

<%--ageError.jsp--%>
<body>
ageError page<br>
<hr>
${ex.message }<br>
</body>

<%--otherError.jsp--%>
<body>
otherError page<br>
<hr>
${ex.message }<br>
</body>

2.5、配置 springmvc 配置文件

<context:component-scan base-package="com.chen.controller"/>

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

3、攔截器

SpringMVC 中的 Interceptor 攔截器是非常重要和相當有用的,它的主要作用是攔截指定的用戶請求, 並進行相應的預處理與后處理。 其攔截的時間點在 “ 控制器映射器根據用戶提交的請求映射出了所要執行的控制器類, 並且也找到了要執行該控制器類的控制器適配器,在控制器適配器執行控制器之前 ” 。當然,在控制器映射器映射出所要執行的控制器類時,已經將攔截器與控制器組合為了一個控制器執行鏈,並返回給了中央調度器。

攔截器的作用域是全局的,可以對多個控制器做攔截。一個項目中可以有 0 個或多個攔截器

攔截器常用於:用戶登陸檢查、用戶權限檢查、記錄日志等。

攔截器的工作時間:

  1. 在 controller 執行前
  2. 在 controller 執行之后
  3. 在請求處理完成后

3.1、一個攔截器

攔截器的使用步驟:

  1. 自定義攔截器類,實現 HandlerInterceptor 接口,有選擇地實現接口中的 3 個方法

    1. preHandle

      參數:

      • request
      • response
      • handler: 被攔截的控制器對象

      返回值:

      • true:請求通過了 preHandle 的驗證,可以執行控制器方法。且會將 afterCompletion() 方法放入到一個專門的方法棧中等待執行
      • false:請求沒有通過 preHandle 的驗證,請求到達 preHandle 就截止了,沒有被繼續處理

      特點:

      • preHandle 在控制器方法執行之前執行,用戶的請求首先到達此方法,是整個項目某些請求的入口。
      • 在 preHandle 中可以獲取請求的信息, 可用於驗證請求是否符合要求
        如,驗證用戶是否登錄, 驗證用戶是否有權限訪問某個連接地址(url),如果驗證失敗,可以截斷請求,請求不能被處理;如果驗證成功,可以放行請求,此時控制器方法才能執行
    2. postHandle
      參數:

      • request
      • response
      • handler
      • modelAndView:控制器方法返回的 視圖+數據

      特點:

      • 在控制器方法執行之后執行,若控制器方法最終未被執行,則 postHandle 不會執行
      • 可以獲取請求的信息
      • 能夠獲取到控制器方法的返回值 ModelAndView ,可以修改 ModelAndView 中的 數據+視圖,可以影響到最后的請求處理結果,用於修正控制器的處理結果
    3. afterCompletion
      參數:

      • request
      • response
      • handler
      • exception:程序中發生的異常

      特點:

      • 當 preHandle() 方法返回 true 時,會將該方法放到專門的方法棧中,等到請求處理完成之后執行執行該方法(框架中規定對視圖執行了 forward 就認定請求處理完成)。
        該方法是在中央調度器渲染(數據填充)了響應頁面之后執行的,此時對 ModelAndView 再操作也對響應無濟於事。
      • 一般用做資源回收工作, 程序請求過程中創建了一些對象,在這里可以刪除,把占用的內存回收
package com.chen.handler;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {

    }
}
  1. springmvc 配置文件中聲明攔截器,指定攔截的請求 uri 地址,/** 表示攔截所有請求
<context:component-scan base-package="com.chen.*"/>

<!-- 注冊攔截器,可以有多個 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 
		攔截指定路徑的請求
		<mvc:mapping path="/user/**"/> 
		-->
        <!-- 攔截所有請求 -->
        <mvc:mapping path="/**"/>
        <!-- 所攔截請求使用的攔截器對象 -->
        <bean class="com.chen.handler.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

image-20200906105717088


3.2、多個攔截器

package com.chen.handler;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {

    }
}
////////////////////////////////////////////////////////////////////////////////////////
public class MyInterceptor2 implements HandlerInterceptor {

}
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/user/**"/>
        <bean class="com.chen.handler.MyInterceptor"/>
    </mvc:interceptor>
	<mvc:interceptor>
        <mvc:mapping path="/student/**"/>
        <bean class="com.chen.handler.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

image-20200906114038239


3.3、攔截器與過濾器的區別

  1. 過濾器是 servlet 中的對象,攔截器是框架中的對象

  2. 過濾器是實現 Filter 接口的對象, 攔截器是實現 HandlerInterceptor 接口的對象

  3. 過濾器是用來設置request,response的參數、屬性的,側重數據過濾
    而攔截器是用來驗證請求的,能截斷請求

  4. 過濾器是在攔截器之前先執行的。

  5. 過濾器是 Tomcat 服務器創建的對象

    攔截器是 SpringMVC 容器中創建的對象

  6. 過濾器只有 1 個執行時間點

    攔截器有 3 個執行時間點

  7. 過濾器可以處理 jsp, js , html 等

    攔截器是側重攔截對 Controller 的請求,如果請求不能被 DispatcherServlet 接收, 這個請求不會被攔截

  8. 攔截器攔截控制器方法的執行,過濾器過濾 servlet 的請求響應


3.4、攔截器使用實例——驗證用戶是否登錄

index.jsp 發起請求,controller 處理請求,

3.4.1、首頁發起請求

在首頁 index.jsp 發起請求,攜帶用戶信息:用戶名、密碼等。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
    <head>
        <title>首頁</title>
        <base href="<%=basePath%>">
    </head>
    <body>
        <h1>登錄頁面</h1>
        <hr>
        <form action="user/login">
            用戶名:<input type="text" name="username"> <br>
            密碼: <input type="password" name="pwd"> <br>
            <input type="submit" value="提交">
        </form>
    </body>
</html>

3.4.2、控制器

package com.chen.controller;

@Controller
@RequestMapping("/user")
public class UserController {

    // 處理登陸請求
    @RequestMapping("/login")
    public String login(HttpSession session, String username, String pwd) throws Exception {
        // 向session記錄用戶身份信息
        session.setAttribute("user", username);
        return "success";
    }
    
	// 沒有登陸,就跳轉到登陸頁面
    @RequestMapping("/toLogin")
    public String jumpLogin() throws Exception {
        return "forward:/index";
    }

    // 登陸成功,就跳轉到成功頁面
    @RequestMapping("/toSuccess")
    public String jumpSuccess() throws Exception {
        return "success";
    }

    // 退出登陸,銷毀 session ,跳轉到登錄頁面
    @RequestMapping("logout")
    public String logout(HttpSession session) throws Exception {
        // session 過期
        session.invalidate();
        return "forward:/login";
    }
}

3.4.3、登陸成功頁面

登錄成功頁面 success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
    <head>
        <title>登錄成功</title>
        <base href="<%=basePath%>">
    </head>
    <body>
        <h1>登錄成功頁面</h1>
        <hr>
        ${user}
        <a href="user/logout">注銷</a>
    </body>
</html>

3.4.4、攔截器

package com.chen.handler;

public class LoginInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                             Object handler) throws ServletException, IOException {
        
        // 如果是登陸頁面則放行
        System.out.println("uri: " + request.getRequestURI());
        if (request.getRequestURI().contains("login")) {
            return true;
        }

        HttpSession session = request.getSession();
        // 如果用戶已登陸也放行
        if(session.getAttribute("user") != null) {
            return true;
        }

        // 用戶沒有登陸則跳轉到登陸頁面
        request.getRequestDispatcher("/index.jsp").forward(request, response);
        return false;
    }
}

3.4.5、注冊攔截器

<mvc:interceptors>
    <mvc:interceptor>
        <!-- 攔截所有請求 -->
        <mvc:mapping path="/**"/>
        <!-- 所攔截請求使用的攔截器對象 -->
        <bean class="com.chen.handler.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM