1 过滤器和拦截器的异同
1.1 一张图表明两者之间的差异:
tomcat,filter,servet,interceptor以及controller等各种容器的关系图
1.2 两者的区别:
-
拦截器是基于java的反射机制的,而过滤器是基于函数回调。
-
拦截器不依赖与servlet容是依赖于spring容器,过滤器依赖与servlet容器。
-
拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用,可以限制用户对图片,文件以及其他资源的访问。
-
拦截器可以访问action请求上下文、值栈里的对象,而过滤器不能访问。
-
在action的生命周期中,拦截器可以多次被调用(因为拦截器是基于反射机制实现的,即通过加载java字节码而实现的函数的调用,所以每一次运行拦截器的时候都需要找到拦截器相关的字节码并且执行),而过滤器只需在容器初始化时被调用一次(意思是只需要调用一次,filter过滤器程序就会一直在内存中执行下去,每当碰到一次请求,服务器无需再次调用都会进行拦截,除非被主动摧毁。(1)启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;(2)、每一次请求时都只调用方法doFilter()进行处理;(3)、停止服务器时调用destroy()方法,销毁实例。)。
-
拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
1.3 两者的相同之处:
本文主要对基于Spring boot对过滤器和拦截器的配置进行的讲解。无论是过滤器还是拦截器都属于AOP(面向切面编程)思想的具体实现。
2 过滤器
2.1 用法
当Web容器接受到一个对资源的请求时:
- 它将判断是否有过滤器与这个资源相关联。
- 如果有,那么容器将把请求交给过滤器进行处理。在过滤器中,你可以改变请求的内容,或者重新设置请求的报头信息,然后再将请求发送给目标资源
。 - 当目标资源对请求作出响应时候,容器同样会将响应先转发给相应的过滤器。
2.2 作用
在过滤器中你可以对响应的内容进行转换,然后再将响应发送到客户端。常见的过滤器用途
主要包括:
- 对用户请求进行统一认证
- 对用户的访问请求进行记录和审核、
- 对用户发送的文本数据进行过滤或替换,比如替换掉脏话、
- 转换图象格式、
- 对响应内容进行压缩以减少传输量、
- 对请求或响应进行加解密处理、
- 触发资源访问事件、
- 对XML的输出应用XSLT等。
2.3 实现过滤器 方法一:不使用@WebFilter下,既需要编写过滤器类,又要编写配置类
2.3.1 先编写过滤器类
过滤器类就是指 当拦截到某一个请求后,在该请求发往servlet容器前,执行什么操作,需要进行什么处理
public class LogCostFilter implements Filter{
@Override
public void init(FilterConfig filterConfig)throws ServletException{
}
@Override
public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throws IOException,ServletException{
long start =System.currentTimeMillis();
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("Execute cost="+(System.currentTimeMillis()-start));
}
@Override
public void destroy(){
}
}
这段代码的逻辑比较简单,就是在方法执行前先记录时间戳,然后通过过滤器链完成请求的执行,在返回结果之间计算执行的时间。这里需要主要,这个类必须继承Filter类,这个是Servlet的规范,这个跟以前的Web项目没区别。
2.3.2 再在其配置类中配置过滤器需要拦截的请求类型的正则表达式以及决定使用哪一个过滤器类
但是,有了过滤器类以后,以前的web项目可以在web.xml中进行配置,但是spring- boot项目并没有web.xml这个文件,那怎么配置?在Spring boot中,我们需要FilterRegistrationBean来完成配置。其实现过程如下:
@Configuration
public class FilterConfig{
@Bean
public FilterRegistrationBean registFilter(){
FilterRegistrationBean registration =new FilterRegistrationBean();
registration.setFilter(newLogCostFilter());
registration.addUrlPatterns("/*");
registration.setName("LogCostFilter");
registration.setOrder(1);
return registration;
}
}
这样配置就完成了,需要配置的选项主要包括实例化Filter类,然后指定url的匹配模式,设置过滤器名称和执行顺序,这个过程和在web.xml中配置其实没什么区别,只是形式不同而已。现在我们可以启动服务器访问任意URL:
2.4 过滤器实现代码 方法二:仅仅使用@WebFilter和过滤器类,不要编写配置类
大家可以看到上面的配置已经生效了。除了通过 FilterRegistrationBean 来配置以外,还有一种更直接的办法,
2.4.1 直接通过注解@WebFilter就可以完成了:
@WebFilter(urlPatterns ="/*", filterName ="logFilter2")
public class LogCostFilter2 implements Filter{
@Override
publicvoid init(FilterConfig filterConfig)throwsServletException{
}
@Override
public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException{
long start =System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("LogFilter2 Execute cost="+(System.currentTimeMillis()- start));
}
@Override
public void destroy(){
}
}
2.4.2
这里直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外,我们还需在配置类中加另外一个注解:@ServletComponetScan,指定扫描的包。
@SpringBootApplication
@MapperScan("com.pandy.blog.dao")
@ServletComponentScan("com.pandy.blog.filters")
public class springbootApplication{
public static void main(String[] args) throws Exception{
SpringApplication.run(Application.class, args);
}
}
现在,我们再来访问一下任意URL:
2.4.3 多个拦截器存在时候的执行顺序问题
可以看到,我们配置的两个过滤器都生效了。细心的读者会发现,第二个Filter我们并没有指定执行的顺序,但是却在第一个Filter之前执行。这里需要解释一下,@WebFilter这个注解并没有指定执行顺序的属性,其执行顺序依赖于Filter的名称,是根据Filter类名(注意不是配置的filter的名字)的字母顺序倒序排列,并且@WebFilter指定的过滤器优先级都高于FilterRegistrationBean配置的过滤器。有兴趣的朋友可以自己实验一下。
3 拦截器
3.1 拦截器定义
3.1.1 讲一讲拦截器实现的机制。
在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加上某些操作。拦截是AOP的一种实现策略。
3.1.2 拦截器作用有什么作用呢?
AOP面向切面有什么作用,那么拦截器就有什么作用。
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV…
- 权限检查:认证或者授权等检查性能
- 监控:通过拦截器在进入处理器之前记录开始时间,处理完成后记录结束时间,得到请求处理时间。
- 通用行为:读取cookie得到用户信息并将用户对象放入请求头中,从而方便后续流程使用。
3.1.3 拦截器实现拦截器集成接口HandlerInterceptor,实现拦截,接口方法有下面三种:
-
preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法将在请求处理之前进行调用。SpringMVC中的Interceptor同Filter一样都是链式调用。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
-
postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。
-
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。总结一点就是:preHandle是请求执行前执行postHandle是请求结束执行afterCompletion是视图渲染完成后执行
3.2 拦截器实现
3.2.1
上面我们已经介绍了过滤器的配置方法,接下来我们再来看看如何配置一个拦截器。我们使用拦截器来实现上面同样的功能,记录请求的执行时间。首先我们实现拦截器类:
public class LogCostInterceptor implements HandlerInterceptor{
long start =System.currentTimeMillis();
@Override
publicboolean preHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o) throws Exception{
start =System.currentTimeMillis();
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o,ModelAndView modelAndView) throws Exception{
System.out.println("Interceptor cost="+(System.currentTimeMillis()-start));
}
@Override
publicvoid afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o,Exception e)throwsException{
}
}
3.3.2
这里我们需要实现HandlerInterceptor这个接口,这个接口包括三个方法,preHandle是请求执行前执行的,postHandler是请求结束执行的,但只有preHandle方法返回true的时候才会执行,afterCompletion是视图渲染完成后才执行,同样需要preHandle返回true,该方法通常用于清理资源等工作。除了实现上面的接口外,我们还需对其进行配置:
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter{
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(newLogCostInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
这里我们继承了WebMVCConfigurerAdapter,看过前面的文章的朋友应该已经见过这个类了,在进行静态资源目录配置的时候我们用到过这个类。这里我们重写了addInterceptors这个方法,进行拦截器的配置,主要配置项就两个,一个是指定拦截器,第二个是指定拦截的URL。现在我们再启动系统访问任意一个URL:
可以看到,我们通过拦截器实现了同样的功能。不过这里还要说明一点的是,其实这个实现是有问题的,因为preHandle和postHandle是两个方法,所以我们这里不得不设置一个共享变量start来存储开始值,但是这样就会存在线程安全问题。当然,我们可以通过其他方法来解决,比如通过ThreadLocal就可以很好的解决这个问题,有兴趣的同学可以自己实现。不过通过这一点我们其实可以看到,虽然拦截器在很多场景下优于过滤器,但是在这种场景下,过滤器比拦截器实现起来更简单。