23.3 Spring MVC的拦截器



介绍Spring MVC的一种高级组件:拦截器Interceptor。

一、拦截器Interceptor入门

1.1 定义

1.2 开发流程:3步

其中:关于第二步HandlerInterceptor接口的三个方法的执行时机:

其中:

  • 因为上述三个方法,都要使用原生的request、response对象,所以要引入第一步的servlet-api依赖。
  • 对于前置处理方法,return true:请求被送给后面。return false:请求被阻止,直接产生响应,返回客户端了。即,请求是向后继续执行,还是请求立即结束,返回响应。

1.3 示例

以工程interceptor为例,演示拦截器的开发。

最终的Tomcat的运行环境,是自带servlet-api这个依赖jar包的。所以,范围设定为provided,含义是:只在开发、编译阶段才会引用,在最终使用阶段,会被排除在外。

1.在工程的配置文件pom.xml中,增加servlet-api依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>restful</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>


    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.9</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.9</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.9</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>        // 增加servlet-api依赖
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

</project>

2.在新建类MyInterceptor中:必须继承HandlerInterceptor接口

package com.imooc.restful.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class MyInterceptor implements HandlerInterceptor {      // 继承HandlerInterceptor接口

    @Override            // 请求前
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getRequestURL()+"-准备执行");
        return true;
    }

    @Override            // 请求后
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(request.getRequestURL()+"-目标处理成功");
    }

    @Override            // 产生响应内容后
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(request.getRequestURL()+"-响应内容已产生");
    }
}

handler

a person who trains or manages another person.

=manager,controller

3.配置拦截器

让Spring MVC认识上述的Java类和三个方法:

第五条

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <!--1.因为Spring IoC 的实例化创建对象、属性注入,均是采用注解形式-->
    <context:component-scan base-package="com.imooc.restful"/>

    <!--2.开启Spring MVC的注解模式。同时,额外创建了消息转换器:响应输出到浏览器上,解决其中文乱码问题-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--3.将静态资源排除在外-->
    <mvc:default-servlet-handler/>
    
    <!--4.安全机制:允许跨域访问-->
    <mvc:cors>
        <mvc:mapping path="/restful/**"
                     allowed-origins="http://localhost:8080,http://www.imooc.com"
                     max-age="3600"/>
    </mvc:cors>

    <!--5.配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>            // 拦截谁?
            <bean class="com.imooc.restful.interceptor.MyInterceptor"/>            // 谁拦截?
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

