拦截器入门

说明:

(1) 本篇博客合理性解释:

● 本篇博客介绍Spring MVC中一个高级组件:拦截器;

● 但是,本篇博客的主要目的只是【引入Interceptor拦截器】、【简单的走一遍拦截器的配置过程】;

有关拦截器的【实用技巧】和【具体细节】等深入的内容在后面会介绍;即,本篇博客仅仅是拦截器的一个入门;

(2) 本篇博客主要内容:

● 拦截器(Interceptor)简介;

● 拦截器(Interceptor)开发流程概述;

● 拦截器(Interceptor)开发流程走了一遍,演示了最初级的使用;

● 介绍了HandlerInterceptor接口的三个方法:preHandle()、postHandle()、afterCompletion();


一:拦截器简介;

说明:

(1) 拦截器的作用:拦截url,对其进行前置/后置过滤;

(2) 【Spring中的拦截器】和【J2EE中的过滤器】,其作用有点类似,都是拦截请求; 但是二者底层的实现逻辑是不同的;

● 拦截器(Interceptor)是Spring MVC的标准组件;(Interceptor对象被创建后,是天然的运行在IoC容器中的;)

● 过滤器(Filter)是J2EE的标准组件,是J2EE的标准;过滤器(Filter)的具体实现,是由不同的第三方容器厂商实现的;(比如,我们在【创建第一个Filter】中直接使用的过滤器,就是Tomcat这个第三方厂商,根据J2EE标准实现的;)

(3) 我们已经知道,拦截器(Interceptor)的作用是拦截url,对其进行前置/后置处理;

● 我们很容易就能联想到Spring中的【AOP面向切面编程】;

● 其实,拦截器(Interceptor)的底层最基本的实现,就是依赖于Spring AOP理念;(【拦截器(Interceptor)的具体实现】和【AOP中的环绕通知】很相似;)


二:拦截器(Interceptor)开发流程;

1. 拦截器(Interceptor)开发流程:简介;

说明:

(1) 拦截器(Interceptor)底层依赖于【最原始的servlet-api,也就是J2EE的javx.servlet】;所以,首先需要引入javax.servlet依赖;(至于为什么要引入Servlet依赖,在【附加:Java简介(JavaSE,Java EE,JDK等);【java.servlet.】和【javax.】简介;Eclipse和IDEA在使用servlet-api.jar时的区别;】和本篇博客的后续内容中会得到答案;)

(2) 新建一个类,然后这个类要实现HandlerInterceptor接口;(这个接口的完整地址是org.springframework.web.servlet.HandlerInterceptor,可以看到,这个接口是Spring定义的)

(3) 然后,在编好了拦截器类后,需要在applicationContext.xml中配置过滤地址;

2.开发前准备:准备一个【interceptor工程】;(这部分不是本篇博客的重点,快速扫过去就行)

(1)创建【interceptor】工程;

首先,创建一Maven WebApp项目;

这个过程可以参考【使用IDEA创建【maven + WebApp】项目】;啰嗦一下吧,其基本过程就是:

第一步:创建一Maven项目;

第二步:将工程设置为Web工程;

● 添加web功能;

● 三个设置项:【设置web.xml文件目录】、【设置web资源保存目录】、【设置artifact发布】;

第三步:给项目配置Tomcat;

● 配置一个本地的Tomcat服务器;

● 在Deployment中配置artifact发布;

● 设置一下Server中的端口号等内容;


然后,配置Spring MVC;

这个过程可以参考【Spring MVC环境配置】,其基本过程就是:

第一步:在pom.xml中引入依赖;

第二步:在web.xml中配置DispatcherServlet;

第三步:创建并编写applicationContext.xml;

第四步:将新引入依赖添加到Tomcat发布中去;


至此,一个Spring MVC项目就创建好了;


(2)【interceptor】工程,初始内容展示;

pom.xml:

<?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>interceptor</artifactId>
        <version>1.0-SNAPSHOT</version>
 
        <repositories>
            <repository>
                <id>aliyun</id>
                <name>aliyun</name>
                <url>https://maven.aliyun.com/repository/public</url>
            </repository>
        </repositories>
 
        <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>
        </dependencies>
 
    </project>

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_3_1.xsd"
             version="3.1">
 
        <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>0</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
 
        <filter>
            <filter-name>characterFilter</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>characterFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
 
        <filter>
            <filter-name>formContentFilter</filter-name>
            <filter-
