文章目录
- 【README】
- 【1】springmvc拦截器回顾
- 【1.1】定义与应用
- 【1.2】拦截器作用范围
- 【2】servlet过滤器回顾
- 【2.1】过滤器定义与应用
- 【2.2】过滤器作用范围
- 【3】springmvc拦截器与servlet过滤器区别(重要*)
- 【3.1】拦截方法调用代码实现
【README】
代码详情参见: springmvcDiscoverFirstDemo【github】
1)本文根据spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理) 中的拦截器与过滤器进行总结;
- 确切的说本文讨论的拦截器指的是springmvc拦截器,过滤器是servlet过滤器;springmvc是基于servlet构建的web框架,但两者有区别;(当然了,servlet有拦截器,也有过滤器)
2)拦截器与过滤器都可以对业务逻辑做拦截,即在上下文织入逻辑,所以把两者放在一起比较;
- 拦截器与过滤器最重要的应用场景:本文认为是偷梁换柱, 在拦截方法中,用新对象替换已有对象 ; (当然也有其他场景,如日志收集, 参数校验)
【1】springmvc拦截器回顾
【1.1】定义与应用
1)springmvc拦截器定义:基于servlet拦截器思想,springmvc提供的对servlet内部处理逻辑进行拦截的抽象接口;
2)应用场景:日志收集, 参数校验等; 其实最重要的应用,应该是偷梁换柱;就是在拦截方法中,可以用新对象替换已有对象 ;
【HandlerInterceptor】
package org.springframework.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
【TimeCostHandlerInterceptor】自定义拦截器
public class TimeCostHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("startTime" , System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute("startTime");
System.out.println(request.getServletPath() + " 执行耗时统计(单位毫秒)=" + (System.currentTimeMillis() - startTime));
}
}
【dispatcher-servlet.xml】装配拦截器到HandlerMapping
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册自定义处理器拦截器 -->
<bean id="timeCostHandlerInterceptor" class="com.tom.springmvc.handlerinterceptor.TimeCostHandlerInterceptor"/>
<!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 -->
<bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="timeCostHandlerInterceptor" /> <!-- 装配拦截器 -->
</list>
</property>
</bean>
<!-- SimpleUrlHandlerMapping: 可以配置web请求到具体二级控制器的映射, 可以把一组或多组拥有相似特征的web请求映射给二级控制器-->
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<!-- 设置SimpleUrlHandlerMapping优先级为1,优先匹配,若有多个HandlerMapping时 -->
<property name="order" value="1" />
<property name="mappings">
<value>
/userController.do=userController
/bankCard*.do=bankCardController
/bankCard/*.do=bankCardController
/pdfUrlView*.do=pdfUrlViewController
</value>
</property>
</bean>
</beans>
【1.2】拦截器作用范围
1) 由配置可知,拦截器的作用范围是HandlerMapping ; 如本文配置了2个HandlerMapping,包括 BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping ;而只有BeanNameUrlHandlerMapping装配了timeCostHandlerInterceptor拦截器,而SimpleUrlHandlerMapping 没有;
- 所以:通过BeanNameUrlHandlerMapping找到的二级处理器,并调用该处理器时,才会有timeCostHandlerInterceptor拦截功能;而SimpleUrlHandlerMapping 没有;
- 而二级处理器是由一级处理器DispatcherServlet调用HandlerMapping查找到的,所以总结起来,springmvc拦截器是在Servlet内部做拦截;(补充:servlet拦击器是对servlet外部做拦截,即对servlet上下文做拦截) ;
【2】servlet过滤器回顾
【2.1】过滤器定义与应用
1)servlet过滤器:servlet框架提供的对servlet逻辑做前置过滤的抽象接口;
2)应用场景:如参数校验; 与拦截器类似, 其实最重要的应用,应该是偷梁换柱;就是在拦截方法(doFilter)中,可以用新对象替换已有对象 ;
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default public void destroy() {
}
}
【CustomFilter】自定义过滤器
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println(request.getServletContext().getContextPath() + " CustomFilter 过滤器执行");
chain.doFilter(request, response);
System.out.println(request.getServletContext().getContextPath() + " CustomFilter 过滤器执行完成后");
}
}
【web.xml】注册过滤器代理到servlet容器
<!-- 注册过滤器代理 -->
<filter>
<filter-name>customFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>customFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
【applicationContext.xml】自定义过滤器注册到spring容器
<!-- 注册自定义过滤器 -->
<bean id="customFilter" class="com.tom.springmvc.filter.CustomFilter"/>
【2.2】过滤器作用范围
1)过滤器: 对servlet进行拦截,且仅可以做前置拦截(在servlet入口方法执行前拦截); 若满足条件,则调用FilterChain.doFilter()放行;否则封装响应报文,直接返回;
【3】springmvc拦截器与servlet过滤器区别(重要*)
1)拦截位置不同:
- 拦截器有3个拦截方法,包括preHandle, postHandle, afterCompletion 方法;可以在上文与下文以及结束时拦截;
- 过滤器只有1个拦截方法, 包括doFilter方法; 也可以在上下文拦截(FilterChain.doFilter()的上文和下文织入业务逻辑);
2)放行请求的方式不同:
- 拦截器的preHandle方法通过返回true/false放行请求;
- 过滤器的doFilter()) 通过调用 FilterChain.doFilter() 放行请求; (责任链模式)
3)作用范围不同:
- 拦截器是装配给HandlerMapping,是对servlet内部逻辑进行拦截;(本文特指springmvc拦截器)
- 过滤器是对servlet上文进行拦截,是对servlet外部逻辑进行拦截;(本文特指servlet过滤器)
4)底层拦截机制不同:
- 拦截器:若有多个拦截器,只要拦截器的preHandle执行通过(返回true,这个非常重要),则其afterCompletion 一定执行(无论是否抛出异常);类似于双向链表;
- 参考【3.1】HandlerExecutionChain#applyPreHandle()代码, 只有preHandle返回true的拦截器,才会执行其afterCompletion 方法;
- 过滤器:若有多个过滤器,是通过 FilterChain.doFilter() 把请求透传下去,类似于单向链表;
【3.1】拦截方法调用代码实现
【HandlerExecutionChain】 拦截方法调用
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 若有一个拦击器前置拦截返回false,则已执行preHandle方法的所有拦截器的afterCompletion方法被执行 【因为暂存了已执行preHandle方法的拦截器索引或下标】
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i; // 暂存已执行preHandle方法的拦截器索引 (下标递增)
}
return true;
}
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
// 把已执行preHandle方法的拦截器索引取出,执行其afterCompletion方法(下标递减)
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}