问题背景
最近在开发项目接口,基于SpringBoot 2.6.8,最终部署到外置Tomcat 8.5.85 下,开发过程中写了一个CookieFilter,实现javax.servlet.Filter接口,代码编译期正常。部署到外置Tomcat 8.5.85 下,在控制台上报错:
16-Jan-2023 16:11:07.756 严重 [localhost-startStop-1] org.apache.catalina.core.StandardContext.filterStart 启动过滤器异常[cookieFilter] java.lang.AbstractMethodError at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:281) at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:109) at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4604) ...省略其他输出...
日志截图如下:
除了初始化错误还有销毁错误,错误类型与以下的错误类型一致:
16-Jan-2023 16:11:07.876 严重 [localhost-startStop-1] org.apache.catalina.core.ApplicationFilterConfig.release 失败的销毁过滤器类型为[xx.CookieFilter]名称为[CookieFilter] java.lang.AbstractMethodError at org.apache.catalina.core.ApplicationFilterConfig.release(ApplicationFilterConfig.java:312) at org.apache.catalina.core.StandardContext.filterStop(StandardContext.java:4638) ...省略其他输出...
日志截图如下:
我的代码差不多长这样:
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class CookieFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { //做一些处理... filterChain.doFilter(request, response); } }
反复分析
- 首先要知道这个错误(java.lang.AbstractMethodError)是什么?
- 也就是说父子两个类编译时机不同,先编译父类,在编译子类前对父子类方法定义做了不兼容的修改,编译子类通过后,让子类与先编译的父类一起运行,导致运行期实例化子类时找不到父类定义抽象方法的实现,从而抛出AbstractMethodError错误。
- 原因就在于在SpringBoot上使用Filter接口需要引用
javax.servlet:javax.servlet-api
依赖包,这个包里定义的Filter接口的init()与destroy()是有默认实现的,代码如图:
- 有两种可能,一是Tomcat 8.5.x的共享库中有Filter定义,与我们需要的Filter不同,没有默认实现init与destroy方法;另一个可能是我们打的包中有两个包包含javax.servlet.Filter,运行期JVM加载顺序不一致就会引出不同的问题!
解决方案
- 方案一:不管新旧servlet-api包,所有Filter都添加默认init与destroy方法
- 方案二:升级外置Tomcat版本到9.x,原因是9.x的Tomcat的共享库Filter有默认实现init与destroy方法
- 方案三:构建排除较新的javax.servlet-api包,继续使用Tomcat 8.5.x,同样地所有Filter都要添加init与destroy方法,空的方法也可以。
- 方案四:使用内嵌Tomcat9.x部署,构建排除旧版servlet-api包
- 方案五:构建排除javax.servlet-api包与旧版servlet-api包,代码改造添加init与destroy方法,以共享库定义Filter为主
- 方案六:不大推荐。构建排除旧版servlet-api包,仍部署在Tomcat 8.5.x,有可能会加载到共享库里的Filter
这几种方案中对于研发层面最简单避免这个问题的就是方案一,这里的解决方案是抛砖引玉,欢迎大家评论给出更优解。我是Hellxz,下次博客见。