學習資源:動力節點的2020最新SpringMVC教程【IDEA版】-springmvc從入門到精通
1、請求轉發與重定向
當控制器對請求處理完畢后,向其它資源進行跳轉時,有兩種跳轉方式:請求轉發與重定向。框架默認采用重定向的方式。
而根據所要跳轉的資源類型,又可分為兩類:跳轉到頁面與跳轉到其它控制器。
注意,對於請求轉發的頁面,可以是 WEB-INF中頁面;而重定向的頁面,是不能為WEB-INF中頁的,因為重定向相當於用戶再次發出一次請求,而用戶是不能直接訪問 WEB-INF 中資源的。
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 的思想,把業務邏輯和異常處理的代碼分開,實現了解耦合。
框架對異常處理的實現步驟:
-
自定義 Exception 類:
控制器中發生的異常,它可能是已知類型的,如輸入的參數不符合要求,對於已知類型的錯誤,我們可以並在適當的時機在控制器中主動拋出這個類型異常。 -
控制器拋出異常,方法上使用 @ExceptionHandler:
在可能拋出異常的(自定義類型+未知類型)控制器方法上使用,該注解只有一個可選屬性 value,為一個 Class<?> 數組,用於指定該注解的方法所要處理的異常類,即所要匹配的異常。
被注解的方法,其返回值可以是ModelAndView
、String
,或void
,方法名隨意,方法參數可以是Exception
及其子類對象、HttpServletRequest
、HttpServletResponse
等,系統會自動為這些方法參數賦值。 -
創建全局異常處理類:
- 類的上面使用 @ControllerAdvice
- 類中定義處理特定類型異常的方法,方法的上面使用 @ExceptionHandler(value=異常的類型) ,表示當控制器發生此類型的異常時,由當前的方法處理。
- 處理異常的方法,其返回值可以是
ModelAndView
、String
,或void
,方法名隨意,方法參數可以是Exception
及其子類對象、HttpServletRequest
、HttpServletResponse
等,系統會自動為這些方法參數賦值。 - 異常的處理邏輯是:
- 記錄異常的信息,到數據庫、日志文件等
- 發送通知,將異常信息通過郵件、短信等形式發送給相關人員
- 在頁面給用戶發生錯誤的友好提示
- 處理異常的方法,其返回值可以是
- 類中再定義處理其他異常的方法,該方法的上面,直接使用 @ExceptionHandler() 即可
-
注冊組件掃描器和注解驅動:
- 注冊 @Controller 掃描器
<context:component-scan base-package="com.chen.controller"
- 注冊 @ControllerAdvice 掃描器
<context:component-scan base-package="com.chen.exceptionHandler"
- 注解驅動
<mvc:annotation-driven/>
2.1、自定義異常類
定義三個異常類: NameException
、 AgeException
、 MyUserException
,其中 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 個或多個攔截器
攔截器常用於:用戶登陸檢查、用戶權限檢查、記錄日志等。
攔截器的工作時間:
- 在 controller 執行前
- 在 controller 執行之后
- 在請求處理完成后
3.1、一個攔截器
攔截器的使用步驟:
-
自定義攔截器類,實現 HandlerInterceptor 接口,有選擇地實現接口中的 3 個方法
-
preHandle:
參數:
- request
- response
- handler: 被攔截的控制器對象
返回值:
- true:請求通過了 preHandle 的驗證,可以執行控制器方法。且會將 afterCompletion() 方法放入到一個專門的方法棧中等待執行
- false:請求沒有通過 preHandle 的驗證,請求到達 preHandle 就截止了,沒有被繼續處理
特點:
- preHandle 在控制器方法執行之前執行,用戶的請求首先到達此方法,是整個項目某些請求的入口。
- 在 preHandle 中可以獲取請求的信息, 可用於驗證請求是否符合要求
如,驗證用戶是否登錄, 驗證用戶是否有權限訪問某個連接地址(url),如果驗證失敗,可以截斷請求,請求不能被處理;如果驗證成功,可以放行請求,此時控制器方法才能執行
-
postHandle:
參數:- request
- response
- handler
- modelAndView:控制器方法返回的 視圖+數據
特點:
- 在控制器方法執行之后執行,若控制器方法最終未被執行,則 postHandle 不會執行
- 可以獲取請求的信息
- 能夠獲取到控制器方法的返回值 ModelAndView ,可以修改 ModelAndView 中的 數據+視圖,可以影響到最后的請求處理結果,用於修正控制器的處理結果
-
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 {
}
}
- 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>
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>
3.3、攔截器與過濾器的區別
-
過濾器是 servlet 中的對象,攔截器是框架中的對象
-
過濾器是實現 Filter 接口的對象, 攔截器是實現 HandlerInterceptor 接口的對象
-
過濾器是用來設置request,response的參數、屬性的,側重數據過濾
而攔截器是用來驗證請求的,能截斷請求 -
過濾器是在攔截器之前先執行的。
-
過濾器是 Tomcat 服務器創建的對象
攔截器是 SpringMVC 容器中創建的對象
-
過濾器只有 1 個執行時間點
攔截器有 3 個執行時間點
-
過濾器可以處理 jsp, js , html 等
攔截器是側重攔截對 Controller 的請求,如果請求不能被 DispatcherServlet 接收, 這個請求不會被攔截
-
攔截器攔截控制器方法的執行,過濾器過濾 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>