index加载显示servlet数据_Servlet(下)

本文深入探讨了ServletContext与Servlet映射规则的重要性,解释了如何通过不同方式获取ServletContext,以及Filter的多种拦截方式。同时,文章还分析了自定义DispatcherServlet的过程及与SpringMVC的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这一篇文章里,将会讨论ServletContext以及Servlet映射规则。这两个知识点非常重要,ServletContext直接关系到SpringIOC容器的初始化(请参考ContextLoaderListener解析),而Servlet映射规则与SpringMVC关系密切。

可以说,作为初学者只要把这两点搞清楚,那么对Spring/SpringMVC的理解将会超过70%的程序员。我没开玩笑,你随便抓一个身边的同事问问,Tomcat和Spring什么关系?SpringMVC和Servlet什么关系?估计没几个能给你讲清楚的。

我会先把SpringMVC讲得很简单,等大家觉得它就是个Servlet的时候,我又会把SpringMVC慢慢展开,露出它的全貌。此时你又会发现:SpringMVC is not only a Servlet.

主要内容:

  • ServletContext是什么
  • 如何获取ServletContext
  • Filter拦截方式之:REQUEST/FORWARD/INCLUDE/ERROR
  • Servlet映射器
  • 自定义DispatcherServlet
  • DispatcherServlet与SpringMVC
  • conf/web.xml与应用的web.xml

ServletContext是什么

ServletContext,直译的话叫做“Servlet上下文”,听着挺别扭。它其实就是个大容器,是个map。服务器会为每个应用创建一个ServletContext对象:

  • ServletContext对象的创建是在服务器启动时完成的
  • ServletContext对象的销毁是在服务器关闭时完成的

924cfab584d8e8e23f36fd60299701ce.png

ServletContext对象的作用是在整个Web应用的动态资源(Servlet/JSP)之间共享数据。例如在AServlet中向ServletContext对象保存一个值,然后在BServlet中就可以获取这个值。

c71ce6e46e412c22d5e5aa6e7a076909.png

这种用来装载共享数据的对象,在JavaWeb中共有4个,而且更习惯被成为“域对象”:

  • ServletContext域(Servlet间共享数据)
  • Session域(一次会话间共享数据,也可以理解为多次请求间共享数据)
  • Request域(同一次请求共享数据)
  • Page域(JSP页面内共享数据)

它们都可以看做是map,都有getAttribute()/setAttribute()方法。

来看一下物理磁盘中的配置文件与内存对象之间的映射关系

c78b86ea1eebd517e9e618f604ec51e9.png
每一个动态web工程,都应该在WEB-INF下创建一个web.xml,它代表当前整个应用。Tomcat会根据这个配置文件创建ServletContext对象

如何获取ServletContext

还记得GenericServlet吗?它在init方法中,将Tomcat传入的ServletConfig对象的作用域由局部变量(方法内使用)提升到成员变量。并且新建了一个getServletContext():

6dde877dc0eede4c23af31cae0d6e5d5.png
getServletContext()内部其实就是config.getServletContext()

也就是说ServletConfig对象可以得到ServletContext对象。但是这并不意味这ServletConfig对象包含着ServletContext对象,而是ServletConfig维系着ServletContext的引用。

其实这也很好理解:servletConfig是servletContext的一部分,就像他儿子。你问它父亲是谁,它当然能告诉你。

另外,Session域和Request域也可以得到ServletContext

session.getServletContext();
request.getServletContext();

9607bbd479073ded69c44d64a55d6c46.png
用域对象获得域对象

最后,还有个冷门的,我在监听器那一篇讲过了:

9deae2ef517b8f778fb7e72d1d8cec2f.png
事件对象就是对事件源(被监听对象)的简单包装

所以,获取ServletContext的方法共5种(page域这里不考虑,JSP太少用了):

  • ServletConfig#getServletContext();
  • GenericServlet#getServletContext();
  • HttpSession#getServletContext();
  • HttpServletRequest#getServletContext();
  • ServletContextEvent#getServletContext();

