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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…