class>org.springframework.web.filter.FormContentFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>formContentFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    </web-app>

Persons:

package com.imooc.restful.entity;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 
 import java.util.Date;
 
 public class Person {
     private String name;
     private Integer age;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date birthday;
 
     public Person() {
     }
 
     public Person(String name, Integer age, Date birthday) {
         this.name = name;
         this.age = age;
         this.birthday = birthday;
     }
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public Integer getAge() {
         return age;
     }
 
     public void setAge(Integer age) {
         this.age = age;
     }
 
     public Date getBirthday() {
         return birthday;
     }
 
     public void setBirthday(Date birthday) {
         this.birthday = birthday;
     }
 }

RestfulController:

package com.imooc.restful.controller;
 
 import com.imooc.restful.entity.Person;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
 @RestController
 @RequestMapping("/restful")
 public class RestfulController {
 
     @GetMapping("/persons")
     public List<Person findPersons() {
         List list = new ArrayList();
         Person person1 = new Person();
         person1.setName("zhang");
         person1.setAge(20);
         person1.setBirthday(new Date());
         Person person2 = new Person();
         person2.setName("wang");
         person2.setAge(21);
         person2.setBirthday(new Date());
         list.add(person1);
         list.add(person2);
         return list;
     }
 }

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mv="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
                http://www.springframework.org/schema/context/spring-
context.xsd
                http://www.springframework.org/schema/mvc
                http://www.springframework.org/schema/mvc/spring-mvc.xsd">
 
        <context:component-scan base-
package="com.imooc.restful"></context:component-scan>
 
        <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>
        <mvc:default-servlet-handler/>
 
        <mvc:cors>
            <mvc:mapping path="/restful/**"
                         allowed-
origins="http://localhost:8080,http://www.imooc.com"
                         max-age="3600"/>
        </mvc:cors>
 
    </beans>

client.html:

<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script type="text/javascript" src="jquery-3.5.1.js"></script>
        <script>
            $(function () {
                $("#btnGet").click(function () {
                    $.ajax({
                        "url" : "/restful/request",
                        "type" : "get",
                        "dataType" : "json",
                        "success" : function (json) {
                            $("#message").text(json.message);
                        },
                        "error":function () {
                            alert("mmmmmmm");
                        }
                    })
                })
            });
 
            $(function () {
                $("#btnPost").click(function () {
                    $.ajax({
                        "url" : "/restful/request/103",
                        "type" : "post",
                        "data": "name=lily&age=23",
                        "dataType" : "json",
                        "success" : function (json) {
                            $("#message").text(json.message + ":" +json.id);
                        },
                        "error":function () {
                            alert("mmmmmmm");
                        }
                    })
                })
            });
 
            $(function () {
                $("#btnPut").click(function () {
                    $.ajax({
                        "url" : "/restful/request",
                        "type" : "put",
                        "data": "name=lily&age=23",
                        "dataType" : "json",
                        "success" : function (json) {
                            $("#message").text(json.message);
                        },
                        "error":function () {
                            alert("mmmmmmm");
                        }
                    })
                })
            });
 
            $(function () {
                $("#btnDelete").click(function () {
                    $.ajax({
                        "url" : "/restful/request",
                        "type" : "Delete",
                        "dataType" : "json",
                        "success" : function (json) {
                            $("#message").text(json.message);
                        },
                        "error":function () {
                            alert("mmmmmmm");
                        }
                    })
                })
            });
 
            $(function () {
                $("#btnPersons").click(function () {
                    $.ajax({
                        "url" : "/restful/persons",
                        "type" : "get",
                        "dataType" : "json",
                        "success" : function (json) {
                            for (var i = 0; i < json.length; i++) {
                                var p = json[i];
                                $("#divPersons").append("<h2>" +p.name + ":"
+ p.age + ":"
                                    + p.birthday+ "</h2>")
                            }
                        }
                    })
                })
            });
        </script>
    </head>
    <body>
        <input type="button" id="btnGet" value="向服务器发送Get请求">
        <input type="button" id="btnPost" value="向服务器发送Post请求">
        <input type="button" id="btnPut" value="向服务器发送Put请求">
        <input type="button" id="btnDelete" value="向服务器发送Delete请求">
        <h1 id="message"></h1>
        <hr/>
        <input type="button" id="btnPersons" value="查询所有人员">
        <div id="divPersons"></div>
 
    </body>
    </html>

