前言

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

遇上问题

为了将返回给用户的视图从JSP改成HTML嘛,我就寻思着不就是把Spring MVC配置文件的视图配置改一下,把.jsp改成.html嘛。

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

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value=""></property><property name="suffix" value=".jsp"></property>
</bean>
复制代码

因为我的JSP文件就是放在web根目录下,所以这里prefix就留空了。

修改成返回HTML的配置 Spring-MVC.xml:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value=""></property><property name="suffix" value=".html"></property>
</bean>
复制代码

然后写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 = "/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的异常

问题分析

我们来分析看看为啥出现这个问题,首先贴出我们前端控制器DispatchServlet的工作流程先

流程用文字说明大概如下:

  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 = "/login")
public String login(){System.out.println("coming");return "/register";  //返回web根目录下的register.html
}
复制代码

简单粗暴,添加个System.out.println("coming");如果请求进来了我们就可以看到打印coming的内容,我们再重新运行项目测试看看

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 = "/login")public String login(){System.out.println("coming");return "/register";  //返回web根目录下的register.html}@RequestMapping(value = "/register.html")public String  aa(){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步,然后无限死循环下去......

遇上新问题

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

    <servlet><servlet-name>Spring-MVC</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:*.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>Spring-MVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
复制代码

匹配规则我写的是/,网上的很多文章也都说匹配规则/是不会拦截.jsp、html等格式的URL的,只会拦截/login这样的,而/*的匹配规则才会拦截所有请求包括/login、.jsp、html等,实践证明网上的这些观点都是错误的! 在这里我要更正一下,匹配规则//*都是会拦截所有请求(包括/login、.jsp、.html、.css等)也就是说他们俩的作用是一样的,既然说.jsp、.html都会拦截那为什么配置/规则的时候.jsp的能正常而.html的却被再次拦截导致死循环和配置/*不管是.jsp还是.html都再次被拦截从而进入死循环?

拨开云雾见月明

既然配置//*的作用一样,为啥结果不一样呢?玄机就在Tomcat上,在Tomcatconf/目录下,有个web.xml的文件。

这个web.xml在Tomcat启动的时候就被加载进来,对所有webapp都有效,至于Tomcat下的web.xml和我们自己项目下的web.xml的区别和联系请自行到参考文章查看。这里我们详细分析一下Tomcat下的web.xml里面有啥,打开conf/web.xml看到他里面定义了个拦截.jsp.jspx的Servlet Tomcat的conf/web.xml

<!-- The mappings for the JSP servlet -->
<servlet-mapping><servlet-name>jsp</servlet-name><url-pattern>*.jsp</url-pattern><url-pattern>*.jspx</url-pattern>
</servlet-mapping>
复制代码

这个拦截规则也就是把所有的.jsp.jspx URL请求都拦截在servlet-name为jsp的servlet中,我们搜索<servlet-name>jsp</servlet-name>查找一下servlet使用的类

Tomcat的conf/web.xml

<servlet><servlet-name>jsp</servlet-name><servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class><init-param><param-name>fork</param-name><param-value>false</param-value></init-param><init-param><param-name>xpoweredBy</param-name><param-value>false</param-value></init-param><load-on-startup>3</load-on-startup>
</servlet>
复制代码

可以看到这个拦截.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

<!-- The mappings for the JSP servlet -->
<servlet-mapping><servlet-name>jsp</servlet-name><url-pattern>*.jsp</url-pattern><url-pattern>*.jspx</url-pattern><url-pattern>*.html</url-pattern>
</servlet-mapping>
复制代码

添加个规则<url-pattern>*.html</url-pattern>,即URL是.html类型的话就走Tomcat的<servlet-name>jsp</servlet-name>这个servlet而不走我们项目的servlet,然后重新运行项目测试一下

(请忽略乱码问题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的,如

<servlet-mapping><servlet-name>jsp</servlet-name><url-pattern>.html</url-pattern>
</servlet-mapping>
复制代码

这样配,但是会报错Cannot resolve Servlet 'jsp',我也不懂为啥,知道的朋友可以补充下。Tomcat下的web.xml除了有名为jsp的servlet,还有一个大家应该都很熟悉的,就是名为default的servlet,它的作用和jsp的大概一样,大家知道它是拿来配置静态资源的,却很少了解它怎么来的 Tomcat的conf/web.xml

<servlet><servlet-name>default</servlet-name><servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class><init-param><param-name>debug</param-name><param-value>0</param-value></init-param><init-param><param-name>listings</param-name><param-value>false</param-value></init-param><load-on-startup>1</load-on-startup>
</servlet><!-- The mapping for the default servlet -->
<servlet-mapping><servlet-name>default</servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>
复制代码

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

<servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.html</url-pattern>
</servlet-mapping>
复制代码

这样即可让.html的请求都不会被前端控制器DispatchServlet拦截到。

总结

当然要返回.html类型的视图也不是只有这种方法,也可以通过更换视图解析器,但是我觉得这样没必要,因为InternalResourceViewResolver视图解析器本身就是支持.html的,只是我们没处理正确而已。通过这次出现的问题,引发我了对Spring MVC的进一步了解,也把分析、解决问题的过程记录下,希望能让自己印象更深刻点,也希望能帮助到大家。

参考文章

servlet-mapping url-pattern / 和 /*区别

Tomcat文件夹下的context.xml和web.xml

web.xml中出现default是什么意思?

转自:ddnd.cn/2018/12/24/…

转载于:https://juejin.im/post/5c2220cdf265da610e801e27

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

  1. java去除json 转移,Spring MVC返回的json去除根节点名称的方法

    这篇文章主要介绍了Spring MVC返回的json去除根节点名称的方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下 spring xml中配置视图如果是如下 那么返回结果会是: {" ...

  2. Spring MVC 返回NULL时客户端用$.getJSON的问题

    如果Spring MVC返回是NULL,那么客户端的$.getJSON就不会触发: ===============20170419补充======================= 后台的输出为: D ...

  3. 一步步完成jsRender + Spring MVC + Nginx前后端分离示例

    2019独角兽企业重金招聘Python工程师标准>>> 本篇博文的目标是使用前端页面渲染插件jsRender做前后端分离,后端采用Spring MVC给出REST API,并结合Ng ...

  4. spring MVC 返回json

    spring MVC如何返回json呢? 有两种方式: 方式一:使用ModelAndView Java代码   @ResponseBody @RequestMapping("/save&qu ...

  5. Java Web(11) Spring MVC 返回Json

    2019独角兽企业重金招聘Python工程师标准>>> 1. 首先是对Spring mvc 进行xml配置 <?xml version="1.0" enco ...

  6. Spring MVC 解决日期类型动态绑定问题

    出处:http://www.cnblogs.com/crazy-fox/archive/2012/02/18/2357699.html ean 名为User,则在相同的包中存在UserEditor类可 ...

  7. Spring Mvc返回html页面404错误解决记录--转载

    原文地址:http://53873039oycg.iteye.com/blog/2061992 以前使用Spring Mvc时候都是返回jsp页面或者ftl页面,昨天想返回html页面,spring- ...

  8. spring mvc DispatcherServlet详解之四---视图渲染过程

    整个spring mvc的架构如下图所示: 现在来讲解DispatcherServletDispatcherServlet的最后一步:视图渲染.视图渲染的过程是在获取到ModelAndView后的过程 ...

  9. spring mvc返回页面显示空白_Spring 框架基础(06):Mvc架构模式简介,执行流程详解...

    一.SpringMvc框架简介 1.Mvc设计理念 MVC是一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个组件里面,在改进和个性化定制界面及用户交互的同时,不 ...

  10. Spring学习手册 1:Spring MVC 返回JSON数据

    目录 完整代码在这 Spring MVC对JSON数据格式的支持非常好,配置完成后什么都不用管靠注解就可以轻松返回JSON格式的数据. Spring 对JSON的支持有三种方式,下面会一一介绍,在此之 ...

最新文章

  1. R 语言绘制环状热图
  2. Quartz.Net—MisFire
  3. 删除DataTable中列重复的行
  4. python断点调试从哪里看数据_Python Pdb 断点调试 - 简明教程
  5. zabbix—自动发现端口并监控
  6. php yield mysql_PHP 5.5 新特性关键字 yield
  7. C++编程调试秘笈(第1次阅读)
  8. RFID打印机有什么用
  9. 计算机组成原理05章在线测试,《计算机组成原理》第05章在线测试.docx
  10. JAVA获得当前时间的几种方法
  11. CUDA memory
  12. MATLAB车牌识别GUI设计实现
  13. 比特大陆发布终端 AI 芯片 端云联手聚焦安防
  14. 现场总线CAN和工业以太网EtherCAT详解
  15. 《智慧工地单点解析系列(一)—— 劳务实名制》
  16. Proxmox集群网络配置
  17. “Stream has already been operated upon or closed” Exception in Java
  18. windows7到底是多用户多任务操作系统还是单用户多任务操作系统
  19. ceisum添加风场插件
  20. linux 内核printk 打印信息查询方法

热门文章

  1. DIV+CSS定义及优势
  2. 循环控制-链表删除结点
  3. Linux网络编程--socket
  4. 128 数据库基本操作
  5. MCS-51子程序库-1
  6. html css 布局小细节
  7. Centos7-安装Gradle4.10
  8. mysql sql
  9. Linux上vi(vim)编辑器使用教程
  10. python 调用文件传参_Python读取ini配置文件传参的简单示例