Grails(Groovy)框架拋出NoHandlerFoundException而不是返回404 Not Found

本文記錄在基于Spring(Boot)框架(使用Java語言)和Grails框架(使用Groovy語言)下,開發Controller接口,對不存在的URL請求,接口返回404 not found,而不是拋出NoHandlerFoundException異常的問題,以及排查過程。

對于Spring (Boot)框架,請參考Spring 。

本文帶著對Grails的極大惡意,謹慎下翻。

Grails

對于Grails框架,使用Groovy開發的Controller接口,Postman請求不存在的index1接口,給出如下響應信息:
在這里插入圖片描述
切換到Preview:
在這里插入圖片描述
經過分析,Postman上看到的preview頁面實際上是下圖中的notFound.gsp文件:
在這里插入圖片描述
notFound.gsp文件如下:

<!doctype html>
<html><head><title>Page Not Found</title><meta name="layout" content="main"><g:if env="development"><asset:stylesheet src="errors.css"/></g:if></head><body><ul class="errors"><li>Error: Page Not Found (404)</li><li>Path: ${request.forwardURI}</li></ul></body>
</html>

gsp文件就是Grails下的JSP頁面,實際上是XML文件。

console打印日志:WARN [nio-8895-exec-5] o.s.web.servlet.PageNotFound : No mapping for GET /index1
在這里插入圖片描述
找不到這個類??

NoHandlerFoundException

添加配置:

spring:mvc:throw-exception-if-no-handler-found: trueweb: # 必須關閉靜態資源的默認處理,否則 /health1 可能被靜態資源處理器攔截或跳過resources:add-mappings: false # 謹慎使用,可能影響靜態資源  

報錯:
在這里插入圖片描述
Postman看到的還是上面的第二個圖。

添加配置類:

package com.abcd@Configuration
@EnableWebMvc
class NoHandlerConfig {@BeanServletRegistrationBean<DispatcherServlet> dispatcherServlet() {DispatcherServlet dispatcher = new DispatcherServlet()dispatcher.setThrowExceptionIfNoHandlerFound(true)  // 關鍵設置ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(dispatcher, "/")registration.setName("dispatcherServlet")return registration}
}

結果應用啟動報錯:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

注釋404映射:
在這里插入圖片描述
在這里插入圖片描述
還是不行。

WTF??

一個很簡單的技術需求,在Grails框架體系下實現起來怎么這么困難???

原理

Grails使用自己的UrlMappings路由系統,它基于AbstractController和動態調度。當訪問/health1

  • Spring MVC層確實找不到Handler;
  • 但Grails的NotFoundController或默認的grails.web.mapping.filter.UrlMappingFilter攔截請求;
  • 最終返回404,根本不經過Spring的DispatcherServlet拋異常邏輯;
  • 所以:NoHandlerFoundException根本不會被拋出,無論你怎么配置throwExceptionIfNoHandlerFound。

經過各種折騰,終于有了一個將就的解決方法:
UrlMappings.groovy最后面添加如下配置:

"/*"(controller: 'notFound', action: 'handle')

再人工實現一個NotFoundController:

class NotFoundController {def handle() {throw new org.springframework.web.servlet.NoHandlerFoundException(request.method,request.forwardURI,new HttpHeaders())}
}

日志打印:

o.g.web.errors.GrailsExceptionResolver   : NoHandlerFoundException occurred when processing request: [GET] /health1
No handler found for GET /health1. Stacktrace follows: 
java.lang.reflect.InvocationTargetException: null
Caused by: org.springframework.web.servlet.NoHandlerFoundException: No handler found for GET /health1

Postman渲染HTML錯誤信息:
在這里插入圖片描述
上面代碼里寫的明明是new HttpHeaders(),這里卻變成String???事實上,這個參數類型變更的問題,我后來又遇到過一次。

這特么太搞笑了。

GlobalExceptionHandler

想要實現的效果是,GlobalExceptionHandler.groovy實現全局Controller接口接管。對于404 Not Found,使用ERROR級別來打印日志(忽視下面截圖里的錯誤,實際上應該是this.logError(e, "請求路徑不存在")):
在這里插入圖片描述

UrlMappings.groovy

UrlMappings.groovy文件如下:

class UrlMappings {static mappings = {"/$controller/$action?/$id?(.$format)?" {constraints {// apply constraints here}}// 分組接口group "/doc", {"/"(controller: "document", method: "GET", action: "index") // 文檔列表"/$doc_id"(controller: "document", method: "GET", action: "detail") // 文檔詳情}// 單獨接口"/opts"(controller: "options", method: "GET", action: "index")// 省略其他若干"/"(view: "/index")"500"(view: '/error')"404"(view: '/notFound')}
}

刪除notFound.gsp文件(并沒有注釋UrlMappings文件里的404配置),請求/index1接口,報錯:

javax.servlet.ServletException: Could not resolve view with name '/notFound' in servlet with name 'grailsDispatcherServlet'
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1385) ~[spring-webmvc-5.3.33.jar:5.3.33]
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1150) ~[spring-webmvc-5.3.33.jar:5.3.33]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-5.3.33.jar:5.3.33]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) ~[spring-webmvc-5.3.33.jar:5.3.33]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.33.jar:5.3.33]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.33.jar:5.3.33]2024-12-31 14:00:57.501  --> ERROR [nio-8995-exec-4] o.a.c.c.C.[Tomcat].[localhost]           : Exception Processing ErrorPage[errorCode=404, location=/error]

