SpringTask 引起的錯誤
1. 場景
在使用 SpringBoot 編寫后臺程序時,當在瀏覽器頁面中發起請求時,MP 自動填充來完成一些字段的填充,例如創建時間、創建人、更新時間、更新人等。但是當編寫微信小程序時,由于一些字段無法進行自動填充,例如創建人、更新人等,這時需要進行處理,來區分是否為微信端的請求
private final HttpServletRequest request;// 是否排除路徑,排除微信端的路徑,不自動填充用戶id
private boolean isExclude() {String path = request.getRequestURI();return !path.startsWith("/member");
}// 插入數據時自動填充
@Override
public void insertFill(MetaObject metaObject) {if (isExclude()) {this.strictInsertFill(metaObject, "createBy", String.class, String.valueOf(getLoginUserId()));}this.strictInsertFill(metaObject, "createTime", Date.class, DateUtils.getNowDate());
}// 修改數據時自動填充
@Override
public void updateFill(MetaObject metaObject) {if (isExclude()) {this.setFieldValByName("updateBy", String.valueOf(getLoginUserId()), metaObject);}this.setFieldValByName("updateTime", DateUtils.getNowDate(), metaObject);
}
但是此時,就會產生一個問題:當在瀏覽器端(后臺管理端)使用定時任務時(這里以 SpringTask 舉例),就會出現異常

提示 “當前請求并未為 Web 請求”,后來經過層層排查,發現是自動填充處使用了 HttpServletRequest
來獲取當前請求詳情。
在場景中,isExclude
方法依賴于 HttpServletRequest
對象來獲取請求路徑。然而,定時任務并不屬于 Web 請求的一部分,因此在定時任務執行時,HttpServletRequest
對象不可用,這會導致遇到的 IllegalStateException
異常。
2. 解決方案
為了解決這個問題,可以考慮以下幾種方法:
- 檢查請求是否存在:在
isExclude
方法中添加一個檢查,判斷HttpServletRequest
是否為空,如果為空則直接返回true
或false
,具體根據你的業務需求來決定。但這只是避免了異常,并沒有真正解決問題,因為定時任務確實不需要根據請求路徑來判斷是否填充用戶信息。 - 使用不同的條件來判斷是否排除:如果定時任務和微信端請求可以通過其他方式區分開來(比如特定的線程池、特定的任務標識等),你可以考慮使用這些方式來判斷,而不是依賴請求路徑。
- 將用戶ID作為參數傳遞:對于定時任務,你可以在任務觸發時直接傳遞用戶ID,而不是在任務內部去獲取。這樣可以避免對
HttpServletRequest
的依賴。 - 使用不同的
MetaObjectHandler
實現:為定時任務創建一個獨立的MetaObjectHandler
實現,這個實現不需要考慮HttpServletRequest
,也不需要調用isExclude
方法。 - 修改定時任務邏輯:在定時任務中,手動填充創建人或更新人字段,而不是依賴
MetaObjectHandler
來自動填充。這樣可以確保定時任務在執行時不會調用isExclude
方法。 - 基于任務類型進行判斷:在
MetaObjectHandler
類中增加一個字段,用于標識當前操作的類型(比如是定時任務還是 Web 請求),在insertFill
和updateFill
方法中根據這個字段來決定是否執行isExclude
方法的判斷。
3. 修改
這里使用一種很簡單的方法,使用 RequestContextHolder.getRequestAttributes()
方法來判斷當前線程是否綁定了一個 Web 請求的上下文,這是一種常見的方法來區分 Web 請求和其他非 Web 請求(例如定時任務)的線程上下文。這種方法在 Spring 框架中被廣泛用于獲取當前請求的相關信息,而不會拋出 No thread-bound request found
這樣的異常。
具體來說,在的 isExclude
方法中,通過檢查 RequestContextHolder.getRequestAttributes()
返回的結果是否為 null
,可以判斷當前線程是否在一個 Web 請求的上下文中。如果返回的是 null
,說明當前線程不是在一個 Web 請求中,因此可以決定不執行某些依賴于 Web 請求的操作,比如填充 createBy
和 updateBy
字段。
private boolean isExclude() {// 校驗是否為 Web 請求,避免非 Web 請求導致 IllegalStateException 異常ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attributes != null) {HttpServletRequest request = attributes.getRequest();String path = request.getRequestURI();return !path.startsWith("/member");}return false;
}
以下是一些關鍵點,幫助更好地理解這種方法:
- RequestContextHolder: 這個類提供了對當前請求的訪問。通過
getRequestAttributes()
方法,可以獲取到當前線程的請求屬性。 - ServletRequestAttributes: 這是
RequestContextHolder.getRequestAttributes()
返回的對象類型之一,包含了當前請求的相關信息。 - 判斷是否為 web 請求: 通過檢查
RequestContextHolder.getRequestAttributes()
是否返回null
,可以判斷當前線程是否在一個 web 請求的上下文中。如果不是null
,則可以安全地獲取HttpServletRequest
并進行相關操作。 - ThreadLocal 使用: 盡管在
MyMetaObjectHandler
中沒有直接使用ThreadLocal
來判斷是否為 web 請求,但 Spring 框架本身在處理 web 請求時會使用ThreadLocal
來存儲請求的上下文信息。因此,RequestContextHolder.getRequestAttributes()
能夠正確地識別當前線程是否為 web 請求線程。
這種方法不僅能夠有效地區分 Web 請求和其他請求,還能避免出現 No thread-bound request found
的異常,是一個比較優雅的解決方案。如果有其他特定的需求或場景,也可以考慮自定義 ThreadLocal
變量來標識不同的請求類型,但通常情況下,使用 Spring 提供的 RequestContextHolder
就可以滿足大多數需求。