本文系本人开发和学习过程中遇到的问题记录,如有错误,还请不吝赐教。
前些时间新的Java后端项目(基于Spring Boot)采用war包打包,使用Tomcat 8.5进行发布。在引入了多个过滤器之后,发布时出现了问题,始终报错“一个或多个筛选器启动失败”。
项目采用的Spring Boot 2.4.5版本,使用Maven 3.6.3进行依赖管理。
相关的依赖配置如下:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
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.
*
* Filters perform filtering in the doFilter
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.
*
* Filters are configured in the deployment descriptor of a web application
*
* Examples that have been identified for this design are
* 1) Authentication Filters
* 2) Logging and Auditing Filters
* 3) Image conversion Filters
* 4) Data compression Filters
* 5) Encryption Filters
* 6) Tokenizing Filters
* 7) Filters that trigger resource access events
* 8) XSL/T filters
* 9) Mime-type chain Filter
*
* @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.
*
* The web container cannot place the filter into service if the init method
* either:
*
* - Throws a ServletException
* - Does not return within a time period defined by the web
* container
*
* 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 doFilter
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.
*
* A typical implementation of this method would follow the following
* pattern:-
* 1. Examine the request
* 2. Optionally wrap the request object with a custom implementation to
* filter content or headers for input filtering
* 3. Optionally wrap the response object with a custom implementation to
* filter content or headers for output filtering
* 4. a) Either invoke the next entity in the chain using
* the FilterChain object (chain.doFilter()
),
* 4. b) or not pass on the request/response pair to the
* next entity in the filter chain to block the request processing
* 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.
*
*
* 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.
*
* Filters perform filtering in the doFilter
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.
*
* Filters are configured in the deployment descriptor of a web application
*
* Examples that have been identified for this design are
* 1) Authentication Filters
* 2) Logging and Auditing Filters
* 3) Image conversion Filters
* 4) Data compression Filters
* 5) Encryption Filters
* 6) Tokenizing Filters
* 7) Filters that trigger resource access events
* 8) XSL/T filters
* 9) Mime-type chain Filter
*
* @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.
*
* The web container cannot place the filter into service if the init method
* either:
*
* - Throws a ServletException
* - Does not return within a time period defined by the web
* container
*
*
* @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 doFilter
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.
*
* A typical implementation of this method would follow the following
* pattern:-
* 1. Examine the request
* 2. Optionally wrap the request object with a custom implementation to
* filter content or headers for input filtering
* 3. Optionally wrap the response object with a custom implementation to
* filter content or headers for output filtering
* 4. a) Either invoke the next entity in the chain using
* the FilterChain object (chain.doFilter()
),
* 4. b) or not pass on the request/response pair to the
* next entity in the filter chain to block the request processing
* 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.
*
*
* 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() {
}
}
修改之后打包部署,完美运行,问题解决。
归根到底是开发时使用的依赖和生产环境中不统一导致的,以后应当尽可能减少这种不规范的开发习惯。