其实,【interceptor工程】就是照搬前面的【restful工程】;

3.开发拦截器(Interceptor);(重点)

(1)第一步:在pom.xml中引入servlet-api依赖;

<dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
             <version>3.1.0</version>
             <scope>provided</scope>
         </dependency>

说明:

(1) servlet依赖说明:

(2) 第一次遇到,基于IDEA,在工程中引入servlet api是在【OA系统前期准备:整合FreeMarker】;并且,当时也是把servlet的scope设置为了provided;

(3) 一个疑问:Eclipse和IDEA在开发的时候,如果需要用到HttpServletRequest或者HttpServletResponse时,一个不需要引入javax.servlet依赖,一个需要引入javax.servlet依赖,这个问题还未解决,等会解决后,再补充; :在【附加:Java简介(Java SE,JavaEE,JDK等);【java.servlet.】和【javax.】简介;Eclipse和IDEA在使用servlet-api.jar时的区别;】中有详细介绍;


(2)第二步:新建一个实现了HandlerInterceptor接口的类;

MyInterceptor类:

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 {
public boolean preHandle(HttpServletRequest request,
 
HttpServletResponse response, Object handler) throws Exception {
//获取当前的url地址,打印一下示意性的信息;
System.out.println(request.getRequestURL() +"请求还未进入Controller之前,执行preHandle()方法。");
//如果返回return true;那么请求就会被送达给【后面的拦截器(如果还有的话)】,或者送给Controller;
//如果返回return false;那么当前的请求就会被阻止,直接产生响应,返回给客户端;
//即,这个return的结果,决定了当前请求是继续向后传递执行,还是立即结束、返回响应;
return true;
}
public void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
System.out.println(request.getRequestURL() +
"Controller方法return以后,但还未产生响应文本之前,执行postHandle()方法。");
}
 
public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) throws Exception
{
System.out.println(request.getRequestURL() +"响应文本产生后,执行afterCompletion()方法。");
}
}

说明:

(1) HandlerInterceptor接口的完整地址是org.springframework.web.servlet.HandlerInterceptor,可以看到这个接口是Spring中定义的;

(2) HandlerInterceptor接口中的三个方法说明:这三个方法对应了三个不同的执行时机;

● preHandle():前置执行处理:意思是:一个请求产生后,在请求还未进Controller之前,先要执行preHandle()方法,对这个请求进行预置处理;

● postHandle():目标资源已被Spring MVC框架处理:意思是:【Controller的方法已经处理了请求,Controller方法return以后,但还未产生响应文本之前】,在这个时机执行postHandle()方法;

● afterCompletion():响应文本已经产生:意思是:产生了响应文本以后,afterCompletion()方法就会被自动执行;(如果Controller方法返回的是ModelAndView,那么数据和模板引擎混合后,会产生一个HTML片段,然后afterCompletion()方法就会被执行;;;;如果Controller方法返回的是一个需要被JSON序列化的对象,那么当jackson组件把这个对象序列化为JSON字符串后,afterCompletion()方法就会被执行)

● 这个三个方法在执行时,是按照时间顺序,有序执行的;

(3) preHandle()、postHandle()、afterCompletion()方法的参数,印证了为什么我们在第一步,要先引入servlet依赖;

(4) 代码内容说明:

强调一些preHandle()方法的返回值问题:

● 首先,一个前提,一个项目可能有多个拦截器,即同一个请求可能会被多个拦截器处理,然后再传给Controller;

● 如果preHandle()方法,返回true的话:那么请求就会被送给后面的拦截器(如果还有的话),或者送给Controller;

● 如果preHandle()方法,返回false的话:那么当前的请求就会被阻止,直接产生响应,返回给客户端;

● 即,这个return的结果,决定了当前请求是继续向后传递执行,还是立即结束、返回响应;


虽然,上面写了实现了HandlerInterceptor接口的MyInterceptor类;但是,Spring MVC还不认识这个类的;我们需要在applicationContext.xml中对其进行配置,就是第三步的内容;

(3)第三步:在applicationContext.xml中配置过滤地址;

