我是如何一步步解决问题 让Spring MVC返回HTML类型的视图

我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

文章目录

  • 前言
  • 遇上问题
  • 问题分析
  • 遇上新问题
  • 拨开云雾见月明
  • 总结
  • 参考文章

前言 这两天在折腾SSM,在捣鼓Spring MVC的时候,我想让Spring MVC的前端控制器(DispatcherServlet)给用户返回的是HTML类型的视图而不是JSP类型的视图,于是我按照常规的思路,把Spring MVC配置文件里面的视图解析器配置修改成HTML后缀的,然后就遇上了各种问题了…当然这些问题也都是我对Spring MVC不够了解才导致的,接下来详细说一下我遇到的问题以及解决过程。
遇上问题 为了将返回给用户的视图从JSP改成HTML嘛,我就寻思着不就是把Spring MVC配置文件的视图配置改一下,把.jsp改成.html嘛。
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

原来返回JSP的配置 Spring-MVC.xml:

因为我的JSP文件就是放在web根目录下,所以这里prefix就留空了。
修改成返回HTML的配置 Spring-MVC.xml:

然后写Controller将视图返回给前端控制器DispatchServlet看看能不能将HTML类型的视图返回给用户
package com.nChat.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class UserController {@RequestMapping(value = "https://www.it610.com/login") public String login(){ return "/register"; //返回web根目录下的register.html } }

emmmm配置文件修改好了,按照我的想法,运行肯定没“问题”吧,可现实总是打我脸…可能是我太年轻了吧,运行之后报错,页面显示404
24-Dec-2018 21:57:35.769 警告 [http-nio-8080-exec-3] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET / 24-Dec-2018 21:57:35.778 警告 [http-nio-8080-exec-2] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET / 24-Dec-2018 21:57:35.854 警告 [http-nio-8080-exec-1] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET / 24-Dec-2018 21:57:38.542 警告 [http-nio-8080-exec-4] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /register.html

错误的意思大概是前端控制器DispatchServlet找不到请求相对应的mapping,所以抛出noHandlerFound的异常
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

问题分析 我们来分析看看为啥出现这个问题,首先贴出我们前端控制器DispatchServlet的工作流程先
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

流程用文字说明大概如下:
  1. 用户发送请求,被 SpringMVC 的前端控制器DispatcherServlet 拦截。
  2. DispatcherServlet 收到请求后自己不进行处理,而是将请求转发给处理器映射器HandlerMapping
  3. 处理器映射器根据请求的URL确定映射关系找出相应的处理器适配器,并且返回HandlerExecutionChain对象给前端控制器。
    处理器映射器找到具体的处理器适配器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
  4. DispatcherServlet根据3返回的HandlerExecutionChain 调用相应的处理器适配器HandlerAdapter
  5. 经过处理器适配器HandlerAdapter调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller将结果封装到ModelAndView返回给HandlerAdapter。
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给视图解析器ViewReslover,查询到相应的视图View。
  9. ViewReslover解析后返回具体的View。
  10. DispatcherServlet把Model交给View进行渲染(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。
看完整个流程是不是知道问题出在哪了??org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /这个报错是出在了流程的第3步中,也就是DispatchServlet将请求转发给HandlerMapping后,HandlerMapping根据用户的请求找不到相应处理器映射器,所以就报了这个错误。那造成这个问题的原因会不会是我们没定义相应的Controller,导致也没有相应的处理器适配器,但是我们的Controller确实已经写好了,而且用返回JSP类型视图的代码测试数是正常的,没任何问题。
那我们一步步排错,我们在Controller中打印输出个字符,判断看看请求有没有进Controller
@RequestMapping(value = "https://www.it610.com/login") public String login(){ System.out.println("coming"); return "/register"; //返回web根目录下的register.html }

简单粗暴,添加个System.out.println("coming"); 如果请求进来了我们就可以看到打印coming的内容,我们再重新运行项目测试看看
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

coming 24-Dec-2018 23:07:38.974 警告 [http-nio-8080-exec-4] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /register.html

我们可以看到确实进来了,既然进来了,也就是说用户的请求至少已经执行到第5步了,那和前面说错误出现在第3步不是矛盾了吗??莫急,继续往下分析,既然肯定用户的请求前5步都没问题的,那打印完coming后为什么又出现了本该出现在第2的错误呢?org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /register.html,莫不是从第5步又跳到第2步了?
为了继续排查下去,我们继续添加个Controller对应register.html,看看他还报错不
@Controller public class UserController {@RequestMapping(value = "https://www.it610.com/login") public String login(){ System.out.println("coming"); return "/register"; //返回web根目录下的register.html }@RequestMapping(value = "https://www.it610.com/register.html") public Stringaa(){ System.out.println("coming aa"); return "/aa"; } }

重新启动项目运行看看,输出如下
coming coming aa 24-Dec-2018 23:29:00.759 警告 [http-nio-8080-exec-4] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /aa.html

输出表明既进到了/login又进到了/register.html,然而还是继续报错org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /aa.html,一样的错误但是造成的错误原因不一样,上面的错误是因为找不到/register.html相应的mapping,然后我们把/register.html的Controller加上并且返回/aa.html,所以导致了这次错误找不到/aa.html相应的mapping。
看到这里是不是有点头绪了?我们在Controller返回ModelAndViewHandlerAdapterHandlerAdapter再把ModelAndView返回给DispatchServlet,然后DispatchServlet再把ModelAndView传给视图解析器ViewReslover解析,也就是图中对应的第6到第8步,到这里之前都是没问题的,问题就出现在了第9步身上了,ViewReslover返回的视图名给DispatchServlet,重点来了!!! DispatchServlet又把这个视图名当做一个新的请求,去交给HandlerMapping处理!!也就是图中的第2步,然后无限死循环下去…
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

遇上新问题 那问题又来了,为什么DispatchServlet会把它当成一个新的请求去处理呢?是不是我们Servlet配置的匹配规则写的不对,把返回的视图也拦截上了?我们来看看我们的Servlet配置
项目的web.xml
Spring-MVC org.springframework.web.servlet.DispatcherServlet contextConfigLocationclasspath:*.xml 1Spring-MVC /

匹配规则我写的是/,网上的很多文章也都说匹配规则/是不会拦截.jsp、html等格式的URL的,只会拦截/login这样的,而/*的匹配规则才会拦截所有请求包括/login、.jsp、html等,实践证明网上的这些观点都是错误的! 在这里我要更正一下,匹配规则//*都是会拦截所有请求(包括/login、.jsp、.html、.css等)也就是说他们俩的作用是一样的,既然说.jsp、.html都会拦截那为什么配置/规则的时候.jsp的能正常而.html的却被再次拦截导致死循环和配置/*不管是.jsp还是.html都再次被拦截从而进入死循环?
拨开云雾见月明 既然配置//*的作用一样,为啥结果不一样呢?玄机就在Tomcat上,在Tomcatconf/目录下,有个web.xml的文件。
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

这个web.xml在Tomcat启动的时候就被加载进来,对所有webapp都有效,至于Tomcat下的web.xml和我们自己项目下的web.xml的区别和联系请自行到参考文章查看。这里我们详细分析一下Tomcat下的web.xml里面有啥,打开conf/web.xml看到他里面定义了个拦截.jsp.jspx的Servlet
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

Tomcat的conf/web.xml
jsp *.jsp *.jspx

这个拦截规则也就是把所有的.jsp.jspx URL请求都拦截在servlet-name为jsp的servlet中,我们搜索jsp查找一下servlet使用的类
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

Tomcat的conf/web.xml
jsp org.apache.jasper.servlet.JspServlet forkfalse xpoweredByfalse 3

可以看到这个拦截.jsp.jspx的servlet使用的类是org.apache.jasper.servlet.JspServlet,也就是使用的类和我们项目下web.xml的servlet使用的org.springframework.web.servlet.DispatcherServlet类不一样,使用的类不一样也导致.jsp.jspx的URL请求都不会走上图中DispatchServlet的流程,而是走它使用的类的具体流程,想了解的可以查询这个类的相关资料。到这里我们可以明确一点的就是我们项目下web.xml的servlet规则定义成/或者/*的时候.jsp正常来说应该是会被我们的项目下web.xml的servlet拦截的,但是根据servlet-mapping的匹配规则,.jsp.jspx的URL都会先被Tomcat下web.xml里面的servlet拦截,而导致.jsp.jspx不会被我们项目的web.xml里面的servlet拦截。**这也就是为什么匹配规则写成/的时候.jsp会被忽略不进行拦截的原因,那问题又来了匹配规则写成/*的时候.jsp没有被忽略仍然进行拦截的呢?原因是规则/*会覆盖所有默认的servlet,从而将所有请求都拦截了下来,接下来我们可以修改Tomcat的web.xml下的servlet配置,让项目的web.xml下的servlet配置规则为/时候也支持返回html类型的视图
Tomcat的conf/web.xml
jsp *.jsp *.jspx *.html

添加个规则*.html,即URL是.html类型的话就走Tomcat的jsp这个servlet而不走我们项目的servlet,然后重新运行项目测试一下
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

(请忽略乱码问题o(╥﹏╥)o) 看到了吧,确实可以通过Controller返回html类型的视图了吧,也就是在执行完第9步后返回的视图不再是被org.springframework.web.servlet.DispatcherServlet拦截,而是被org.apache.jasper.servlet.JspServlet拦截,从而跳出了DispatchServlet的魔抓不会再死循环了。
那以后想要返回.html类型的视图是不是都要去修改Tomcat的web.xml?也不用这样,我们只要在我们的项目web.xml中配置一个相同名的servlet即可,它会自动覆盖Tomcat的web.xml的,如
jsp .html

这样配,但是会报错Cannot resolve Servlet 'jsp',我也不懂为啥,知道的朋友可以补充下。Tomcat下的web.xml除了有名为jsp的servlet,还有一个大家应该都很熟悉的,就是名为default的servlet,它的作用和jsp的大概一样,大家知道它是拿来配置静态资源的,却很少了解它怎么来的
Tomcat的conf/web.xml
我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
文章图片

default org.apache.catalina.servlets.DefaultServlet 【我是如何一步步解决问题 让Spring MVC返回HTML类型的视图】debug0 listingsfalse 1default /

我们也可以用它来代替jsp的servlet来达到不拦截.html类型的视图,同样我们在项目的web.xml下配置名为default的servlet
default *.html

这样即可让.html的请求都不会被前端控制器DispatchServlet拦截到。
总结 当然要返回.html类型的视图也不是只有这种方法,也可以通过更换视图解析器,但是我觉得这样没必要,因为InternalResourceViewResolver视图解析器本身就是支持.html的,只是我们没处理正确而已。通过这次出现的问题,引发我了对Spring MVC的进一步了解,也把分析、解决问题的过程记录下,希望能让自己印象更深刻点,也希望能帮助到大家。
参考文章 servlet-mapping url-pattern / 和 /*区别
Tomcat文件夹下的context.xml和web.xml
web.xml中出现default是什么意思?
转自:https://ddnd.cn/2018/12/24/springmvc-view-webxml/

    推荐阅读