介绍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底层的执行原理、数据的处理流程,从而了解程序运行背后到底做了哪些事情。
关于执行链:
Comments | NOTHING