<mvc:interceptors>
           <mvc:interceptor>
               <mvc:mapping path="/**"/>
               <bean class="com.imooc.restful.interceptor.MyInterceptor"/>
           </mvc:interceptor>
       </mvc:interceptors>

说明:

(1) 内容说明:主要配置两项:【拦截哪些url】,【使用哪个类来处理,拦截到的url】;

其中, 【path=”/**“】表示,拦截所有请求;

(2)一个 mvc:interceptor中,只能配置一个bean节点;


(4)启动Tomcat,观察效果;

可以看到, preHandle()、postHandle()、afterCompletion()这三个方法的执行顺序,符合预期;

拦截器使用技巧

说明:

(1) 本篇博客合理性解释:

● 在上篇中,已经展示了拦截器的基本使用流程,了解了【preHandle()、postHandle()、afterCompletion()方法的基本情况】;但是拦截器在实际时,还有很多问题需要解决:为此,写了本篇博客; (2)本篇博客主要内容:

● 一个话题:如何让拦截器不拦截某些url;由此引出了【<mvc:exclude-mapping path=""/】标签;

● 多拦截器时的执行顺序;

● 拦截器的perHandle()方法的return值;(如果return false,那么拦截器就相当于一个“阻断器”);


一:<mvc:exclude-mapping path=""/标签:设置【不拦截的url】;

1.先看一个客观情况:只要一个资源的url符合“拦截器的path”,那么访问该资源的时候,这个请求就会被拦截器处理;

对于【interceptor工程】,启动Tomcat,访问【localhost/client.html】;

看后台,会发现:.html,.js这些资源,拦截器都拦截了;(主要是.js这些静态资源也被拦截了)

即,上面的案例主要说明:

● 我们知道所有资源都有一个uri,自然其也有url(uri是url的一部分);

● 我们访问某个资源时候,只要该资源的url符合【拦截器的path属性】,那么【访问这个资源的url时,这个请求就会被拦截】;

但是,在绝大部分情况下,对于【.js,.ico,.jpg】等静态资源,我们并不希望拦截器对其进行处理;所以,当我们访问【.js,.ico,.jpg等静态资源】时,如何不拦截这些请求,就是接下里要介绍的内容;

2.通过【mvc:exclude-mapping path=""/】标签:设置【不拦截的url】

(在实际开发中,一般通过这个标签,设置不去处理【.js,ico,jpg】等静态资源;)

启动Tomcat,观察效果:

3.在实际开发中,一种很好的开发经验

对于静态资源:可以把静态资源放在特定的目录(如resources目录)中,来简化【<mvc:exclude-mapping path=""/的配置】;

可以看到,如果我们的资源都存放在webapp的根路径中时:

那么我们通过【mvc:exclude-mapping path=""/】的方式去设置时,每一类静态资源都需要配置,这工作量有点大,不太好;

但是,如果把所有静态资源存放在某个目录中,那么【mvc:exclude-mapping path=""/】的方式去设置时,就会方便很多;这也是我们在实际开中,常采用的一种策略;

4.一种反向思维:mvc:mapping path=“/restful/**”

/:只去过滤特定的地址;

这样以后,拦截器只会拦截以【/restful/】开头的url了;(其他url不会拦截,自然那些【.js,ico,jpg】等静态资源也不会拦截了;)


然后,如果有多少个需要拦截的url,在后面直接追加就行了;


上面在applicationContext.xml中配置过程虽然有点麻烦,但是有几点好处:

● applicationContext.xml是全局性的,我们在这儿配置后,整个项目都适用;

● 我们在applicationContext.xml中的【<mvc:mapping path=“/restful/**”/】配置,也可以看成是一种【规则和限制】;程序员在编写代码的时候,如果涉及到该模块,那么url就必须按照【restful】的规则去书写;这也能提高项目的规范程序;


二:多个拦截器;

1.多个拦截器的执行顺序;

(1)多个拦截执行顺序:简述;

说明:

(1) 【拦截器1】和【拦截器2】是根据在applicationContext.xml中的配置顺序来定的;

(2) 执行顺序:(前提是一切顺利,畅通无阻,都能执行的到)

● 请求过来的时候,请求还未到Controller的时候:会依次执行的【拦截器1的preHandle()方法,拦截器2的preHandle()方法】;

● 执行Controller逻辑代码;然后,Controller方法return;(但还未产生响应文本之前)

● 在响应文本未产生之前,反向依次执行【拦截器2的postHandle()方法,拦截器1的postHandle()方法】;

● 产生响应文本;

● 反向执行【拦截器2的afterCompletion()方法,拦截器1的afterCompletion()方法】;


(2)多个拦截器的执行顺序:演示;

首先,在创建一个拦截器类:MyInterceptor2类;

然后,配置下MyInterceptor2拦截器类;在这个配置中,MyInterceptor类配置在前(其就相当于拦截器1);MyInterceptor2类配置在前(其就相当于拦截器2);

然后,启动Tomcat;


三:拦截器中preHandle()方法的返回值:【return true】或【return false】;

我们已经知道:

● 如果preHandle()方法,返回true的话:那么请求就会被送给后面的拦截器(如果还有的话),或者送给Controller;

● 如果preHandle()方法,返回false的话:那么当前的请求就会被阻止,直接产生响应,返回给客户端;

● 即,这个return的结果,决定了当前请求是继续向后传递执行,还是立即结束、返回响应;

1.preHandle()方法:return false:相当于一个“阻断器”;

启动Tomcat,访问;

说明:

(1) 当拦截器的preHandler()方法,return false时:这个拦截器就相当于一个阻断器;

(2) 一旦拦截器的preHandler()方法,return false时:那么请求就会立即中断, 响应就直接在这个preHandler()方法中产生了;

2. 在实际开发中,可以利用【preHandler()方法的return值】:来决定【放行请求】还是【立即响应,中断请求】;

由此可见,利用拦截器的preHandle()方法的return返回值,可以做很多事情;比如,对于某个url请求,preHandle()方法可以对其进行前置检查,如果符合要求就【preHandle()方法就return true,放行请求】;如果不符合要求就【preHandle()方法就return false,返回响应,中断请求】;

开发用户流量拦截器

说明:

(1) 本篇博客合理性解释:

● 在上面两篇博客介绍了拦截器的基本使用;那么,本篇博客就通过一个案例,演示下在项目中如何使用拦截器;

文章逻辑: 当用户发送请求,访问我们的系统时 → 请求中包含很多信息 → 我们可以收集这些基础信息,以供利用 → 为此,拦截器就派上用场了→ 我们可以利用拦截器拦截用户请求,从而获取请求中的基础信息 → 然后,我们也可以把这些信息存储到具体日志文件中 →为此,就引出了利用logback存储日志了; (2) 本篇博客内容:

● 使用拦截器来采集用户基础数据;

● 也涉及到了【使用logback日志组件,把日志数据存储到日志文件中】


一: 用户流量信息简述;

(1) 用户流量信息:用户在访问我们的系统时,用户请求中包含如访问时间、访问网址(url)、使用的什么浏览器、使用的什么系统、使用的什么手机、用户IP等信息;

(2) 这些用户流量信息是比较重要的:比如电商网站: ● 我们根据访问网址(url),就能看出什么商品是最受欢迎的; ● 根据访问时间,就能推断出用户是夜猫子还是白天访问;(由此,针对不同的时间段,我们是否可以推荐不同的商品); ● 根据发送的请求中附带的用户环境信息,知道当前用户是使用PC机访问的还是使用手机访问的;用到手机是iOS还是Android;

(3) 在用户访问应用时,这些用户流量信息,我们可以通过拦截器来收集;

(4) 即,本篇博客主要内容是:如何使用拦截器来采集用户基础数据;同时,在其中也会介绍如何使用logback日志组件来存储日志数据;


二:预先准备:先创建一个【拦截用户请求,以获取流量信息】的拦截器类:AccessHistoryInterceptor类;

1.创建拦截器类:AccessHistoryInterceptor类;

初始准备,我们创建AccessHistoryInterceptor拦截器类,来完成采集用户基础数据的工作;

AccessHistoryInterceptor:

package com.imooc.restful.interceptor;
 
 import org.springframework.web.servlet.HandlerInterceptor;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
  * 访问历史拦截器;所有的请求都会被这个拦截器拦截,在请求被处理之前,把请求的相关信息记录到日志文件中;
  */
 public class AccessHistoryInterceptor implements HandlerInterceptor {
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
}

