SpringMVC:六、拦截器

10、拦截器

10.1、简介

SpringMVC 拦截器(HandlerInterceptor)是 Spring AOP 的应用。

  • 可用于对处理器进行预处理和后置处理:相当于 AOP 环绕通知;
  • 类似 Servlet 的过滤器(Filter)。
过滤器 拦截器
说明 Servlet 规范中的一部分 SpringMVC 框架内置
可用工程 任何 Java Web 工程 使用了 SpringMVC 框架的工程
配置 web.xmlurl-pattern 中配置 Spring 配置文件的 mvc:interceptor 中配置
拦截 要访问的资源,包括静态资源 Controller 请求方法,不包括静态资源

10.2、自定义拦截器

10.2.1、环境搭建

  1. 新建 module,添加 web 框架支持:在项目结构中手动添加 lib 目录
  2. web.xml
    • DispatcherServlet
    • 字符编码过滤器
  3. applicationContext.xml
    • 添加注解支持
    • 静态资源过滤
    • 添加注解驱动
    • 视图解析器

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>characterEncoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

applicationContext.xml

<?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="indi.jaywee.controller"/>

    <mvc:default-servlet-handler/>

    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>

    </bean>

</beans>

10.2.2、自定义拦截器

TestController

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/t1")
    public String test1() {
        String msg = "TestController test() has been executed";
        System.out.println(msg);
        
        return msg;
    }
}

MyInterceptor

实现 HandlerInterceptor 接口,重写方法

public class MyInterceptor1 implements HandlerInterceptor {

    /**
     * @return true表示放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1:预处理");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1:后置处理");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1:资源清理");
    }
}

配置

Spring 配置文件中配置拦截器。

  • / :拦截请求,不拦截页面;

  • /*:拦截文件夹,不包括子文件夹;

  • /**:拦截文件夹,包括子文件夹。

<mvc:interceptors>
    <mvc:interceptor>
        <!-- 要拦截的请求路径 -->
        <mvc:mapping path="/test/**"/>
        <!-- 拦截器 -->
        <bean class="indi.jaywee.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

10.3、HandlerInterceptor

  • SpringMVC 中的 Interceptor 是链式调用的;
  • 在一个应用或请求中可以同时存在多个拦截器,它们根据声明顺序(配置的先后顺序)依次执行。

preHandle ()

/**
 * 
 * @param request
 * @param response
 * @param handler
 * @return
 * @throws Exception
 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ...
}
  • 返回值类型:Boolean
    • true:放行。执行请求和 Interceptor 中的其它两个方法,调用下个 Interceptor
    • false :拦截请求。
  • 运行时间
    • 在请求处理之前,即在 Controller 方法调用之前运行;
    • 先声明的 InterceptorpreHandler 方法先执行。
  • 作用
    • 前置初始化操作或预处理请求,如获取 sessioncookie 判断是否登录;
    • 判断是否要执行请求,通过方法的返回值决定。

postHandle ()

/**
 * 
 * @param request
 * @param response
 * @param handler
 * @param modelAndView
 * @throws Exception
 */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    ...
}
  • 返回值类型:void
  • 运行时间
    • 在请求处理之后,即在 Controller 方法调用之后运行;
    • DispatcherServlet 进行视图渲染之前;
    • 后声明的 InterceptorpostHandle 方法先执行,与 preHandle 相反;
  • 作用:使用 ModelAndView 对象,如设置 sessioncookie

afterCompletion ()

/**
 * 
 * @param request
 * @param response
 * @param handler
 * @param ex
 * @throws Exception
 */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    ...
}
  • 返回值类型:void
  • 运行时间
    • 在整个请求处理结束之后,DispatcherServlet 渲染了视图渲染之后;
    • 后声明的 InterceptorafterCompletion 方法先执行,与 postHandle 相同,与 preHandle 相反;
  • 作用:资源清理。

10.4、拦截器执行顺序

先测试,再得出结论。

10.4.1、一个拦截器