PageNotFound

o.s.web.servlet.PageNotFound這個類到底是在哪個GAV里的?

這個類不存在,至少在spring-webmvc-5.x下面并不存在。

經過各種排查,

public class DispatcherServlet extends FrameworkServlet {public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {if (pageNotFoundLogger.isWarnEnabled()) {pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));}if (this.throwExceptionIfNoHandlerFound) {throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),new ServletServerHttpRequest(request).getHeaders());} else {response.sendError(HttpServletResponse.SC_NOT_FOUND);}}protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}}@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}
}

在這里插入圖片描述
當Spring框架的DispatcherServlet無法找到處理請求的處理器(Handler)時,它會返回null,從而導致404 Not Found錯誤。

resources.groovy

resources.groovy文件如下:

import com.aliyun.oss.OSSClient
import io.minio.MinioAsyncClient
import io.minio.MinioClient// Place your Spring DSL code here
beans = {def grailsConfig = grailsApplication.configossClient(OSSClient, grailsConfig.oss?.endpoint, grailsConfig.oss?.accessKeyId, grailsConfig.oss?.accessKeySecret)minioClient(MinioClient, MinioAsyncClient.builder().endpoint(grailsConfig.minio?.endpoint as String).credentials(grailsConfig.minio?.accessKey as String, grailsConfig.minio?.secretKey as String).build())
}

增加:

import org.springframework.web.servlet.DispatcherServletbeans = {dispatcherServlet(DispatcherServlet) {throwExceptionIfNoHandlerFound = true}
}

結果報錯:
在這里插入圖片描述
console控制臺打印異常日志:

ERROR [nio-8867-exec-5] o.g.web.errors.GrailsExceptionResolver   : NullPointerException occurred when processing request: [GET] /temp/all
Stacktrace follows: 
java.lang.NullPointerException: nullat org.grails.web.mime.HttpServletResponseExtension.getMimeTypeForRequest(HttpServletResponseExtension.groovy:131) ~[grails-plugin-mimetypes-6.2.1.jar:6.2.1]at org.grails.web.mime.HttpServletResponseExtension.getMimeType(HttpServletResponseExtension.groovy:127) ~[grails-plugin-mimetypes-6.2.1.jar:6.2.1]at org.grails.web.mime.DefaultMimeTypeResolver.resolveResponseMimeType(DefaultMimeTypeResolver.groovy:41) ~[grails-plugin-mimetypes-6.2.1.jar:6.2.1]at org.grails.web.mapping.mvc.UrlMappingsHandlerMapping.findRequestedVersion(UrlMappingsHandlerMapping.groovy:184) ~[grails-web-url-mappings-6.2.1.jar:6.2.1]at org.grails.web.mapping.mvc.UrlMappingsHandlerMapping.getHandlerInternal(UrlMappingsHandlerMapping.groovy:132) ~[grails-web-url-mappings-6.2.1.jar:6.2.1]at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:499) ~[spring-webmvc-5.3.39.jar:5.3.39]at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1266) ~[spring-webmvc-5.3.39.jar:5.3.39]at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1048) ~[spring-webmvc-5.3.39.jar:5.3.39]ERROR [nio-8867-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: class org.springframework.web.context.request.ServletRequestAttributes cannot be cast to class org.grails.web.servlet.mvc.GrailsWebRequest (org.springframework.web.context.request.ServletRequestAttributes and org.grails.web.servlet.mvc.GrailsWebRequest are in unnamed module of loader 'app')] with root cause 
java.lang.ClassCastException: class org.springframework.web.context.request.ServletRequestAttributes cannot be cast to class org.grails.web.servlet.mvc.GrailsWebRequest (org.springframework.web.context.request.ServletRequestAttributes and org.grails.web.servlet.mvc.GrailsWebRequest are in unnamed module of loader 'app')at org.grails.web.mapping.AbstractUrlMappingInfo.evaluateNameForValue(AbstractUrlMappingInfo.java:119) ~[grails-web-url-mappings-6.2.1.jar:6.2.1]at org.grails.web.mapping.DefaultUrlMappingInfo.getNamespace(DefaultUrlMappingInfo.java:185) ~[grails-web-url-mappings-6.2.1.jar:6.2.1]at org.grails.web.mapping.mvc.AbstractGrailsControllerUrlMappings.collectControllerMapping(AbstractGrailsControllerUrlMappings.groovy:206) ~[grails-web-url-mappings-6.2.1.jar:6.2.1]at org.grails.web.mapping.mvc.AbstractGrailsControllerUrlMappings.matchStatusCode(AbstractGrailsControllerUrlMappings.groovy:120) ~[grails-web-url-mappings-6.2.1.jar:6.2.1]WARN [nio-8867-exec-5] c.z.security.config.AdviceConfiguration  : [Web][有Warn被拋出] >> Warn類=[java.lang.IllegalArgumentException], URI=[/error], 消息=[不合法的參數異常], Warn=[java.lang.IllegalArgumentException: HandlerMapping requires a Grails web requestat org.springframework.util.Assert.notNull(Assert.java:201)at org.grails.web.mapping.mvc.UrlMappingsHandlerMapping.getHandlerInternal(UrlMappingsHandlerMapping.groovy:130)

應用啟動時,報錯如上面的StackTrace所示,后續的接口請求,則報錯StackTrace頂部的空指針:

ERROR [nio-8867-exec-9] c.z.security.config.AdviceConfiguration  : [Web][有異常被拋出] >> 異常類=[java.lang.NullPointerException], URI=[/temp/all], 消息=[null]

而且是所有的Controller接口都會報錯NPE!!!!

NPE

臭名昭著的空指針!為啥會NPE??
在這里插入圖片描述
UrlMappingsHandlerMapping.groovy源碼:

@CompileStatic
class UrlMappingsHandlerMapping extends AbstractHandlerMapping {protected String findRequestedVersion(GrailsWebRequest currentRequest) {String version = currentRequest.getHeader(HttpHeaders.ACCEPT_VERSION)if(!version && mimeTypeResolver) {MimeType mimeType = mimeTypeResolver.resolveResponseMimeType(currentRequest)version = mimeType.version}return version}
}

DefaultMimeTypeResolver.groovy源碼:

@CompileStatic
class DefaultMimeTypeResolver implements MimeTypeResolver {@OverrideMimeType resolveResponseMimeType(GrailsWebRequest webRequest= GrailsWebRequest.lookup()) {if (webRequest != null) {return HttpServletResponseExtension.getMimeType(webRequest.response)}return null}
}

HttpServletResponseExtension.groovy源碼:

@CompileStatic
class HttpServletResponseExtension {@CompileStaticstatic MimeType getMimeType(HttpServletResponse response) {final webRequest = GrailsWebRequest.lookup()return getMimeTypeForRequest(webRequest)}private static MimeType getMimeTypeForRequest(GrailsWebRequest webRequest) {HttpServletRequest request = webRequest.getCurrentRequest()MimeType result = (MimeType) request.getAttribute(GrailsApplicationAttributes.RESPONSE_MIME_TYPE)if (!result) {// 省略代碼}return result}
}

WTF?webRequest是null的??

其他

網絡上有很多對Grails的吐槽

  • stackoverflow-1

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92297.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92297.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92297.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

muduo中事件循環線程池的理解

事件循環線程池的理解前置知識reactor模型thread::start()方法的理解創建線程池子線程被喚醒的幾種情況子線程被主線程喚醒新連接到來有消息需要發送時&#xff08;多reactor情況時&#xff09;關閉連接時子線程被喚醒執行任務在 上一篇中&#xff0c;我們討論了關于簡單的線程…

AI智能體“上下文工程”實踐:來自 Manus 項目的經驗總結

轉載&#xff1a;https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus 在啟動 Manus (manus.im/app) 項目之初&#xff0c;我的團隊面臨一個關鍵抉擇&#xff1a;究竟是基于開源基礎模型訓練一個端到端的智能體模型&#xff0c;還是在前沿大…

day19 鏈表

定義鏈式存儲的線性表頭文件相關定義 typedef int datatype;//定義數據域類型 typedef struct Node {union{int len; //頭結點數據域datatype data; //普通節點數據域};struct Node *next; //節點指針域 }Node,*Node_ptr;鏈表的函數 注意事項 1.創建節點時&#xff0c;需要初…

【第三節】Class與Style綁定

文章目錄Class與Style綁定綁定HTML Class對象語法數組語法綁定內聯樣式對象語法數組語法自動添加前綴Class與Style綁定 數據綁定一個常見需求是操作元素的 class 列表和它的內聯樣式,因為它們都是屬性&#xff0c;我們可以用 v-bind 處理它們:我們只需要計算出表達式最終的字符…

CMOS知識點 離子注入工藝

知識點8&#xff1a;離子注入是為了將摻雜劑&#xff08;如硼、磷等&#xff09;精確引入硅晶片的近表面區域&#xff0c;以改變其電學性質。工藝過程&#xff1a;電離與加速&#xff1a;摻雜劑原子在離子源中被電離&#xff08;帶電&#xff09;&#xff0c;通過高壓電場&…

從安裝到上手:Ubuntu 22.04 玩轉 Containerd 2.1.3 容器運行時

Containerd 是一款支持 OCI 規范的容器運行時&#xff0c;注重容器部署和生命周期管理的簡單性、健壯性與可移植性&#xff0c;常被嵌入到 Docker 和 Kubernetes 等系統中。本文將詳細介紹在 Ubuntu 22.04 服務器上通過二進制包手動安裝 Containerd 的完整步驟&#xff0c;包括…

Hadoop與云原生集成:彈性擴縮容與OSS存儲分離架構深度解析

Hadoop與云原生集成的必要性Hadoop在大數據領域的基石地位作為大數據處理領域的奠基性技術&#xff0c;Hadoop自2006年誕生以來已形成包含HDFS、YARN、MapReduce三大核心組件的完整生態體系。根據CSDN技術社區的分析報告&#xff0c;全球超過75%的《財富》500強企業仍在使用Had…

飛算科技:以創新科技引領數字化變革,旗下飛算 JavaAI 成開發利器

作為國家級高新技術企業&#xff0c;飛算科技專注于自主創新&#xff0c;在數字科技領域持續深耕&#xff0c;用前沿技術為各行業客戶賦能&#xff0c;助力其實現數字化轉型升級的飛躍。?飛算科技憑借深厚的技術積累&#xff0c;將互聯網科技、大數據、人工智能等技術與實際應…

多線程Python爬蟲:加速大規模學術文獻采集

1. 引言 在學術研究過程中&#xff0c;高效獲取大量文獻數據是許多科研工作者和數據分析師的需求。然而&#xff0c;傳統的單線程爬蟲在面對大規模數據采集時&#xff0c;往往效率低下&#xff0c;難以滿足快速獲取數據的要求。因此&#xff0c;利用多線程技術優化Python爬蟲&a…

NX717NX720美光固態閃存NX724NX728

美光NX系列固態閃存深度解析&#xff1a;技術、性能與市場洞察一、技術架構與核心創新美光NX系列固態閃存&#xff08;包括NX717、NX720、NX724、NX728&#xff09;的技術根基源于其先進的G9 NAND架構。該架構通過5納米制程工藝和多層3D堆疊技術&#xff0c;實現了存儲單元密度…

淺談——C++和C#差異

雖然這個話題看著似乎有些關公戰秦瓊的味道&#xff0c;但是作為游戲開發者&#xff0c;C和C#一定是繞不開的兩門語言。不過雖然說是比較二者差異&#xff0c;因為我學習的過程主要是先學C&#xff0c;所以我先基于C的認知&#xff0c;再來聊聊C#之中的不同。&#xff08;為什么…

rocky9-zabbix簡單部署

目錄 一、準備 1、&#xff08;rocky9&#xff09; 2、配置數據庫 二、配置文件 1、導入初始架構與數據 2、配置相關文件 三、啟動服務 1、瀏覽器訪問 2、解決亂碼問題 ?編輯 四、監控 ① 添加主機 1、修改配置文件 2、啟動服務 3、網頁添加 ②添加監控模塊 1…

tabBar設置底部菜單選項、iconfont圖標(圖片)庫、模擬京東app的底部導航欄

歡迎來到我的UniApp技術專欄&#xff01;&#x1f389; 在這里&#xff0c;我將與大家分享關于UniApp開發的實用技巧、最佳實踐和項目經驗。 專欄特色&#xff1a; &#x1f4f1; 跨平臺開發一站式解決方案 &#x1f680; 從入門到精通的完整學習路徑 &#x1f4a1; 實戰項目經…

7.22總結mstp,vrrp

一、MSTP技術&#xfeff;&#xfeff;MSTI和MSTI域根&#xfeff;&#xfeff;MSTP中的端口角色3. MSTP工作原理 MSTP 計算方法? CST/IST的計算和RSTP類似 ? MSTI的計算僅限于區域內 ? MSTI計算參數包含在IST BPDU中&#xff0c;和IST的計 算同步完成&#xfeff;&#xfe…

【電腦】網卡的基礎知識

網卡&#xff08;Network Interface Card, NIC&#xff09;是計算機中用于連接網絡的關鍵組件之一&#xff0c;它負責管理和發送數據包到互聯網或其他局域網設備。下面是一些關于網卡的詳細知識&#xff1a;網卡的基本結構MAC地址&#xff1a;每個網卡都有一個唯一的物理地址&a…

IPv4枯竭時代:從NAT技術到IPv6的演進之路

&#x1f50d; 開發者資源導航 &#x1f50d;&#x1f3f7;? 博客主頁&#xff1a; 個人主頁&#x1f4da; 專欄訂閱&#xff1a; JavaEE全棧專欄 IPv4&#xff08;Internet Protocol version 4&#xff09;是互聯網最核心的通信協議之一&#xff0c;自 1981 年正式標準化以來…

模式結構-微服務架構設計模式

需求&#xff08;Forces)結果上下文(Resulting context)相關模式(Related patterns)需求&#xff1a;必須解決的問題需求部分描述了必須解決的問題和圍繞這個問題的特定上下文環境。需求有時候是相互沖突的&#xff0c;所以不能指望把他們全部都解決&#xff08;必須取舍&#…

30個常用的Linux命令匯總和實戰場景示例

下面匯總常用的 30 個常用的 Linux 命令&#xff0c;每個都附有簡要說明和典型示例&#xff0c;適合日常開發、服務器維護或系統學習使用。30 個常用的 Linux 命令匯總 一、文件與目錄操作&#xff08;基礎&#xff09;命令說明示例ls列出文件和目錄ls -l 顯示詳細信息cd切換目…

Taro 網絡 API 詳解與實用案例

Taro 網絡 API 詳解與實用案例 在現代前端開發中&#xff0c;網絡通信是不可或缺的一環。Taro 作為一款多端開發框架&#xff0c;提供了豐富且統一的網絡 API&#xff0c;幫助開發者在小程序、H5、React Native 等多端環境下高效地進行數據交互。本文將詳細介紹 Taro 的四大網…

Bitbucket平臺的HTTP Access Tokens操作手冊

在Bitbucket平臺添加HTTP Access Tokens&#xff08;用于替代密碼進行認證&#xff09;。 1. 登錄Bitbucket并訪問個人設置 打開 Bitbucket 并登錄賬號。點擊右上角頭像 → 選擇 Manage account。 2. 生成Access Token 在左側菜單中選擇 Access tokens&#xff08;位于 Sec…