说明:

(1) 因为AccessHistoryInterceptor拦截器类的主要目的是:拦截用户请求,获取其中的基础信息;所以,这个方法只实现类preHandle()方法;


三:引入logback日志组件;

前面介绍过logback日志组件:如有需要可以依次参考;

●【MyBatis进阶一】:第一次引入SLF4日志门面、logback日志实现;(也知道了Mybatis框架中,可以很好的使用logback日志组件)

●【【logger.error()】介绍;(只是将日志打印在Console控制台)】:介绍了logger.error()方法的几种重构形式;

●【Spring编程式事务】:知道引入logback后,Spring框架就可以默认使用logback来输出日志;

1.在pom.xml中引入logback依赖;

然后,一个老生常谈的点:引入一个新依赖后,如有需要,记得把这个依赖添加到发布中去;

我们知道,引入logback依赖后,Spring框架就可以默认使用logback来输出日志;启动Tomcat,看下logback是否生效了;

2.创建并编写logback.xml配置文件

【定义appender输出器】;【把输出器作用到AccessHistoryInterceptor这个类上】;

(1)创建logback.xml文件,并通过基本配置,看下是否OK;

在logback配置文件logback.xml中,自定义日志的格式;

logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <configuration>
     <appender name="consoleheihei"
class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
             <pattern>[%thread] %d %level %logger{10} -%msg%n</pattern>
 <!--            <pattern>%d{HH:mm:ss:SSS} [%thread] %-5level %logger{36}
