一、請求轉發和重定向
當處理器對請求處理完畢後,向其它資源進行跳轉時,有兩種跳轉方式:請求轉發與重定向。而根據所要跳轉的資源型別,又可分為兩類:跳轉到頁面與跳轉到其它處理器
注意,對於請求轉發的頁面,可以是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. 異常處理步驟:
- 新建maven web專案
- 加入依賴
- 新建一個自定義異常類 MyUserException , 再定義它的子類NameException ,AgeException
- 在controller丟擲NameException , AgeException
- 建立一個普通類,作用全域性異常處理類
- 在類的上面加入@ControllerAdvice
- 在類中定義方法,方法的上面加入@ExceptionHandler
- 建立處理異常的檢視頁面
- 建立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. 攔截器使用步驟
- 新建maven web專案
- 加入依賴
- 建立Controller類
- 建立一個普通類,作為攔截器使用
- 實現HandlerInterceptor介面
- 實現介面中的三個方法
- 建立show.jsp
- 建立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()-->
<!-- -------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. 攔截器和過濾器的區別
- 過濾器是servlet中的物件, 攔截器是框架中的物件
- 過濾器實現Filter介面的物件, 攔截器是實現HandlerInterceptor
- 過濾器是用來設定request,response的引數,屬性的,側重對資料過濾的,
- 攔截器是用來驗證請求的,能截斷請求。
- 過濾器是在攔截器之前先執行的。
- 過濾器是tomcat伺服器建立的物件、攔截器是springmvc容器中建立的物件