【問題解決】Tomcat啟動服務時提示Filter初始化或銷燬出現java.lang.AbstractMethodError錯誤

東北小狐狸發表於2023-01-16

問題背景

最近在開發專案介面,基於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錯誤。
  • 根據Filter的生命週期,初始化init,執行期doFilter,銷燬destroy,我們可以推測出是CookieFilter沒有實現init方法與destroy方法
  • 這裡問題就來了:這裡的父(介面)是javax.servlet.Filter,實現的CookieFilter只是一個Filter實現類,我們並沒有辦法修改Filter介面,按理說編譯期就應該報錯才對嘛,為啥能編譯成功呢?
    • 原因就在於在SpringBoot上使用Filter介面需要引用javax.servlet:javax.servlet-api依賴包,這個包裡定義的Filter介面的init()與destroy()是有預設實現的,程式碼如圖:
    • 也就是說,我們編譯期不重寫init()與destroy()其實是可以的;
  • 但是執行在外接Tomcat8.5.x上的時候,這兩個方法卻要求必須實現了麼?
    • 有兩種可能,一是Tomcat 8.5.x的共享庫中有Filter定義,與我們需要的Filter不同,沒有預設實現init與destroy方法;另一個可能是我們打的包中有兩個包包含javax.servlet.Filter,執行期JVM載入順序不一致就會引出不同的問題!
  • 根據上邊的猜測一,我找到了Tomcat 8.5.85的Filter原始碼https://github.com/apache/tomcat/blob/8.5.85/java/javax/servlet/Filter.java,我們發現在第67行init方法的確沒有default關鍵字修飾,destroy方法也是這樣的,第一個猜測是成立的
  • 根據猜測二,我在程式War包中找到了兩個servlet-api包:javax.servlet-api與servlet-api,前者版本較後者新。問題分析到這裡就可以做解決方案了。

解決方案

  • 方案一:不管新舊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,下次部落格見。

相關文章