- %msg%n</pattern>-->
            </encoder>
        </appender>
        <root level="debug">
            <appender-ref ref="consoleheihei"></appender-ref>
        </root>
    </configuration>

说明:这些配置项的解释, 【MyBatis进阶一】中有详细介绍,这儿就不重复了;


(2)在logback.xml配置,把日志信息存储到本地日志文件中;(核心重点!!!)

日志打印在控制台中并不符合我们的需求,我们想要的是:AccessHistoryInterceptor这个类获取用户请求数据,然后再把这些数据转移到日志中去;然后,将这些日志信息存储在系统的某个日志文件中;只有把日志保存在一个日志文件中,才有助于我们后面分析嘛;

<?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <appender name="consoleheihei"
class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>[%thread] %d %level %logger{10} -%msg%n</pattern>
    <!--            <pattern>%d{HH:mm:ss:SSS} [%thread] %-5level %logger{36}
- %msg%n</pattern>-->
            </encoder>
        </appender>
        <appender name="accessHistoryLog"
    class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy
    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>e:/logs/history.%d.log</fileNamePattern>
            </rollingPolicy>
            <encoder>
                <pattern>[%thread] %d %level %logger{10} -%msg%n</pattern>
            </encoder>
        </appender>
        <root level="debug">
            <appender-ref ref="consoleheihei"></appender-ref>
        </root>
        <logger
    name="com.imooc.restful.interceptor.AccessHistoryInterceptor"
                level="INFO" additivity="false">
            <appender-ref ref="accessHistoryLog"/>
        </logger>
    </configuration>

说明:

(0) 在【MyBatis进阶一】中,我们第一次遇到了logback.xml的appender输出器配置,如有需要可以参考;

(1) 为了实现【使用logback,完成把AccessHistoryInterceptor这个类产生的日志,存储到某个日志文件中】;需要在logback.xml配置文件中,配置一个新的<appender输出器,同时通过<logger使其作用到AccessHistoryInterceptor这个类上;

(2) 配置内容分析;

至此,为了实现【使用logback,完成把AccessHistoryInterceptor这个类产生的日志,存储到某个日志文件中】这个目标;logback的底层日志配置就完成了;接下来就是,结合logback日志,利用拦截器拦截用户数据,并写入日志了;


四: AccessHistoryInterceptor拦截器类:【获取用户请求信息】,【将这些信息,存储到日志中去】;(核心重点!!!)

在AccessHistoryInterceptor拦截器类中,编写代码:拦截用户请求,获取请求中的信息,将这些信息添加到日志中去;

