Listener 可以监听 web 服务器中某一个 事件操作并触发注册的 回调函数。通俗的语言就是在 application``session``request 三个对象 创建/消亡 或者 增删改 属性时自动执行代码的功能组件。
Servlet 是一种运行 服务器端 的 java 应用程序具有 独立于平台和协议 的特性并且可以动态的生成 web 页面它工作在 客户端请求 与 服务器响应 的中间层。
Filter 对 用户请求 进行 预处理接着将请求交给 Servlet 进行 处理 并 生成响应最后 Filter 再对 服务器响应 进行 后处理。Filter 是可以复用的代码片段常用来转换 HTTP 请求、响应 和 头信息。Filter 不像 Servlet它不能产生 响应而是只 修改 对某一资源的 请求 或者 响应。
类似 面向切面编程 中的 切面 和 通知我们通过 动态代理 对一个 service() 方法添加 通知 进行功能增强。比如说在方法执行前进行 初始化处理在方法执行后进行 后置处理。拦截器 的思想和 AOP 类似区别就是 拦截器 只能对 Controller 的 HTTP 请求进行拦截。
对于自定义 Servlet 对请求分发流程
Filter 过滤请求处理Servlet 处理请求Filter 过滤响应处理。对于自定义 Controller 的请求分发流程
Filter 过滤请求处理Interceptor 拦截请求处理对应的 HandlerAdapter 处理请求Interceptor 拦截响应处理Interceptor 的最终处理Filter 过滤响应处理。拦截器(Interceptor)和过滤器(Filter)的区别
配置一个 Spring Boot 启动入口类这里需要配置两个注解。
@ServletComponentScan: 允许 Spring Boot 扫描和装载当前 包路径 和 子路径 下配置的 Servlet。@EnableMvc: 允许 Spring Boot 配置 Spring MVC 相关自定义的属性比如拦截器、资源处理器、消息转换器等。 @EnableWebMvc @ServletComponentScan @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }配置一个 ServletContext 监听器使用 @WebListener 标示即可。在 Servlet 容器 初始化 过程中contextInitialized() 方法会被调用在容器 销毁 时会调用 contextDestroyed()。
配置 IndexHttpServlet重写 HttpServlet 的 doGet() 方法直接输出 IndexHttpServlet 定义的 初始化参数 和在 IndexServletContextListener 设置的 ServletContext 上下文参数。
配置 @WebServlet 注解用于注册这个 Servlet``@WebServlet 注解的 各个参数 分别对应 web.xml 中的配置
<servlet-mapping> <servlet-name>IndexHttpServlet</servlet-name> <url-pattern>/index/IndexHttpServlet</url-pattern> </servlet-mapping> <servlet> <servlet-name>IndexHttpServlet</servlet-name> <servlet-class>io.ostenant.springboot.sample.servlet.IndexHttpServlet</servlet-class> <init-param> <param-name>createdBy</param-name> <param-value>Vainlgory</param-value> </init-param> <init-param> <param-name>createdOn</param-name> <param-value>2018-06-20</param-value> </init-param> </servlet>一个 Servlet 请求可以经由多个 Filter 进行过滤最终由 Servlet 处理并响应客户端。这里配置两个过滤器示例
FirstIndexFilter.java
@WebFilter(filterName = "firstIndexFilter", displayName = "firstIndexFilter", urlPatterns = {"/index/*"}, initParams = @WebInitParam( name = "firstIndexFilterInitParam", value = "io.ostenant.springboot.sample.filter.FirstIndexFilter") ) public class FirstIndexFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(FirstIndexFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { LOGGER.info("Register a new filter {}", filterConfig.getFilterName()); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { LOGGER.info("FirstIndexFilter pre filter the request"); String filter = request.getParameter("filter1"); if (isEmpty(filter)) { response.getWriter().println("Filtered by firstIndexFilter, " + "please set request parameter \"filter1\""); return; } chain.doFilter(request, response); LOGGER.info("FirstIndexFilter post filter the response"); } @Override public void destroy() { LOGGER.info("Destroy filter {}", getClass().getName()); } }以上 @WebFilter 相关的配置属性对应于 web.xml 的配置如下
<filter-mapping> <filter-name>firstIndexFilter</filter-name> <filter-class>io.ostenant.springboot.sample.filter.FirstIndexFilter</filter-class> <url-pattern>/index/*</url-pattern> <init-param> <param-name>firstIndexFilterInitParam</param-name> <param-value>io.ostenant.springboot.sample.filter.FirstIndexFilter</param-value> </init-param> </filter-mapping>配置 FirstIndexFilter使用 @WebFilter 注解进行标示。当 FirstIndexFilter 初始化时会执行 init() 方法。每次请求路径匹配 urlPatterns 配置的路径时就会进入 doFilter() 方法进行具体的 请求 和 响应过滤。
当 HTTP 请求携带 filter1 参数时请求会被放行否则直接 过滤中断结束请求处理。
SecondIndexFilter.java
@WebFilter(filterName = "secondIndexFilter", displayName = "secondIndexFilter", urlPatterns = {"/index/*"}, initParams = @WebInitParam( name = "secondIndexFilterInitParam", value = "io.ostenant.springboot.sample.filter.SecondIndexFilter") ) public class SecondIndexFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(SecondIndexFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { LOGGER.info("Register a new filter {}", filterConfig.getFilterName()); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { LOGGER.info("SecondIndexFilter pre filter the request"); String filter = request.getParameter("filter2"); if (isEmpty(filter)) { response.getWriter().println("Filtered by firstIndexFilter, " + "please set request parameter \"filter2\""); return; } chain.doFilter(request, response); LOGGER.info("SecondIndexFilter post filter the response"); } @Override public void destroy() { LOGGER.info("Destroy filter {}", getClass().getName()); } }以上 @WebFilter 相关的配置属性对应于 web.xml 的配置如下
<filter-mapping> <filter-name>secondIndexFilter</filter-name> <filter-class>io.ostenant.springboot.sample.filter.SecondIndexFilter</filter-class> <url-pattern>/index/*</url-pattern> <init-param> <param-name>secondIndexFilterInitParam</param-name> <param-value>io.ostenant.springboot.sample.filter.SecondIndexFilter</param-value> </init-param> </filter-mapping>配置 SecondIndexFilter使用 @WebFilter 注解进行标示。当 SecondIndexFilter 初始化时会执行 init() 方法。每次请求路径匹配 urlPatterns 配置的路径时就会进入 doFilter() 方法进行具体的 请求 和 响应过滤。
当 HTTP 请求携带 filter2 参数时请求会被放行否则直接 过滤中断结束请求处理。
来看看 doFilter() 最核心的三个参数
ServletRequest: 未到达 Servlet 的 HTTP 请求ServletResponse: 由 Servlet 处理并生成的 HTTP 响应FilterChain: 过滤器链 对象可以按顺序注册多个 过滤器。 FilterChain.doFilter(request, response);解释 一个 过滤器链 对象可以按顺序注册多个 过滤器。符合当前过滤器过滤条件即请求 过滤成功 直接放行则交由下一个 过滤器 进行处理。所有请求过滤完成以后由 IndexHttpServlet 处理并生成 响应然后在 过滤器链 以相反的方向对 响应 进行后置过滤处理。
配置 IndexController用于测试 /index/IndexController 路径是否会被 Filter 过滤和 Interceptor 拦截并验证两者的先后顺序。
@RestController @RequestMapping("index") public class IndexController { @GetMapping("IndexController") public String index() throws Exception { return "IndexController"; } }拦截器 Interceptor 只对 Handler 生效。Spring MVC 会为 Controller 中的每个 请求方法 实例化为一个 Handler对象由 HandlerMapping 对象路由请求到具体的 Handler然后由 HandlerAdapter 通过反射进行请求 处理 和 响应这中间就穿插着 拦截处理。
为了区分日志下面同样对 IndexController 配置两个拦截器类
FirstIndexInterceptor.java
public class FirstIndexInterceptor implements HandlerInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(FirstIndexInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LOGGER.info("FirstIndexInterceptor pre intercepted the request"); String interceptor = request.getParameter("interceptor1"); if (isEmpty(interceptor)) { response.getWriter().println("Filtered by FirstIndexFilter, " + "please set request parameter \"interceptor1\""); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { LOGGER.info("FirstIndexInterceptor post intercepted the response"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { LOGGER.info("FirstIndexInterceptor do something after request completed"); } }SecondIndexInterceptor.java
public class SecondIndexInterceptor implements HandlerInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(SecondIndexInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LOGGER.info("SecondIndexInterceptor pre intercepted the request"); String interceptor = request.getParameter("interceptor2"); if (isEmpty(interceptor)) { response.getWriter().println("Filtered by SecondIndexInterceptor, " + "please set request parameter \"interceptor2\""); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { LOGGER.info("SecondIndexInterceptor post intercepted the response"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { LOGGER.info("SecondIndexInterceptor do something after request completed"); } }在 Spring Boot 中 配置拦截器 很简单只需要实现 WebMvcConfigurer 接口在 addInterceptors() 方法中通过 InterceptorRegistry 添加 拦截器 和 匹配路径 即可。
@Configuration public class WebConfiguration implements WebMvcConfigurer { private static final Logger LOGGER = LoggerFactory.getLogger(WebConfiguration.class); @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new FirstIndexInterceptor()).addPathPatterns("/index/**"); registry.addInterceptor(new SecondIndexInterceptor()).addPathPatterns("/index/**"); LOGGER.info("Register FirstIndexInterceptor and SecondIndexInterceptor onto InterceptorRegistry"); } }对应的 Spring XML 配置方式如下
<bean id="firstIndexInterceptor" class="io.ostenant.springboot.sample.interceptor.FirstIndexInterceptor"></bean> <bean id="secondIndexInterceptor" class="io.ostenant.springboot.sample.interceptor.SecondIndexInterceptor"></bean> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/index/**" /> <ref local="firstIndexInterceptor" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/index/**" /> <ref local="secondIndexInterceptor" /> </mvc:interceptor> </mvc:interceptors>我们通过实现 HandlerInterceptor 接口来开发一个 拦截器来看看 HandlerInterceptor 接口的三个重要的方法
preHandle(): 在 controller 接收请求、处理 request 之前执行返回值为 boolean返回值为 true 时接着执行 postHandle() 和 afterCompletion() 方法如果返回 false 则 中断 执行。postHandle(): 在 controller 处理请求之后 ModelAndView 处理前执行可以对 响应结果 进行修改。afterCompletion(): 在 DispatchServlet 对本次请求处理完成即生成 ModelAndView 之后执行。下面简单的看一下 Spring MVC 中心调度器 DispatcherServlet 的 doDispatch() 方法的原理重点关注 拦截器 的以上三个方法的执行顺序。
doDispatch(): DispatchServlet 处理请求分发的核心方法。 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 1. 按从前往后的顺序调用各个拦截器preHandle()方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 2. HandlerAdapter开始真正的请求处理并生产响应视图对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // 3. 按照从后往前的顺序依次调用各个拦截器的postHandle()方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 4. 最终会调用拦截器的afterCompletion()方法 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { // 4. 最终会调用拦截器的afterCompletion()方法 triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }上面注释的几个 HandlerExecutionChain 的方法: applyPreHandle()、applyPostHandle() 和 triggerAfterCompletion()。
applyPreHandle(): 按 从前往后 的顺序调用各个拦截器的 preHandle() 方法。任意一个 HandlerInterceptor 拦截返回 false 则 preHandle() 返回 false记录拦截器的位置 interceptorIndex然后中断拦截处理最终触发 AfterCompletion() 方法并返回 false。 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; } applyPostHandle(): 按照 从后往前 的顺序依次调用各个拦截器的 postHandle() 方法。只有当所有 HandlerInterceptor 的 preHandle() 方法返回 true 时才有机会执行到 applyPostHandle() 方法。 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } } triggerAfterCompletion: triggerAfterCompletion() 只在 preHandle() 方法返回 false 和 程序抛出异常 时执行。在 preHandle() 方法中通过 interceptorIndex 记录了返回 false 的 拦截器索引。一旦 applyPreHandle() 方法返回 false则从当前返回 false 的拦截器 从后往前 的执行 afterCompletion() 方法。 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } }