Tomcat 8.5 运行war包出现“一个或多个筛选器启动失败”问题排除

Tomcat 8.5 运行war包出现“一个或多个筛选器启动失败”问题排除

本文系本人开发和学习过程中遇到的问题记录,如有错误,还请不吝赐教。

问题说明

前些时间新的Java后端项目(基于Spring Boot)采用war包打包,使用Tomcat 8.5进行发布。在引入了多个过滤器之后,发布时出现了问题,始终报错“一个或多个筛选器启动失败”。

项目采用的Spring Boot 2.4.5版本,使用Maven 3.6.3进行依赖管理。

相关的依赖配置如下:

<dependencies>     <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-web</artifactId>         <exclusions><!-- 去掉默认配置 -->             <exclusion>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-starter-tomcat</artifactId>             </exclusion>         </exclusions>     </dependency>     <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-tomcat -->     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-tomcat</artifactId>         <scope>provided</scope>     </dependency>     <!-- 其他不相关依赖已经省略 --> </dependencies> 

问题排除

网上有很多关于 Tomcat 发布war项目时报错“一个或多个筛选器启动失败”的文章,基本都是由于在打war包的过程中没有将相关的依赖引入,或者引入不全导致。

经过仔细核对,本项目的打包过程不存在任何问题,也不存在依赖没有引入的问题。

后来在查看源码的过程中发现Spring Boot 2.4.5内嵌的Tomcat版本为9.0,过滤器接口源码如下。

/*  * Licensed to the Apache Software Foundation (ASF) under one or more  * contributor license agreements.  See the NOTICE file distributed with  * this work for additional information regarding copyright ownership.  * The ASF licenses this file to You under the Apache License, Version 2.0  * (the "License"); you may not use this file except in compliance with  * the License.  You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */ package javax.servlet;  import java.io.IOException;  /**  * A filter is an object that performs filtering tasks on either the request to  * a resource (a servlet or static content), or on the response from a resource,  * or both. <br>  * <br>  * Filters perform filtering in the <code>doFilter</code> method. Every Filter  * has access to a FilterConfig object from which it can obtain its  * initialization parameters, a reference to the ServletContext which it can  * use, for example, to load resources needed for filtering tasks.  * <p>  * Filters are configured in the deployment descriptor of a web application  * <p>  * Examples that have been identified for this design are<br>  * 1) Authentication Filters <br>  * 2) Logging and Auditing Filters <br>  * 3) Image conversion Filters <br>  * 4) Data compression Filters <br>  * 5) Encryption Filters <br>  * 6) Tokenizing Filters <br>  * 7) Filters that trigger resource access events <br>  * 8) XSL/T filters <br>  * 9) Mime-type chain Filter <br>  *  * @since Servlet 2.3  */ public interface Filter {      /**      * Called by the web container to indicate to a filter that it is being      * placed into service. The servlet container calls the init method exactly      * once after instantiating the filter. The init method must complete      * successfully before the filter is asked to do any filtering work.      * <p>      * The web container cannot place the filter into service if the init method      * either:      * <ul>      * <li>Throws a ServletException</li>      * <li>Does not return within a time period defined by the web      *     container</li>      * </ul>      * The default implementation is a NO-OP.      *      * @param filterConfig The configuration information associated with the      *                     filter instance being initialised      *      * @throws ServletException if the initialisation fails      */     public default void init(FilterConfig filterConfig) throws ServletException {}      /**      * The <code>doFilter</code> method of the Filter is called by the container      * each time a request/response pair is passed through the chain due to a      * client request for a resource at the end of the chain. The FilterChain      * passed in to this method allows the Filter to pass on the request and      * response to the next entity in the chain.      * <p>      * A typical implementation of this method would follow the following      * pattern:- <br>      * 1. Examine the request<br>      * 2. Optionally wrap the request object with a custom implementation to      * filter content or headers for input filtering <br>      * 3. Optionally wrap the response object with a custom implementation to      * filter content or headers for output filtering <br>      * 4. a) <strong>Either</strong> invoke the next entity in the chain using      * the FilterChain object (<code>chain.doFilter()</code>), <br>      * 4. b) <strong>or</strong> not pass on the request/response pair to the      * next entity in the filter chain to block the request processing<br>      * 5. Directly set headers on the response after invocation of the next      * entity in the filter chain.      *      * @param request  The request to process      * @param response The response associated with the request      * @param chain    Provides access to the next filter in the chain for this      *                 filter to pass the request and response to for further      *                 processing      *      * @throws IOException if an I/O error occurs during this filter's      *                     processing of the request      * @throws ServletException if the processing fails for any other reason      */     public void doFilter(ServletRequest request, ServletResponse response,             FilterChain chain) throws IOException, ServletException;      /**      * Called by the web container to indicate to a filter that it is being      * taken out of service. This method is only called once all threads within      * the filter's doFilter method have exited or after a timeout period has      * passed. After the web container calls this method, it will not call the      * doFilter method again on this instance of the filter. <br>      * <br>      *      * This method gives the filter an opportunity to clean up any resources      * that are being held (for example, memory, file handles, threads) and make      * sure that any persistent state is synchronized with the filter's current      * state in memory.      *      * The default implementation is a NO-OP.      */     public default void destroy() {} } 

