拦截器入门
说明:
(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:
web.xml:
Persons:
RestfulController:
applicationContext.xml:
client.html:
其实,【interceptor工程】就是照搬前面的【restful工程】;
3.开发拦截器(Interceptor);(重点)
(1)第一步:在pom.xml中引入servlet-api依赖;
说明:
(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类:
说明:
(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中配置过滤地址;
说明:
(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:
说明:
(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:
说明:这些配置项的解释, 【MyBatis进阶一】中有详细介绍,这儿就不重复了;
(2)在logback.xml配置,把日志信息存储到本地日志文件中;(核心重点!!!)
日志打印在控制台中并不符合我们的需求,我们想要的是:AccessHistoryInterceptor这个类获取用户请求数据,然后再把这些数据转移到日志中去;然后,将这些日志信息存储在系统的某个日志文件中;只有把日志保存在一个日志文件中,才有助于我们后面分析嘛;
说明:
(0) 在【MyBatis进阶一】中,我们第一次遇到了logback.xml的appender输出器配置,如有需要可以参考;
(1) 为了实现【使用logback,完成把AccessHistoryInterceptor这个类产生的日志,存储到某个日志文件中】;需要在logback.xml配置文件中,配置一个新的<appender输出器,同时通过<logger使其作用到AccessHistoryInterceptor这个类上;
(2) 配置内容分析;
至此,为了实现【使用logback,完成把AccessHistoryInterceptor这个类产生的日志,存储到某个日志文件中】这个目标;logback的底层日志配置就完成了;接下来就是,结合logback日志,利用拦截器拦截用户数据,并写入日志了;
四: AccessHistoryInterceptor拦截器类:【获取用户请求信息】,【将这些信息,存储到日志中去】;(核心重点!!!)
在AccessHistoryInterceptor拦截器类中,编写代码:拦截用户请求,获取请求中的信息,将这些信息添加到日志中去;
说明:
(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等静态资源的也拦截到了)
在实际的业务中,这些请求中的基础信息,目前看似无用;其实,其中蕴含着很多有用的商机等信息;
以前自己做项目的时候,也做过这样的业务:从日志信息中提取出特定的信息,以分析数据,获取更有效的信息;
注:以后遇到一些,需要【对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的整个工作流程和六大组件也是很必要的,这有助于日后的学习和理解;