过滤器和拦截器的区别
Spring开发中我们会遇到Filter过滤器与Interceptor拦截器的使用,他们都能对一些请求做一下预处理,但他们之间还是有很大的不同的:
- 拦截器是基于Java的反射机制的,而过滤器是基于函数回调。
- Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
- Filter在只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器)
- 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
应用场景
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
- 拦截器能深入方法的前后执行过程,可以进行统计方法执行时间来判断性能问题
过滤器
过滤器接口
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
Filter是javax.servlet中的接口,是有生命周期的,存在初始化,执行以及销毁的过程。
过滤器的实现
下面来看一下我写的一个权限检验的demo
public class AuthCheckFilter implements Filter {
//@Resource //不生效
private UserService userService;
//Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,
//web 服务器将创建Filter的实例对象,并调用其init方法进行初始化,
//容器只有在实例化过滤器时才会调用该方法一次。
//容器为这个方法传递一个FilterConfig对象,其中包含与Filter相关的配置信息
public void init(FilterConfig filterConfig) throws ServletException {
//获取容器上下文,可以进行对Bean的操作
ServletContext context = filterConfig.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
userService = (UserServiceImpl)ctx.getBean(UserServiceImpl.class);
}
//每次filter进行拦截都会执行。需要注意的是过滤器的一个实例可以同时服务于多个请求,
// 特别需要注意线程同步问题,尽量不用或少用实例变量。
// 在过滤器的doFilter()方法实现中,任何出现在FilterChain的doFilter方法之前地方,
//request是可用的;在doFilter()方法之后response是可用的。
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String user_token = request.getHeader("user_token");
String methodString=request.getMethod();
//这个判断只是简单的说明,通过init方法我们可以获取bean对象来进行验证操作
if(!userService.findOne(user_token)){
response.setContentType("application/json; charset=utf-8");
String jsonString="{\"status\":\"false\",\"info\":\"用户未登陆或超时\"}";
OutputStream os = response.getOutputStream();
os.write(jsonString.getBytes());
os.close();
}
//这里是个简单的demo ,具体逻辑需要时再处理
if(StringUtils.isEmpty(user_token)){
response.setContentType("application/json; charset=utf-8");
String jsonString="{\"status\":\"false\",\"info\":\"用户未登陆或超时\"}";
OutputStream os = response.getOutputStream();
os.write(jsonString.getBytes());
os.close();
}
filterChain.doFilter(servletRequest, servletResponse);
}
//在Web容器卸载 Filter 对象之前被调用
//如果过滤器使用了其他资源,需要在这个方法中释放这些资源。
public void destroy() {
}
}
在上面代码的初始化方法init()中,我添加了获取Spring上下文以及bean对象的方法,这样我们就可以有更大的灵活性来做处理。
Filter的配置是在web.xml中添加的
<filter>
<filter-name>authFilter</filter-name>
<filter-class>app.filter.AuthCheckFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/user/*</url-pattern>
</filter-mapping>
<!--<filter-mapping>标记是有先后顺序的,它的声明顺序说明容器是如何形成过滤器链的-->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-filter
拦截器
拦截器接口
public interface HandlerInterceptor {
//预处理回调方法,实现处理器的预处理(如登录检查)
//第三个参数为响应的处理器,拦截的controller对象
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
//后处理回调方法,实现处理器的后处理,但在渲染视图之前
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
//整个请求处理完毕回调方法,即在视图渲染完毕时回调
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
拦截适配器
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
@Override
public void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
}
}
这是HandlerInterceptor的适配器,简单的实现了HandlerInterceptor(此处所以三个回调方法都是空实现,preHandle返回true。),我们这样就不必要实现HandlerInterceptor的全部方法,我们通过HandlerInterceptorAdapter来实现自己需要的方法。
运行流程:
注意:异常流程中,比如是HandlerInterceptor2中断的流程(preHandle返回false),此处仅调用它之前拦截器的preHandle返回true的afterCompletion方法。
拦截器配置
<mvc:annotation-driven />
<context:component-scan base-package="app.controller"/>
<context:component-scan base-package="app.service"/>
<!--拦截器配置-->
<mvc:interceptors>
<!-- 日志拦截器,全局拦截 -->
<bean class="app.interceptor.LogInterceptor"/>
<!--配置拦截器, 多个拦截器,顺序执行 -->
<!--权限校验,排除部分请求-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/**"/>
<!--排除部分请求,不被拦截(下面拦截的请求都是swagger的请求)-->
<mvc:exclude-mapping path="/swagger-ui.html*"/>
<mvc:exclude-mapping path="/webjars/**"/>
<mvc:exclude-mapping path="/configuration/ui/**"/>
<mvc:exclude-mapping path="/swagger-resources/**"/>
<mvc:exclude-mapping path="/v2/**"/>
<bean class="app.interceptor.AuthCheckInterceptor" />
</mvc:interceptor>
<!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
</mvc:interceptors>
拦截器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-interceptor
拦截器的深入学习可以看Spring DispatcherServlet