Filter拦截方式之:REQUEST/FORWARD/INCLUDE/ERROR

在很多人眼里,Filter只能拦截Request:

d86509260bb42b9617476953bee2a728.png

这样的理解还是太片面了。

其实配置Filter时可以设置4种拦截方式:

2a378f4df9adbb00082331b8b98a4c1a.png

很多人要么不知道,要么理解得不够清晰。

这里,我先帮大家把这4种方式和重定向(Redirect)剥离开,免得有人搞混,它们是完全两类(前者和Request有关,后者通过Response发起),以FORWARD为例:

8607207e511f5967a3ba1df4e06cdcd5.png
蓝色:重定向,橙色:转发

我们日常开发中,FORWARD用的最多的场景就是转发给JSP,然后模板输出HTML。

Redirect和REQUEST/FORWARD/INCLUDE/ERROR最大区别在于:

  • 重定向会导致浏览器发送2次请求,FORWARD们是服务器内部的1次请求

了解这个区别之后,我提一个很奇怪的问题:为什么这4种只引发1次请求?

是不是听傻了?我接下来给的答案,属于意料之外情理之中的那种:

因为FORWARD/INCLUDE等请求的分发是服务器内部的流程,不涉及浏览器

还记得如何转发吗:

42fa9807eaa85df0b821a7c2e6b9e7ef.png

我们发现通过Request或者ServletContext都可以得到分发器Dispatcher,但由于ServletContext代表整个应用,我更倾向于认为:ServletContext拥有分发器,Request是找它借的。

分发器是干嘛的?分发请求:REQUEST/FORWARD/INCLUDE/ERROR。REQUEST是浏览器发起的,而ERROR是发生页面错误时发生的,稍微特殊些。

所以,所谓Filter更详细的拦截其实是这样:

f9566a6ddf67f3584b5ecf0b1d4adb01.png
灰色块:Filter

最外层那个圈,可以理解成ServletContext,FORWARD/INCLUDE这些都是内部请求。如果在web.xml中配置Filter时4种拦截方式全配上,那么服务器内部的分发跳转都会被过滤。

当然,这些都是可配置的,默认只拦截REQUEST,也就是浏览器来的那一次。


Servlet映射器

上一篇说了很多Servlet的源码,也介绍了Servlet的作用就是处理请求。但是对于每个请求具体由哪个Servlet处理,却只字未提。其实,每一个URL要交给哪个Servlet处理,具体的映射规则都由一个映射器决定:

160dbdab12450406fa1312acbac8aaa9.png

这所谓的映射器,其实就是Tomcat中一个叫Mapper的类。

2947351dd76a2508953f0dd00fffa675.png

它里面有个internalMapWrapper方法:

f892a994e3b49540a369d1fe6f79b341.png

定义了7种映射规则:

cfe4283ceee0fbe0193fbb0a5c039f3c.png
1.精确匹配 2.前缀匹配

5e90b3cd8519e99d88ae9d5fca069ab5.png
3.扩展名匹配

2bf683ad4b7f8cc42ca7ba61482d2836.png
4.5.6 欢迎列表资源匹配?

f38e180ce791e49ba4b9ebf94a040edb.png
7.如果上面都不匹配,则交给DefaultServlet,就是简单地用IO流读取静态资源并响应给浏览器。如果资源找不到,报404错误

简单来说就是:

对于静态资源,Tomcat最后会交由一个叫做DefaultServlet的类来处理
对于Servlet ,Tomcat最后会交由一个叫做 InvokerServlet的类来处理
对于JSP,Tomcat最后会交由一个叫做JspServlet的类来处理
引用自:tomcat中对静态资源的访问也会用servlet来处理吗?

94de41f920170927e6065652c365ec8c.png

自定义DispatcherServlet

web.xml

d3237f939728738c13be963f227b06e6.png

