SpringBoot整合JavaWeb三大组件(Servlet、Listener、Filter)

    技术2022-07-11  104

    几种组件介绍

    监听器Listener

    Listener 可以监听 web 服务器中某一个 事件操作并触发注册的 回调函数。通俗的语言就是在 application``session``request 三个对象 创建/消亡 或者 增删改 属性时自动执行代码的功能组件。

    Servlet

    Servlet 是一种运行 服务器端 的 java 应用程序具有 独立于平台和协议 的特性并且可以动态的生成 web 页面它工作在 客户端请求 与 服务器响应 的中间层。

    过滤器Filter

    Filter 对 用户请求 进行 预处理接着将请求交给 Servlet 进行 处理 并 生成响应最后 Filter 再对 服务器响应 进行 后处理。Filter 是可以复用的代码片段常用来转换 HTTP 请求、响应 和 头信息。Filter 不像 Servlet它不能产生 响应而是只 修改 对某一资源的 请求 或者 响应。

    拦截器Interceptor

    类似 面向切面编程 中的 切面 和 通知我们通过 动态代理 对一个 service() 方法添加 通知 进行功能增强。比如说在方法执行前进行 初始化处理在方法执行后进行 后置处理。拦截器 的思想和 AOP 类似区别就是 拦截器 只能对 Controller 的 HTTP 请求进行拦截。

     

    过滤器 VS 拦截器

    两者的区别

    Filter 是基于 函数回调的而 Interceptor 则是基于 Java 反射 和 动态代理。Filter 依赖于 Servlet 容器而 Interceptor 不依赖于 Servlet 容器。Filter 对几乎 所有的请求 起作用而 Interceptor 只对 Controller 对请求起作用。

    执行顺序

    对于自定义 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); } }

    配置监听器Listener

    配置一个 ServletContext 监听器使用 @WebListener 标示即可。在 Servlet 容器 初始化 过程中contextInitialized() 方法会被调用在容器 销毁 时会调用 contextDestroyed()。

    使用WebListener注解

    @WebListener public class ContextListener implements ServletContextListener { @Autowired private RedisClient redisClient; @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("ServletContext initialized"); // 在容器初始化时往 ServletContext 上下文设置了参数名称为 CtxPath 可以全局直接访问 sce.getServletContext().setAttribute("CtxPath", "/jaemon"); redisClient.setKV("applicationContext", "/jaemon"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("ServletContext destroyed"); } }

    不用WebListener注解

    @Bean public ServletListenerRegistrationBean<ContextListener> getServletListenerRegistrationBean() { ServletListenerRegistrationBean<ContextListener> bean = new ServletListenerRegistrationBean<>( new ContextListener()); return bean; }

    配置Servlet

    配置 IndexHttpServlet重写 HttpServlet 的 doGet() 方法直接输出 IndexHttpServlet 定义的 初始化参数 和在 IndexServletContextListener 设置的 ServletContext 上下文参数。

    使用WebServlet注解

    @WebServlet(name = "IndexHttpServlet", displayName = "indexHttpServlet", urlPatterns = {"/index/IndexHttpServlet"}, initParams = { @WebInitParam(name = "createdBy", value = "Vainlgory"), @WebInitParam(name = "createdOn", value = "2018-06-20") } ) public class IndexHttpServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.getWriter().println(format("Created by %s", getInitParameter("createdBy"))); resp.getWriter().println(format("Created on %s", getInitParameter("createdOn"))); resp.getWriter().println(format("Servlet context param: %s", req.getServletContext().getAttribute("content"))); } }

    不用WebServlet注解

    public class IndexHttpServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { // ... } } @Bean public ServletRegistrationBean getServletRegistrationBean(){ ServletRegistrationBean bean = new ServletRegistrationBean(new IndexHttpServlet()); bean.addUrlMappings("/index/IndexHttpServlet"); return bean; }

    配置 @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>

    配置过滤器Filter

    一个 Servlet 请求可以经由多个 Filter 进行过滤最终由 Servlet 处理并响应客户端。这里配置两个过滤器示例

    使用WebFilter注解

    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注解

    @Bean public FilterRegistrationBean getFilterRegistrationBean() { FilterRegistrationBean bean = new FilterRegistrationBean(new FirstIndexFilter()); // bean.addUrlPatterns(new String[]{"*.do","*.jsp"});//拦截多个时 bean.addUrlPatterns("/index/*"); return bean; }

    以上 @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 处理并生成 响应然后在 过滤器链 以相反的方向对 响应 进行后置过滤处理。

    配置控制器Controller

    配置 IndexController用于测试 /index/IndexController 路径是否会被 Filter 过滤和 Interceptor 拦截并验证两者的先后顺序。

    @RestController @RequestMapping("index") public class IndexController { @GetMapping("IndexController") public String index() throws Exception { return "IndexController"; } }

    配置拦截器Interceptor

    拦截器 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 之后执行。

    拦截器在DispatcherServlet中的应用

    下面简单的看一下 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); } } } }

     

    Reference

    实战Spring Boot 2.0系列(五) - Listener, Servlet, Filter和InterceptorSpringBoot整合三大组件(Servlet、Listener、Filter)
    Processed: 0.020, SQL: 9