本文记录在基于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
文件如下:
gsp文件就是Grails下的JSP页面,实际上是XML文件。
console打印日志:WARN [nio-8895-exec-5] o.s.web.servlet.PageNotFound : No mapping for GET /index1
找不到这个类??
NoHandlerFoundException
添加配置:
报错:
Postman看到的还是上面的第二个图。
添加配置类:
结果应用启动报错:
注释404映射:
还是不行。
WTF??
一个很简单的技术需求,在Grails框架体系下实现起来怎么这么困难???
原理
Grails使用自己的UrlMappings路由系统,它基于AbstractController和动态调度。当访问/health1
:
- Spring MVC层确实找不到Handler;
- 但Grails的NotFoundController或默认的
grails.web.mapping.filter.UrlMappingFilter
拦截请求; - 最终返回404,根本不经过Spring的DispatcherServlet抛异常逻辑;
- 所以:NoHandlerFoundException根本不会被抛出,无论你怎么配置throwExceptionIfNoHandlerFound。
经过各种折腾,终于有了一个将就的解决方法:
在UrlMappings.groovy
最后面添加如下配置:
再人工实现一个NotFoundController:
日志打印:
Postman渲染HTML错误信息:
上面代码里写的明明是new HttpHeaders()
,这里却变成String???事实上,这个参数类型变更的问题,我后来又遇到过一次。
这特么太搞笑了。
GlobalExceptionHandler
想要实现的效果是,GlobalExceptionHandler.groovy
实现全局Controller接口接管。对于404 Not Found,使用ERROR级别来打印日志(忽视下面截图里的错误,实际上应该是this.logError(e, "请求路径不存在")
):
UrlMappings.groovy
UrlMappings.groovy
文件如下:
删除notFound.gsp
文件(并没有注释UrlMappings文件里的404配置),请求/index1
接口,报错:
PageNotFound
o.s.web.servlet.PageNotFound
这个类到底是在哪个GAV里的?
这个类不存在,至少在spring-webmvc-5.x
下面并不存在。
经过各种排查,
当Spring框架的DispatcherServlet无法找到处理请求的处理器(Handler)时,它会返回null,从而导致404 Not Found错误。
resources.groovy
resources.groovy
文件如下:
增加:
结果报错:
console控制台打印异常日志:
应用启动时,报错如上面的StackTrace所示,后续的接口请求,则报错StackTrace顶部的空指针:
而且是所有的Controller接口都会报错NPE!!!!
NPE
臭名昭著的空指针!为啥会NPE??
UrlMappingsHandlerMapping.groovy
源码:
DefaultMimeTypeResolver.groovy
源码:
HttpServletResponseExtension.groovy
源码:
WTF?webRequest是null的??
其他
网络上有很多对Grails的吐槽