DispatcherServlet

56482681294f5e6a3a60d6a8c1c983d8.png

知道了映射器的映射规则后,我们来分析下上图中三种拦截方式会发生什么。

但在此之前,我必须再次强调,我从没说我现在写的是SpringMVC的DispatcherServlet,这是我自己自定义的一个普通Servlet,恰好名字叫DispatcherServlet而已。所以,下面的内容,请当做一个普通Servlet的映射分析。

  • *.do:拦截.do结尾

78499b66870f9a059bde30d0d1da95d1.png

各个Servlet和谐相处,没问题。

  • /*:拦截所有

8ab580392a65af29bfadcf58a9f1285b.png

拦截localhost:8080

bb2c75b599602c9dc07e2493f3e95d05.png

拦截localhost:8080/index.html

7fc5361a908499ea1eaa035d6a7f42d0.png

拦截localhost:8080/index.jsp

116880d326c75ef424a0d20330d84def.png

也就是说,/*这种配置,相当于把DefaultServlet、JspServlet以及我们自己写的其他Servlet都“短路”了,它们都失效了。

这会导致两个问题

  • JSP无法被编译成Servlet输出HTML片段(JspServlet短路)
  • HTML/CSS/JS/PNG等资源无法获取(DefaultServlet短路)
  • /:拦截所有,但不包括JSP

90a0b3f3cb09eac27a06228e7e1d0034.png

拦截localhost:8080

4280df50ba5e2682595baf038f9d8b5d.png

拦截localhost:8080/index.html

b9b52ea27bdb1f9c4afc10ba3f3e1ec5.png

不拦截JSP

a41f707e04931a493c3a1c4d7ca37172.png

虽然JSP不拦截了,但是DefaultServlet还是“短路”了。而DispatcherServlet把本属于DefaultServlet的工作也抢过来,却又不会处理(IO读取静态资源返回)。

怎么办?


DispatcherServlet与SpringMVC

SpringMVC的核心控制器叫DispatcherServlet,映射原理和我们上面山寨版的一样,因为本质还是个Servlet。但SpringMVC提供了一个标签,解决上面/无法读取静态资源的问题:

    <!-- 静态资源处理  css js imgs -->
    <mvc:resources location="/resources/**" mapping="/resources"/>

其他的我也不说了,一张图,大家体会一下DispatcherServlet与SpringMVC到底是什么关系:

a87d3ee7c5dc47006b167e24cff6f46d.png

DispatcherServlet确实是一个Servlet,但它只是入口,SpringMVC要比想象的庞大。


conf/web.xml与应用的web.xml

conf/web.xml指的是Tomcat全局配置web.xml。

943aa50282dd6ee29b475e95d6dc42e9.png

它里面配置了两个Servlet:

c9eb2dac49858694fb45dd73ff8e3c92.png

2d6ed440f0719ded5d3b58b3c42b776d.png

也就是JspServlet和DefaultServlet的映射路径。

我们可以按Java中“继承”的思维理解conf/web.xml:

conf/web.xml中的配置相当于写在了每一个应用的web.xml中。

相当于每个应用默认都配置了JSPServlet和DefaultServlet处理JSP和静态资源。

如果我们在应用的web.xml中为DispatcherServlet配置/,会和DefaultServlet产生路径冲突,从而覆盖DefaultServlet。此时,所有对静态资源的请求,映射器都会分发给我们自己写的DispatcherServlet处理。遗憾的是,它只写了业务代码,并不能IO读取并返回静态资源。JspServlet的映射路径没有被覆盖,所以动态资源照常响应。

如果我们在应用的web.xml中为DispatcherServlet配置/*,虽然JspServlet和DefaultServlet拦截路径还是.jsp和/,没有被覆盖,但无奈的是在到达它们之前,请求已经被DispatcherServlet抢去,所以最终不仅无法处理JSP,也无法处理静态资源。

2019-5-14 18:19:05

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值