package com.imooc.restful.interceptor;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.servlet.HandlerInterceptor;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
     * 访问历史拦截器;所有的请求都会被这个拦截器拦截,在请求被处理之前,把请求的相关信息记录到日志文件中;
     */
    public class AccessHistoryInterceptor implements HandlerInterceptor {
        private Logger logger =
LoggerFactory.getLogger(AccessHistoryInterceptor.class);
 
        public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
            StringBuilder log = new StringBuilder();
            log.append(request.getRemoteAddr());//获取请求的ip地址,并追加到log字符串上;
            log.append("|");
            log.append(request.getRequestURL());//获取请求的url地址,并追加到log字符串上;
            log.append("|");
            log.append(request.getHeader("user-agent"));//获取请求中的“user-
agent”请求头,并追加到log字符串上;“user-agent”请求头是用户的客户端环境;
            //这儿我们只获取了【用户ip】,【请求url】,【user-
agent请求头】;这儿只是一个演示;以后在实际开发中,根据具体业务需求,增加即可;
            logger.info(log.toString());//对日志进行输入
            return true;
        }
    }

说明:

(1) Logger和LoggerFactory,是哪个包下的,别用错了:要用slf4j这个日志门面中的;(自然我们引入的logback提供了slf4j的具体实现)

(2) 因为日志这儿,涉及大量的字符串操作,由于String具有不可变性,为了减轻内存和垃圾回收的压力,这儿使用了StringBuilder;

(3) 这儿只是举了几个例子,在用户请求中,获取用户的一些基础信息;

其中的user-agent请求头,如果记不清了,可以参考下图;

(4) 因为我们在logback.xml中配置的日志最低级别是“INFO”,所以这儿我们使用Logger对象的info()方法来输出日志信息;

(5) 因为我们已经在logback.xml中配置了AccessHistoryInterceptor类和对应的<appender输出器;所以,此时AccessHistoryInterceptor类产生的日志信息,就可以存储到对应的日志文件;

拦截器类编写好了之后,但Spring MVC还不认识这个类,为此我们需要在applicationContext.xml中对其进行配置;


五:在applicationContext.xml中,配置AccessHistoryInterceptor这个拦截器;

在applicationContext.xml中配置AccessHistoryInterceptor这个拦截器;


六:最后,启动Tomcat,测试;

日志信息为:(因为AccessHistoryInterceptor拦截器,只拦截了resources目录下的静态资源,而我们的resources目录仅仅是个演示用的,没什么是实质作用,所以对弈js,ico等静态资源的也拦截到了)

[http-nio-80-exec-1] 2021-11-30 20:04:43,880 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/94.0.4606.61
Safari/537.36
[http-nio-80-exec-2] 2021-11-30 20:04:43,924 INFO
c.i.r.i.AccessHistoryInterceptor
-0:0:0:0:0:0:0:1|http://localhost/jquery-3.5.1.js|Mozilla/5.0 (Windows NT
10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61
Safari/537.36
[http-nio-80-exec-5] 2021-11-30 20:04:54,372 INFO
c.i.r.i.AccessHistoryInterceptor
-0:0:0:0:0:0:0:1|http://localhost/favicon.ico|Mozilla/5.0 (Windows NT 10.0;
Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61
Safari/537.36
[http-nio-80-exec-3] 2021-11-30 20:08:55,761 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/94.0.4606.61 Safari/537.36

在实际的业务中,这些请求中的基础信息,目前看似无用;其实,其中蕴含着很多有用的商机等信息;

以前自己做项目的时候,也做过这样的业务:从日志信息中提取出特定的信息,以分析数据,获取更有效的信息;


注:以后遇到一些,需要【对url进行批量处理的】场景,那么很可能就能用上拦截器;

Spring MVC处理流程

说明:

(1) 本篇博客合理性解释:前面介绍了Spring MVC的基本内容,包括【Spring MVC入门与数据绑定】、【Restful开发风格】、【拦截器(Interceptor)】;那么本篇博客就来总结下Spring MVC的底层原理和数据处理流程;

(2) 本篇博客参考的博客有:【SpringMVC 工作原理】,该文的作者是【aFa攻防实验室】;


一:Spring MVC处理流程;

1.Spring MVC处理流程:分析;

说明:

(1) 用户在客户端浏览器上,通过某个请求路径,向web应用服务器发送了一个request请求;在Spring MVC中,这个请求会被DispatcherServlet这个中央处理器(有时也称前端控制器)拦截并处理;

(2) DispatcherServlet(中央处理器)会请求HandlerMapper(处理器映射器)去查找Handler;(可以根据注解或者XML配置去查找)

● Handler:叫做处理器,也可称作句柄;