对一组请求配置拦截器:A

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/test/**"/>
        <bean class="indi.jaywee.interceptor.MyInterceptorA"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 放行:预处理方法返回true

    SpringMVC:六、拦截器

  • 拦截:预处理方法返回 false

    SpringMVC:六、拦截器

10.4.2、多个拦截器

对同一组请求配置3个拦截器,声明顺序为 A、B、C。

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/test/**"/>
        <bean class="indi.jaywee.interceptor.MyInterceptorA"/>
    </mvc:interceptor>
    
    <mvc:interceptor>
        <mvc:mapping path="/test/**"/>
        <bean class="indi.jaywee.interceptor.MyInterceptorB"/>
    </mvc:interceptor>
    
    <mvc:interceptor>
        <mvc:mapping path="/test/**"/>
        <bean class="indi.jaywee.interceptor.MyInterceptorC"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 放行:三个预处理方法都返回 true

    SpringMVC:六、拦截器

  • A拦截:A的预处理方法返回 false

    SpringMVC:六、拦截器

  • A放行,B拦截:A的预处理方法返回 true,B的预处理方法返回 false

    SpringMVC:六、拦截器

  • AB放行,C拦截:AB的预处理方法返回 true,C的预处理方法返回 false

    SpringMVC:六、拦截器

10.4.3、结论

拦截器在不同情况的执行顺序如下:

一个拦截器

  • 放行
    1. preHandle
    2. 请求处理
    3. postHandle
    4. afterCompletion
  • 拦截:只执行 preHandle

多个拦截器

  • 全部放行

    1. 按拦截器声明的顺序,执行每个拦截器的 preHandle

    2. 请求处理

    3. 按拦截器声明的相反顺序,执行每个拦截器的 postHandle

    4. 按拦截器声明的相反顺序,执行每个拦截器的 afterCompletion

  • 部分放行:假设执行到拦截器demo时拦截请求

    1. 按拦截器声明的顺序,执行每个拦截器的 preHandle,直到被拦截(包括demopreHandle);
    2. 按拦截器声明的相反顺序,执行每个拦截器的 afterCompletion(不包括demoafterCompletion);
    3. 即:放行了的拦截器,会执行 preHandleafterCompletion;而未放行的拦截器,只会执行 preHandle

10.5、案例:登录拦截

10.5.1、环境搭建

Controller

UserController

  • 用户登录成功,添加 session
  • 用户注销,移除 session
@Controller
@RequestMapping("/user")
public class UserController {

    /**
     * 跳转到首页
     */
    @RequestMapping("/toHome")
    public String toHome() {
        return "home";
    }

    /**
     * 跳转到登录页
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

    /**
     * 处理用户登录
     */
    @RequestMapping("/login")
    public String login(String username, String password, HttpSession session) {

        // 判断用户名密码是否合法,此处省略

        // 登录成功,设置session
        session.setAttribute("userInfo", username);

        return "success";
    }

    /**
     * 处理用户注销
     */
    @RequestMapping("/logout")
    public String logout(HttpSession session) {
        
        String userInfoSession = "userInfo";
        
        if (session.getAttribute(userInfoSession) != null) {
            session.removeAttribute(userInfoSession);
        }

        return "login";
    }
}

页面

index.jsp

  • 由于页面放在 WEB-INF 下,无法直接通过地址栏访问;
  • 因此通过超链接请求访问。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<h3><a href="${pageContext.request.contextPath}/user/toHome">进入首页</a></h3>

<h3><a href="${pageContext.request.contextPath}/user/toLogin">进入登录页</a></h3>

</body>
</html>

home.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户首页</title>
</head>
<body>
<h1>用户首页</h1>

<span>这里是用户首页,用户登录才能访问</span>

</body>
</html>

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>

<form action="${pageContext.request.contextPath}/user/login" method="post">
    <label> 用户名<input type="text" name="username" required> </label>
    <br>
    <label> 密码<input type="password" name="password" required> </label>
    <br>
    <input type="submit">
</form>

</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h1>登录成功:${userInfo}</h1>

<h3><a href="${pageContext.request.contextPath}/user/toHome">进入首页</a></h3>
<h3><a href="${pageContext.request.contextPath}/user/logOut">注销</a></h3>

</body>
</html>

Controller 和基本的页面都写好了,此时所有的页面都可以直接访问。

  • 我们想要的效果是:
    • 用户登入成功,将用户信息存入 session,并跳转到成功页面;
    • 如果用户未登入,就无法进入用户首页;
  • 因此需要使用拦截器。

10.5.3、拦截器

LoginInterceptor

通过 session 判断用户是否已登录。

  • 用户登录:放行;
  • 用户未登录:重定向到登录页。
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userInfo = (String) request.getSession().getAttribute("userInfo");
        System.out.println("loginInfo = " + userInfo);

        if (userInfo != null) {
            System.out.println("用户已登录,放行");
            return true;
        } else {
            response.sendRedirect(request.getContextPath() + "/user/toLogin");
        }
        return false;
    }
}

配置

  • 需要配置不拦截地址,否则 /user 下的所有请求,包括登录也会被拦截;
  • 如果登录请求也拦截了,此时还没有登入,就不会存入 session,则一定会被拦截。
<mvc:interceptor>
    <mvc:mapping path="/user/**"/>
    
    <!-- 不拦截地址 -->
    <mvc:exclude-mapping path="/user/toLogin"/>
    <mvc:exclude-mapping path="/user/login"/>
    
    <bean class="indi.jaywee.interceptor.LoginInterceptor"/>
</mvc:interceptor>

再次测试

  • 用户登入成功,将用户信息存入 session,并跳转到成功页面;
  • 如果用户未登入,访问用户首页时会重定向到登录请求;

拦截器是 AOP 思想的一个体现,可以在不改变业务代码的情况下扩展业务。

SpringMVC:六、拦截器

上一篇:easy-rules-centraldogma-spring-boot-starter 使用说明


下一篇:go语言文件操作实例