可以发现过滤器接口中init()destroy()方法都是默认方法,在实现过滤器接口时无需一定实现上述两个方法。因此我的其中的一个过滤器如下。

import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpStatus; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.RequestMethod;  import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 跨域过滤器  * @author SUN Katus  * @version 1.0, 2021-05-21  */ @Order(1) @Slf4j @WebFilter(         filterName = "corsFilter",         urlPatterns = {"/*"} ) public class CORSFilter implements Filter {      @Override     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {         HttpServletRequest httpRequest = (HttpServletRequest) request;         HttpServletResponse httpResponse = (HttpServletResponse) response;         httpResponse.setCharacterEncoding("UTF-8");         httpResponse.setContentType("application/json; charset=utf-8");         httpResponse.setHeader("Access-Control-Allow-Origin", "*");         httpResponse.setHeader("Access-Control-Allow-Credentials", "true");         httpResponse.setHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");         httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With,token");         httpResponse.setHeader("Access-Control-Expose-Headers", "*");         httpResponse.setHeader("Access-Control-Max-Age", "3600");         if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {             httpResponse.setStatus(HttpStatus.SC_OK);             return;         }         chain.doFilter(httpRequest, httpResponse);     } } 

后来我又去查看了Tomcat 8.5过滤器接口的源码如下。

