[面试]HandlerInterceptors vs. Filters in Spring MVC
一. Filters
过滤器是web服务器的一部分,而不是Spring框架的一部分。对于传入的请求,我们可以使用过滤器来操作甚至阻止请求到达任何的servlet。反之亦然,我们也可以阻止响应到达客户端。
创建filter
我们需要新建一个类,实现javax.servlet.Filter interface
@Component
public class LogFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(LogFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("Hello from: " + request.getLocalAddr());
chain.doFilter(request, response);
}
}
接下来,我们重写doFilter方法,在这里我们可以访问或操作ServletRequest、ServletResponse或FilterChain对象。我们可以使用FilterChain对象允许或阻止请求。
最后,我们通过@Component注释将Filter添加到Spring中。
二.HandlerInterceptors
HandlerInterceptors是Spring MVC框架的一部分,位于DispatcherServlet和我们的控制器之间。我们可以在请求到达控制器之前以及视图呈现之前和之后拦截请求。
创建HandlerInterceptors
要创建HandlerInterceptor,我们需要创建一个实现org.springframework.web.servlet.HandlerInterceptor接口的类。
- preHandle() -在目标处理程序被调用之前执行
- postHandle() -在目标处理程序之后但在DispatcherServlet呈现视图之前执行
- afterCompletion() -在完成请求处理和视图呈现后回调
public class LogInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
logger.info("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
logger.info("afterCompletion");
}
}
三.关键区别和使用范例
过滤器在请求到达DispatcherServlet之前拦截请求,这使得它们非常适合执行粗粒度的任务,例如:
- 身份验证
- 日志记录和审计
- 图像和数据压缩
- 任何我们想从SpringMVC中解耦的功能
HandlerIntercepors拦截DispatcherServlet和控制器之间的请求。这是在Spring MVC框架内完成的,提供了对处理程序和ModelAndView对象的访问
四.总结
区别 | 拦截器 | 过滤器 |
---|---|---|
实现原理 | 基于Java的反射机制(动态代理) | 基于函数回调 |
使用范围 | 是一个Spring组件,并由Spring容器管理,可以单独使用 | 实现 javax.servlet.Filter 接口,该接口在Servlet规范中定义,因此只能在web程序中使用 |
触发时机 | 请求进入servlet中,进入Controlloer之前预处理,Controlloer渲染了对应的视图之后请求结束 | 请求进入容器但进去servlet之前预处理,servlet处理完之后结束 |
拦截的请求范围 | 只对controller中请求或访问static目录下的资源请求起作用 | 对所有请求起作用 |
补充:
- 过滤器Filter基于函数回调?
函数回调:A类拥有B类引用,通过A类完成业务时,对B进行实现,由调用方控制B的实现。相当于A调用了B,B中又调用了A的业务逻辑
举例:你去图书馆借两本书,但是只有一本有,另外一本被别人借走了,于是你就先借了一本,跟管理员说,等第二本到了,跟你打电话,你来拿。
上面的过程就相当于一个回调函数,你是回调对象,管理员是控制器对象,打电话是回调的方法。引自https://blog.csdn.net/weixin_44036154/article/details/108220620
@WebFilter("/*") //对所有的请求都执行该过滤器
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
//执行过滤处理
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
req.setCharacterEncoding("UTF-8");
//放行
filterChain.doFilter(req,resp);
}
@Override
public void destroy() {
}
}
在调用 doFilter 方法时,会传递一个 filterChain 对象进来,如果调用该方法,则 web 服务器就会调用 web 资源的 service 方法,即 web 资源就会被访问。
- 拦截器?
我们创建一个计算方法执行时间的拦截器
@Slf4j
public class MyHandlerInterceptor implements HandlerInterceptor {
// 进入我们的 Controller方法之前执行,如果返回结果为false,那么的处理器就不执行了。如果为true,放行。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("==================preHandle====================");
// String path = request.getServletPath();
// System.out.println(handler);
// System.out.println(path);
Long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
log.info(handler + "被调用了");
return true;
}
// 我们的处理器执行完毕,在返回 modelAndView 给前端控制器之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// System.out.println("==================postHandle====================");
Long startTime = (Long)request.getAttribute("startTime");
Long endTime = System.currentTimeMillis();
log.info("执行时间为:" + (endTime-startTime)/1000 + "秒");
}
// 把 modelAndView 返回给前端控制器之后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// System.out.println("==================afterCompletion====================");
}
}
流程如下:
我们从流程图可以看到,拦截器的执行是在穿插在SpringMvc的工作流程中的,并没有用到动态代理机制,直接调用的拦截器方法。
拦截器在实现层面,并没有用到aop,并没有切面,通知这一类的代码,所以它的实现并不是基于aop的。但是拦截器从思想层面上,是面向切面编程的,是在controller这个层面上进行的代码织入。引自https://blog.csdn.net/oracle1158/article/details/123307319