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>
posted @ 2020-09-06 17:38  卡文迪雨  阅读(1162)  评论(0编辑  收藏  举报