● Handler:在这儿就是:就是指拦截器或者Controller;

(3) HandlerMapper(处理器映射器)的主要作用是:通过访问的url,得到对应的执行链条;然后,把执行链返回给DispatcherServlet(中央处理器);

● 说白了HandlerMapper(处理器映射器)的主要作用就是:得到请求的url后,获取【从前到后,依次有哪些拦截器、Controller来进行,请求的处理】;

● HandlerMapper(处理器映射器)只会获取执行链,并不会真正的去处理请求;

(4) DispatcherServlet(中央处理器)得到HandlerMapper(处理器映射器)返回的执行链后,DispatcherServlet(中央处理器)会向HandlerAdapter(处理器适配器)发起执行Handler的请求;

(5) HandlerAdapter(处理器适配器)根据Handler的类型(Handler可能是拦截器,也可能是控制器)的不同,去选择执行不同的方法;

● 如果Handler是一个拦截器(Interceptor),那么就会选择去执行拦截器中的preHandle()前置处理方法;

● 如果Handler是一个Controller,那么就去会选择去执行Controller中的对应方法;

(6) 请求被Handler处理完以后,Handler会返回给HandlerAdapter(处理器适配器)一个ModelAndView对象(按最常见的返回类型ModelAndView举例了);返回的ModelAndView对象会被HandlerAdapter(处理器适配器)接收;

● ModelAndView对象是Spring MVC底层对象,包括Model数据模型和View视图信息;

(7) HandlerAdapter(处理器适配器)接收到Handler返回的ModelAndView对象后,会将其返回给DispatcherServlet(中央处理器);

(8) DispatcherServlet(中央处理器)接收到ModelAndView对象后,会判断当前的ModelAndView对象该由什么模板引擎处理(FreeMarker或JSP);比如,此时DispatcherServlet(中央处理器)根据ModelAndView对象中View视图的信息,分析后发现我们使用了FreeMarker作为模板引擎,那么DispatcherServlet(中央处理器)就会去选择与之对应的FreeMarker的ViewResolver(视图解析器),来进行View视图对象的创建;

(9) ViewResolver(视图解析器)处理完之后,会把处理好的View视图对象返回给DispatcherServlet(中央处理器);

(10) DispatcherServlet(中央处理器)得到ViewResolver(视图解析器)返回的View视图对象后;DispatcherServlet(中央处理器)就会把ModelAndView对象的Model中的模板数据渲染到View视图中去;得到最终的View视图(也就是最终的HTML);

(11) DispatcherServlet(中央处理器)把最终得到的HTML放入响应,返回给客户端浏览器;浏览器对其进行解释,我们就看到了最终的展现结果;

2. Spring MVC处理流程:关键组件;

Spring MVC处理流程中,有六大关键组件:DispatcherServlet(中央处理器),HandlerMapper(处理器映射器),HandlerAdapter(处理器适配器),Handler(处理器),ViewResolver(视图解析器),View(视图);


以下内容参考自【SpringMVC 工作原理】;

DispatcherServlet(中央处理器): 是Spring MVC的核心,用来接收用户请求,反馈用户结果。相当于转发器或中央处理器,控制着整个流程的运行,对各个组件进行调度,降低组件之间的耦合性,并且有利于组件之间的扩展;

HandlerMapper(处理器映射器): 作用是根据请求的 url 路径,通过注解或者是 xml 配置的方式,去寻找匹配的处理器Handler 信息;

HandlerAdapter(处理器适配器): 根据Handler的类型,去选择执行不同的、对应的Handler中的方法;

Handler(处理器): 拦截器或者Controller的统称;作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装到 ModelAndView 对象中;

ViewResolver(视图解析器): 作用是进行视图解析操作,将逻辑视图名解析成真正的视图View;将处理结果生成View视图。ViewResolver(视图解析器)结合(ModelAndView对象中的)Model和View,来渲染视图;

View(视图): 就是不同的 View 类型,例如JSP,FreeMarker等。其作者用是根据View视图和Model数据,得到最终的View视图(就是HTML);

3. Spring MVC处理流程:总结;

在实际开发中,需要我们程序员自己开发的就是Handler(处理器)和View(视图);即使如此,了解Spring MVC的整个工作流程和六大组件也是很必要的,这有助于日后的学习和理解;