SpringMVC之拦截器

158 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第28天,点击查看活动详情

拦截器

应用场景

如果我们想在多个Handler方法执行之前或者之后都进行一些处理,甚至某些情况下需要拦截掉,不让Handler方法执行。那么可以使用SpringMVC为我们提供的拦截器。

拦截器和过滤器的区别

过滤器是在Servlet执行之前或者之后进行处理。而拦截器是对Handler(处理器)执行前后进行处理。

image.png

创建并配置拦截器

SpringMVC 中的拦截器用于拦截控制器方法的执行,需要实现HandlerInterceptor,并在 SpringMVC 的配置文件中进行配置

①创建类实现HandlerInterceptor接口

public class MyInterceptor implements HandlerInterceptor {
}

②实现方法

public class MyInterceptor implements HandlerInterceptor {
    
    //在handler方法执行之前会被调用
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        //返回值代表是否放行,如果为true则放行,如果为fasle则拦截,目标方法执行不到
        return true;
    }
​
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }
​
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

③配置拦截器

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--
                    配置拦截器要拦截的路径
                    /*    代表当前一级路径,不包含子路径
                    /**   代表当前一级路径和多级路径,使用的更多
​
                    例如:
                        /test/*   这种会拦截下面这种路径/test/add  /test/delete
                                  但是拦截不了多级路径的情况例如  /test/add/abc  /test/add/abc/bcd
                        /test/**  这种可以拦截多级目录的情况,无论    /test/add还是/test/add/abc/bcd 都可以拦截
            -->
            <mvc:mapping path="/**"/>
            <!--配置排除拦截的路径-->
            <!--<mvc:exclude-mapping path="/"/>-->
            <!--配置拦截器对象注入容器-->
            <bean class="com.sangeng.interceptor.MyInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

1.4 拦截器方法及参数详解

  • preHandle方法会在Handler方法执行之前执行,我们可以在其中进行一些前置的判断或者处理。
  • postHandle方法会在Handler方法执行之后执行,我们可以在其中对域中的数据进行修改,也可以修改要跳转的页面。
  • afterCompletion方法会在最后执行,这个时候已经没有办法对域中的数据进行修改,也没有办法修改要跳转的页面。我们在这个方法中一般进行一些资源的释放。
    /**
     * 在handler方法执行之前会被调用
     * @param request 当前请求对象
     * @param response 响应对象
     * @param handler 相当于是真正能够处理请求的handler方法封装成的对象,对象中有这方法的相关信息
     * @return 返回值代表是否放行,如果为true则放行,如果为fasle则拦截,目标方法执行不到
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        //返回值代表是否放行,如果为true则放行,如果为fasle则拦截,目标方法执行不到
        return true;
    }
    /**
     * postHandle方法会在Handler方法执行之后执行
     * @param request 当前请求对象
     * @param response 响应对象
     * @param handler 相当于是真正能够处理请求的handler方法封装成的对象,对象中有这方法的相关信息
     * @param modelAndView handler方法执行后的modelAndView对象,我们可以修改其中要跳转的路径或者是域中的数据
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }
    /**
     * afterCompletion方法会在最后执行
     * @param request 当前请求对象
     * @param response 响应对象
     * @param handler 相当于是真正能够处理请求的handler方法封装成的对象,对象中有这方法的相关信息
     * @param ex 异常对象
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }

案例-登录状态拦截器

1.5.1需求

我们的接口需要做用户登录状态的校验,如果用户没有登录则跳转到登录页面,登录的情况下则可以正常访问我们的接口。

1.5.2 分析

怎么判断是否登录?

登录时往session写入用户相关信息,然后在其他请求中从session中获取这些信息,如果获取不到说明不是登录状态。

很多接口都要去写判断的代码,难道在每个Handler中写判断逻辑?

用拦截器,在拦截器中进行登录状态的判断。

登录接口是否应该进行拦截?

不能拦截

静态资源是否要进行拦截?

不能拦截

1.5.3 步骤分析

①登录页面,请求发送给登录接口

②登录接口中,校验用户名密码是否正确(模拟校验即可,先不查询数据库)。

如果用户名密码正确,登录成功。把用户名写入session中。

③定义其他请求的Handler方法

④定义拦截器来进行登录状态判断

如果能从session中获取用户名则说明是登录的状态,则放行

如果获取不到,则说明未登录,要跳转到登录页面。

1.5.4 代码实现

1.5.4.1 登录功能代码实现
①编写登录页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/login">
        用户名:<input type="text" name="username">
        密码:<input type="password" name="password">
        <input type="submit">
    </form>
</body>
</html>
②编写登录接口

接口中,校验用户名密码是否正确(模拟校验即可,先不查询数据库)。如果用户名密码正确,登录成功。把用户名写入session中。

@Controller
public class LoginController {
​
    @PostMapping("/login")
    public String longin(String username, String password, HttpSession session){
        //往session域中写入用户名用来代表登录成功
        session.setAttribute("username",username);
        return "/WEB-INF/page/success.jsp";
    }
}
​
1.5.4.2 登录状态校验代码实现
①定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
}
②重写方法,在preHandle方法中实现状态校验
public class LoginInterceptor implements HandlerInterceptor {
​
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从session中获取用户名,判断是否存在
        HttpSession session = request.getSession();
        String username = (String) session.getAttribute("username");
        if(StringUtils.isEmpty(username)){
            //如果获取不到说明未登录 ,重定向跳转到登录页面
            String contextPath = request.getServletContext().getContextPath();
            response.sendRedirect(contextPath+"/static/login.html");
        }else{
            //如果获取到了,说明之前登录过。放行。
            return true;
        }
        return false;
    }
}
③配置拦截器
  • 登录相关接口不应该拦截
  • 静态资源不拦截
    <mvc:interceptors>
        <mvc:interceptor>
            <!--要拦截的路径-->
            <mvc:mapping path="/**"/>
            <!--排除不拦截的路径-->
            <mvc:exclude-mapping path="/static/**"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/WEB-INF/page/**"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/login"></mvc:exclude-mapping>
            <bean class="com.sangeng.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

总结

拦截器的三个抽象方法

  • preHandle:控制器方法执行之前执行preHandle(),其 boolean 类型的返回值表示是否拦截或放行
    • 返回 true 表示放行,即调用控制器方法
    • 返回 false 表示拦截,即不调用控制器方法
  • postHandle:控制器方法执行之后执行postHandle()
  • afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()