Servlet虚拟路径映射配置详解

Servlet虚拟路径映射配置详解

​在上一篇中我们初识了Servlet,相信大家对Servlet也都有了些了解,知道了如何创建一个Servlet,并且为其添加虚拟映射,最终发布项目,并在浏览器上请求对应的Servlet。

​我们知道,只有给Servlet配置好虚拟路径,客户端才可以进行访问,但是对于Servlet的路径映射,真的只有现在所知的这么简单么?

​答案当时是No了,不然怎么会有这篇文章????,下面让我们一起来探究其中的秘密吧!

Servlet虚拟路径映射

在web.xml文件中,一个<servlet-mapping>元素用于映射一个Servlet的对外访问路径,该路径也称为虚拟路径。例如<url-pattern>/TestServlet</url-pattern>,其中“/TestServlet”就是一个虚拟路径。

1.配置多个映射路径

​在上一文中,我们说到@WebServlet中的urlPatterns属性,其可以是一组匹配规则,也就是说一个Servlet是可以配置多个虚拟路径的,也就是Servlet和虚拟路径可以是一对多的一个关系(并不是多对多,一个虚拟路径只能映射一个Servlet),其具体实现如下,并修改doPost处的代码:

@WebServlet(
		description = "My First Servlet", 
		urlPatterns = { "/HelloServlet", "/StillMe" }, 
		initParams = { 
				@WebInitParam(name = "name", value = "lizishu")
		})
public class HelloServlet extends HttpServlet {
 //具体逻辑参看上篇文章
 //...
 
 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//设置返回客户端的contentType
		//text/plain :纯文本格式 设置为text/html println的换行会失效
		response.setContentType("text/plain;charset=utf-8");
		//response.setCharacterEncoding("utf-8"); 
		PrintWriter out = response.getWriter();
		out.println("Served at: " + request.getContextPath());
		String name = this.getInitParameter("name");
		out.println("name: " + name);
		out.println("访问的Servle名为:" + HelloServlet.class);
	}
}

​可以看到,增加一个虚拟路径映射非常方便,只需在urlPatterns中新增一项即可(注意'/'不可省略),启动项目,在浏览器上输入url,可以看到,无论是输入http://localhost:8080/FirstProject/HelloServlet、还是http://localhost:8080/FirstProject/StillMe页面上得到的输出内容均一致。

​urlPatterns在Servlet 3.0版本之前,都是配置在web.xml中的,每个Servlet会有一个对应的<servlet-mapping>标签,其中可以配置多个<url-pattern>

2.urlPatterns匹配规则

