学习资源:动力节点的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>