其中:

  • 区别:/** 表示所有的文件夹及里面的子文件夹;/* 表示所有的文件夹,不包含子文件夹

4.结果

在类RestfulController中:

@RestController                    
@RequestMapping("/restful")
public class RestfulController {

    @GetMapping("/persons")
    public List<Person> findPersons(){
        List list = new ArrayList();

        Person p1 = new Person();
        p1.setName("Lily");
        p1.setAge(23);
        p1.setBirthday(new Date());

        Person p2 = new Person();
        p2.setName("Smith");
        p2.setAge(22);
        p2.setBirthday(new Date());

        list.add(p1);
        list.add(p2);
        System.out.println("RestfulController.findPersons() - return list");
        return list;
    }

}

浏览器:

成功拦截

二、拦截器Interceptor的使用细则

上节:开发了一个自定义的拦截器。

2.1 排除静态资源、拦截指定资源

1.如何将不需要拦截的静态资源,排除在外?

由于静态资源种类实在是太多,比如.jpg.gif.js.css.ico等,甚至还有字体、文本对象等,不可能一一按照扩展名排除。如下:

因为规范的写法:所有的静态资源都会放在根路径下面的resources目录下,所以它们都有一个统一的前缀:

2.如何将需要拦截的资源,圈定?

2.2 多个拦截器的执行顺序

如果一个请求,被多个拦截器同时拦截,执行顺序?

小试验。

1.

新建第二个新的拦截器MyInterceptor1类:

public class MyInterceptor1 implements HandlerInterceptor {      // 继承HandlerInterceptor接口

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getRequestURL()+"-准备执行-1");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(request.getRequestURL()+"-目标处理成功-1");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(request.getRequestURL()+"-响应内容已产生-1");
    }
}

浏览器:

结果

2.

之所以会出现上述结果,是因为这涉及请求、响应的流向问题:

前置:按照拦截器配置的先后顺序,依次进行

后置:刚好反过来

2.3 前置处理的开关

1.返回值是false:

后面原本执行的所有处理,都被忽略了:

请求立即结束,返回响应

2.4 Interceptor与Filter的区别

https://class.imooc.com/lesson/2273#mid=52950

三、案例:开发统计用户流量的拦截器

需求:

  • 自动对用户的基础信息进行收集,比如用户访问的时间、手机系统、浏览器类型等等。

​ 比如:电商网站,可以分析什么商品是最受用户欢迎的 。

  • 使用logback日志组件,对用户信息进行日志存储

3.1 1.引入日志组件logback的依赖

基于上节的工程,进行开发。

1.

新建拦截器AccessHistoryInterceptor类:

所有的请求,都会被这个拦截器拦截,并记录到日志文件中。

public class AccessHistoryInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
}

2.

在工程的配置文件pom.xml中:

引入日志组件logback的依赖

同时,在Tomcat发布时,将新的依赖手动放到发布目录中:

启动Tomcat,检查日志组件logback是否生效:

生效,并且最低的输出级别为DEBUG

3. 2 配置定义追加器,向控制台书写日志

上述黑字是日志组件logback产生的系统级别的日志,这是不够的。还需要用户访问的数据。

对日志组件logback的配置文件进行设置:

在自己的拦截器中,对用户访问的数据进行登记.

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--1.定义一个向控制台书写日志的追加器-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!--2.追加器的输出级别是:debug-->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

追加器能成功的向控制台书写日志

3. 3 配置产生日志文件

上述,日志已经打印在了控制台中。

但是,希望自己的拦截器产生的日志,落地到系统的某个日志文件中。因为只有保存在文件中,未来才可以对其进行解析、分析。

再次对日志组件logback的配置文件进行设置:

为产生日志文件

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

    <!--1.定义一个向控制台书写日志的追加器-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--2.追加器的输出级别是:debug-->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>


    <!--3.生成按天滚动的日志文件-->
    <appender name="accessHistoryLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>e:/code/logs/history.%d.log</fileNamePattern>   <!-- %d:是指当前日期 -->
        </rollingPolicy>
        <encoder>
            <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern>
        </encoder>
    </appender>


    <!--4.该拦截器AccessHistoryInterceptor,使用输出器accessHistoryLog对文件进行输出-->
    <logger name="com.imooc.restful.interceptor.AccessHistoryInterceptor" level="INFO" additivity="false">
        <appender-ref ref="accessHistoryLog"/>
    </logger>

</configuration>

3.4 编写拦截器AccessHistoryInterceptor

在拦截器AccessHistoryInterceptor类中:

在前置处理的方法中,将用户访问的基础信息,放进日志对象中

public class AccessHistoryInterceptor implements HandlerInterceptor {
    
    // 创建一个日志对象
    private Logger logger = LoggerFactory.getLogger(AccessHistoryInterceptor.class);   
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        StringBuffer log = new StringBuffer();
        log.append(request.getRemoteAddr());        // 将用户的ip地址,进行追加
        log.append("|");
        log.append(request.getRequestURL());        // 将用户访问的URL网址,进行追加
        log.append("|");
        log.append(request.getHeader("user-agent"));        // 用户的客户端环境,保存在user-agent请求头中,进行追加

        logger.info(log.toString());
        return true;
    }
    
}

在Spring 的配置文件applicationContext.xml中:

对上述拦截器AccessHistoryInterceptor进行配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <!--1.因为Spring IoC 的实例化创建对象、属性注入,均是采用注解形式-->
    <context:component-scan base-package="com.imooc.restful"/>

    <!--2.开启Spring MVC的注解模式。同时,额外创建了消息转换器:响应输出到浏览器上,解决其中文乱码问题-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--3.将静态资源排除在外-->
    <mvc:default-servlet-handler/>

    <!--4.安全机制:允许跨域访问-->
    <mvc:cors>
        <mvc:mapping path="/restful/**"
                     allowed-origins="http://localhost:8080,http://www.imooc.com"
                     max-age="3600"/>
    </mvc:cors>


    <!--5.配置拦截器优先于3-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/restful/**"/>
            <mvc:mapping path="/webapp/**"/>
            <mvc:exclude-mapping path="/resources/**"/>
            <bean class="com.imooc.restful.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

    <!--6.配置拦截器AccessHistoryInterceptor-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>                                // 拦截范围?
            <mvc:exclude-mapping path="/resources/**"/>
            <bean class="com.imooc.restful.interceptor.AccessHistoryInterceptor"/>        // 谁拦截?
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

查看底层的日志文件,是否保存了用户的访问信息:

成功保存。

具体如下:

[http-nio-80-exec-1] 2022-11-25 19:23:21,119 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/client.html|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
[http-nio-80-exec-1] 2022-11-25 19:25:16,002 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/client.html|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
[http-nio-80-exec-2] 2022-11-25 19:25:19,312 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/persons|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
[http-nio-80-exec-4] 2022-11-25 19:25:39,283 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/request|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
[http-nio-80-exec-5] 2022-11-25 19:25:43,692 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/request/100|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

综上所述,成功开发了统计用户流量的拦截器,并且使用logback日志组件,对用户信息进行日志存储。

四、Spring MVC处理流程

介绍Spring MVC底层的执行原理、数据的处理流程,从而了解程序运行背后到底做了哪些事情。

关于执行链:

声明:Jerry's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 23.3 Spring MVC的拦截器


Follow excellence, and success will chase you.