​说到Servlet虚拟路劲的匹配规则,还需要说到urlPatterns的几种匹配规则,主要有以下四种:

  • 精确匹配:也就是我们在上面配置的匹配规则,需要完全相等才能匹配成功,这也是我们经常发生错误的地方,请求Servlet时的大小写拼写错误导致404;
  • 路径匹配:比如想匹配以rest开头的所有请求,可以写成"/rest/*",其格式为以'/‘字符开头,并以'/*'结尾;
  • 扩展名匹配:比如想匹配所有以.do结尾的请求,可以写成"*.do",其格式为以'*.',后面跟上扩展名;
  • 缺省匹配:映射路径为"/",那么这个Servlet就是当前应用的缺省Servlet,默认处理无法匹配到虚拟路径的请求。

​需要注意的是,路径匹配和扩展匹配无法混合使用,即urlPattern无法写成"/rest/*.do";这也是让部分同学感到困惑的地方,Servlet的虚拟路径匹配并不是完全的按照正则来匹配的,虽然路径匹配和扩展匹配是按照正则中的通配符(*)来匹配的,这也是部分同学可以会写出特定的正则,但是却不是一个合法的虚拟路径;Servlet容器收到请求后,会将请求从上下文路径(通过request.getContextPath()获取的)处截断,使用剩余的部分来进行路径匹配,比如请求url为http://localhost:8080/FirstProject/HelloServlet,那么Servlet容器就会使用"/HelloServlet"来匹配Servlet。

​最后需要注意的是,我们说了上面四种匹配规则,尤其是缺省匹配,可以匹配到任意请求,那么一个请求如果可以匹配多个Servlet的虚拟路径,那么该执行哪个Servlet?其实啊,这些匹配规则是有优先级的,具体的优先级为:精确匹配>路径匹配>扩展名匹配>缺省匹配,Servlet容器会从优先级高的虚拟路径开始匹配,匹配到后就会立刻将请求交给对应的Servlet来处理,不会再关心其他Servlet的虚拟路径是否会匹配成功。

​下面我们来一组Servlet及其对应的虚拟路径:

urlPatterns Servlet Name
/abc/* Servlet1
/ Servlet2
/abc Servlet3
*.do Servlet4

​当请求去除上下文路径后路径为:"/abc/a.html"时,根据上述规则,会调用Servlet1;

​请求为:"/abc",根据匹配优先级,会调用Servlet3;

​请求为:"/abc/a.do",会匹配到'/abc/*'、'*.do',但根据匹配优先级,会调用Servlet1;

​请求为:"/a.do",会匹配到'/'、'*.do',但根据匹配优先级,会调用Servlet4;

3.Tomcat提供的缺省Servlet

​为了测试缺省Servlet,我们来进行一个测试。我们新建个SelfDefaultServlet,其urlPatterns我们配置为"/",其中的方法我们不做任何修改。

@WebServlet(
		description = "Self create default Servlet", 
		urlPatterns = { "/" }
		)
public class SelfDefaultServlet extends HttpServlet {
 //...
}

我们启动项目后,在浏览器上输入http://localhost:8080/FirstProject/hahaha或者其他任意无法匹配到HelloServlet虚拟路径的请求,发现页面上的结果都如下所示,是不是这样也不错,不会报404错误了。

Servlet虚拟路径映射配置详解

​但是,此时我们想访问WebContent目录下的静态页面(新建的一个welcome.html文件),浏览器上输入http://localhost:8080/FirstProject/welcome.html,猜猜会发生什么?我们来一起看下结果,如图所示,请求结果并没有按照我们的想法,根据请求路径找到welcome.htm页面,而是调用了SelfDefaultServlet,是不是很懵?

Servlet虚拟路径映射配置详解

​其实,客户端的每个请求,都是由Servlet容器根据虚拟路径的匹配规则来进行处理的,包括静态资源。并且,如果路径输入错误(去除了自己配置的缺省Servlet后),我们常见的下面的错误,也是Servlet返回给我们,哈哈,还是很意外?

Servlet虚拟路径映射配置详解

​我们能通过servlet方便简单的开发网站,是因为我们站在了巨人的肩膀上,下面我们一起来看下Sun公司都为我们开发者提前做了些什么工作。Tomcat会为项目配置一个缺省的Servlet(如果项目中自行配置,则不会生效),配置文件在tomcat安装目录下conf目录中的web.xml文件中,具体内容如下,缺省的Servlet名为DefaultServlet。

<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>
<servlet-mapping>
 <servlet-name>default</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>

客户端请求静态资源文件时,也是由缺省的Servlet处理的(自己单独配置Servlet除外),如果请求文件能找到,就会将页面通过HttpServletResponse对象以流的方式返回给客户端,否则报404错误。

​不过讲到这里,大家可以自己试一试配置了缺省Servelt时,访问welcome.html的情况(会调用SelfDefaultServlet),但是,如果我们在浏览器中输入http://localhost:8080/FirstProject/index.jsp(index.jsp是创建的第一个jsp页面)呢?会是什么样一个结果?也是调用缺省的Servlet么?真是的运行结果如下:

Servlet虚拟路径映射配置详解

​这是什么原因?为什么不是调用缺省的servlet了?这是因为tomcat除了缺省Serlvet外,还给我们提供一个处理jsp文件的Servlet,配置如下,因为后缀匹配的优先级高于缺省的Servlet,所以访问JSP的时候需要交由JspServlet来处理(JSP因为可能包含Java代码,所以第一次执行的时候需要先编译,这个工作由JspServlet完成)

<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>
<servlet-mapping>
 <servlet-name>jsp</servlet-name>
 <url-pattern>*.jsp</url-pattern>
 <url-pattern>*.jspx</url-pattern>
</servlet-mapping>

4.总结

​本文具体讨论了urlPatterns属性的匹配规则,主要为四种,其优先级也各不相同,我们在使用时,也需要根据自己的需求自己设定urlPatterns,不过知道了匹配规则,使用起来也会方便很多,也能帮我们快速的定位错误。