为什么web.xml配置servlet映射要多一个servlet-name
昨天在看《Head First Servlets & JSP》二刷JavaWeb的时候,突然注意到一个点,为什么web.xml配置servlet映射要多一个servlet-name?
像这样:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.microsoft.servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/myservlet</url-pattern>
</servlet-mapping>
</web-app>
我直接从url-pattern
映射到servlet-class
不就行了吗?为什么要多一层servlet-name
呢?我查了CSDN和知乎等一系列博文,大家似乎都只说了映射的查找顺序,抑或是一笔带过,询问技术群的大佬,不是装x就是讲的模糊(也可能是我太菜),我打算用此篇博文记录我的心路历程和现在的理解
理解 v1.0
我提出这个问题,得到的第一个回答是:
“你是学生,为什么要有名字呢?”
第二个回答是:
“总得找到爹吧?”
我内心这么翻译:
“他是servlet,为什么不该有个名字呢?得有个统一管理他们的爸爸。”
说的是有道理,但是我的名字难道不可以用url体现吗?不可以直接通过类名体现吗?
思来想去,我决定暂时这么理解:servlet-name就像一个map容器,装了多对servlet-class和url的映射
Tomcat容器扫描web.xml,通过反射拿到了所有servlet-name的map容器,通过用户请求的url,给出对应的class
// 通过反射拿到了map,并add了所有映射
Class servletClass = map.get("urlPattern");
// 通过模板实例化servlet类
可是这样一来,在我加载时我就要创建并填充所有的map容器,似乎效率并不高,为了统一管理
这样做,似乎这个设计并不合理
理解 v2.0
我得到了另外一个兄弟的回复:
“我不止是/myservlet这一条url要map这个MyServlet,我可能还有/myservlet1,/myservlet2。早期这么设计,也算是一定程度的解耦,以达到复用的目的。”
我说了我的理解v1.0后,他也给整晕了:
“一个name可以bind多个url吧,这就可以复用了,这是规范。现在这些不都被SpringMVC磨平了吗。。。”
emm…我也不再过问
按照他的说法:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.microsoft.servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/myservlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/myservlet1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/myservlet2</url-pattern>
</servlet-mapping>
</web-app>
多组映射复用,但是似乎还是有点抽象啊;我在想,我能不能这么理解,结合理解v1.0
:
故事1:
操场上散步遇到女神,过来问我:“你们班最帅的那个男生叫什么啊?”
我:“额…你说丁黄冈吗?”
只见她羞答答地直冲我们班,找丁黄冈去了…
故事2:
操场上散步又遇到女神,过来问我:“你们班打篮球很强的那个男生叫什么啊?”
我:“就…就是…丁黄冈啊?”
只见她又羞答答地直冲我们班,找丁黄冈去了…
(别问,问就是老舔狗了)
我们把故事转换成代码:(把servlet想成同学)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>丁黄冈</servlet-name>
<servlet-class>一班.丁黄冈同学</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>丁黄冈</servlet-name>
<url-pattern>/最帅的</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>丁黄冈</servlet-name>
<url-pattern>/打球很强的</url-pattern>
</servlet-mapping>
</web-app>
url可能是对一种特征(或功能)的描述,所指向的对象,可能是同一个。丁黄冈可以同时又帅又有钱又高,还会打球,可是只有这么一个丁黄冈!可是,你告诉我他是几班的xxx不就行了,为什么还是要多个name呢,名字可以在class中体现啊?(疯狂牛角尖)
理解 v3.0
我将最后的希望寄托于stack overflow
,结果还真找到了知音!!!
第一条评论:
为什么你会觉得少写这几行,会让xml运行效率更高?
emm…不是很懂…
第二条评论:
通常来说,servlet容器会去扫描web.xml,并在内存中创建servlet实例对象。它比每次发出请求时读取web.xml都要快得多。
有点类似缓存???不是很懂,但我觉得涉及到了servlet创建的问题:
首先Servlet实例为一个单例对象,从头到尾只有一个,而其创建在于第一次被调用(第一次处理请求),这是一种默认行为。
既然说是默认了,那么你也想到了,肯定可以手动更改了?当然,猜想正确。
其可以通过修改web.xml这个配置文件:
<servlet>
<load-on-startup>1</load-on-startup>
</servlet>
该属性可以将servlet对象的创建修改为:服务器启动时就创建该对象。
就比如说,我已经在开启容器之初就创建好了,我可以通过名字直接拿,就不用再用全限定类名去反射拿模板创建了,因为已经有了一个实例对象,我直接拿来用就好了;没有的话,再去创建。
理解 v3.1
还有一个答案评价最高的老哥是这么说的:
它允许一个servlet可以有有多个servlet映射。
<servlet>
<servlet-name>Servlet1</servlet-name>
<servlet-path>foo.Servlet</servlet-path>
</servlet>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/enroll</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/pay</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/bill</url-pattern>
</servlet-mapping>
请注意,web.xml
在应用程序启动期间仅读取和解析一次(在扫描时候,我先拿到了servlet类模板,但我不一定创建,需要时我再拿这个模板创建实例化对象),而不是您认为的每个HTTP请求都读取和解析。
从Servlet 3.0开始,存在@WebServlet注释,该注释使此样板最小化:
@WebServlet("/enroll")
public class Servlet1 extends HttpServlet {
总结
我的疑问大抵是打消了。
servlet-class
相对应的就是方便容器读取xml,拿到类模板,name相对来说,就像做个记录。用理解v1.0来说,理解为map
似乎也说的过去:因为我填充的是类模板,还未实例化,效率并没有想象那么低。“为什么还是要多个name呢,名字可以在class中体现啊?”我之前这个问题,现在看来似乎有点傻…
如果我是直接略过servlet-name
这一层,直接绑定class和url,容器不一定认得啊,他可能每次都要去读取xml,找类模板,反射创建对象,效率不是更低???
反过来看第一个老哥的评论:
为什么你会觉得少写这几行,会让xml运行效率更高?
是啊,Why do you think a different XML schema would affect runtime performance?
更新
三种映射模式
- 完全匹配
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-path>foo.MyServlet</servlet-path>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/examples/myservlet.do</url-pattern>
</servlet-mapping>
- 目录匹配
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-path>foo.MyServlet</servlet-path>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/examples/*</url-pattern>
</servlet-mapping>
- 扩展名匹配
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-path>foo.MyServlet</servlet-path>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
优先级
容器匹配顺序:
- 完全
- 如果同时有多个匹配满足,则优先最长匹配原则
- 目录
- 后缀