/*  * Licensed to the Apache Software Foundation (ASF) under one or more  * contributor license agreements.  See the NOTICE file distributed with  * this work for additional information regarding copyright ownership.  * The ASF licenses this file to You under the Apache License, Version 2.0  * (the "License"); you may not use this file except in compliance with  * the License.  You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */ package javax.servlet;  import java.io.IOException;  /**  * A filter is an object that performs filtering tasks on either the request to  * a resource (a servlet or static content), or on the response from a resource,  * or both. <br>  * <br>  * Filters perform filtering in the <code>doFilter</code> method. Every Filter  * has access to a FilterConfig object from which it can obtain its  * initialization parameters, a reference to the ServletContext which it can  * use, for example, to load resources needed for filtering tasks.  * <p>  * Filters are configured in the deployment descriptor of a web application  * <p>  * Examples that have been identified for this design are<br>  * 1) Authentication Filters <br>  * 2) Logging and Auditing Filters <br>  * 3) Image conversion Filters <br>  * 4) Data compression Filters <br>  * 5) Encryption Filters <br>  * 6) Tokenizing Filters <br>  * 7) Filters that trigger resource access events <br>  * 8) XSL/T filters <br>  * 9) Mime-type chain Filter <br>  *  * @since Servlet 2.3  */ public interface Filter {      /**      * Called by the web container to indicate to a filter that it is being      * placed into service. The servlet container calls the init method exactly      * once after instantiating the filter. The init method must complete      * successfully before the filter is asked to do any filtering work.      * <p>      * The web container cannot place the filter into service if the init method      * either:      * <ul>      * <li>Throws a ServletException</li>      * <li>Does not return within a time period defined by the web      *     container</li>      * </ul>      *      * @param filterConfig The configuration information associated with the      *                     filter instance being initialised      *      * @throws ServletException if the initialisation fails      */     public void init(FilterConfig filterConfig) throws ServletException;      /**      * The <code>doFilter</code> method of the Filter is called by the container      * each time a request/response pair is passed through the chain due to a      * client request for a resource at the end of the chain. The FilterChain      * passed in to this method allows the Filter to pass on the request and      * response to the next entity in the chain.      * <p>      * A typical implementation of this method would follow the following      * pattern:- <br>      * 1. Examine the request<br>      * 2. Optionally wrap the request object with a custom implementation to      * filter content or headers for input filtering <br>      * 3. Optionally wrap the response object with a custom implementation to      * filter content or headers for output filtering <br>      * 4. a) <strong>Either</strong> invoke the next entity in the chain using      * the FilterChain object (<code>chain.doFilter()</code>), <br>      * 4. b) <strong>or</strong> not pass on the request/response pair to the      * next entity in the filter chain to block the request processing<br>      * 5. Directly set headers on the response after invocation of the next      * entity in the filter chain.      *      * @param request  The request to process      * @param response The response associated with the request      * @param chain    Provides access to the next filter in the chain for this      *                 filter to pass the request and response to for further      *                 processing      *      * @throws IOException if an I/O error occurs during this filter's      *                     processing of the request      * @throws ServletException if the processing fails for any other reason      */     public void doFilter(ServletRequest request, ServletResponse response,             FilterChain chain) throws IOException, ServletException;      /**      * Called by the web container to indicate to a filter that it is being      * taken out of service. This method is only called once all threads within      * the filter's doFilter method have exited or after a timeout period has      * passed. After the web container calls this method, it will not call the      * doFilter method again on this instance of the filter. <br>      * <br>      *      * This method gives the filter an opportunity to clean up any resources      * that are being held (for example, memory, file handles, threads) and make      * sure that any persistent state is synchronized with the filter's current      * state in memory.      */     public void destroy(); } 

可以明显发现过滤器接口中init()destroy()方法声明都是普通的,没有默认实现,这也就说明了相应的过滤器需要显式实现过滤器接口中的全部方法。

经过修改的过滤器如下。

import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpStatus; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.RequestMethod;  import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 跨域过滤器  * @author SUN Katus  * @version 1.0, 2021-05-21  */ @Order(1) @Slf4j @WebFilter(         filterName = "corsFilter",         urlPatterns = {"/*"} ) public class CORSFilter implements Filter {      @Override     public void init(FilterConfig filterConfig) {}      @Override     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {         HttpServletRequest httpRequest = (HttpServletRequest) request;         HttpServletResponse httpResponse = (HttpServletResponse) response;         httpResponse.setCharacterEncoding("UTF-8");         httpResponse.setContentType("application/json; charset=utf-8");         httpResponse.setHeader("Access-Control-Allow-Origin", "*");         httpResponse.setHeader("Access-Control-Allow-Credentials", "true");         httpResponse.setHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");         httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With,token");         httpResponse.setHeader("Access-Control-Expose-Headers", "*");         httpResponse.setHeader("Access-Control-Max-Age", "3600");         if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {             httpResponse.setStatus(HttpStatus.SC_OK);             return;         }         chain.doFilter(httpRequest, httpResponse);     }      @Override     public void destroy() {} } 

修改之后打包部署,完美运行,问题解决。

问题反思

归根到底是开发时使用的依赖和生产环境中不统一导致的,以后应当尽可能减少这种不规范的开发习惯。