9 servlet
-
Servlet概述
-
Servlet概述
- Servlet是什么
-
Servlet概述
Servlet是sun公司提供的一门用于开发动态web资源的技术。
按照这套规范写出来的Servlet可以放置到web应用中在Servlet容器中运行。
-
-
- 开发Servlet步骤
-
想要开发一个Servlet只需要两个步骤:
(1)写一个类实现javax.servlet接口。
(2)在web.xml中为servlet配置对外访问路径。
-
开发第一个Servlet
-
写一个类实现Servlet接口
- Servlet接口api
-
写一个类实现Servlet接口
接下来我们就手动编写一个Servlet感受一下Servlet开发的过程。
我们用记事本,写一个类实现Servlet接口,我们打开api发现如果直接实现Servlet接口需要实现如下方法,如图-1所示:
图-1
简单介绍下其中重要方法:
init(ServletConfig config)
初始化方法,会在Servlet被创建出来后立即执行,做一些初始化的工作
destroy()
销毁方法,会在Servlet被销毁之前执行,做一些善后工作
service(ServletRequest req, ServletResponse res)
服务方法,每当有请求访问Servlet时,此方法执行,处理请求的代码要写到这个方法里。
-
-
- GenericServlet抽象类
-
我们发现这个接口中方法太多了,其实我们可以直接继承Servlet接口的一个默认实现了GenericServlet类,如图-2所示:
图-2
通过观察api,我们发现GenericServlet是个抽象类,实现了Servlet接口中的大部分方法,唯独service方法没有做实现,我们继承GenericServlet需要实现这个Service方法在其中写处理请求的代码。如图-3所示:
图-3
在记事本中编写如下代码,注意写的过程中需要导入包。如图-4所示:
图-4
我们输出当前时间到客户端,service方法有两个参数,ServletRequest代表HTTP请求,ServletResponse代表HTTP响应,我们想要获取客户端发送过来的信息时可以找ServletRequest,现在需要向客户端发送数据就可以使用ServletResponse对象。
通过查询ServletResponse对象的api,发现其中有获取写出数据的流的方法,通过这个方法获取流就可以将数据发送给浏览器。代码如图-5所示:
图-5
编写好java文件后,需要进行编译,如图-6所示:
图-6
在编译的过程中发现少了开发包,这是因为我们现在开发的是javaee项目,需要将javaee相关的开发包加入classpath环境变量,这个包在tomcat的支持包中存有,将其加入classpath环境变量即可。如图-7所示:
图-7
再次编译。
报出了警告,是因为Date的toLocaleString方法已经过时,但是我们不关心,到此编译已经完成。编译成功后将包拷入web应用的WEB-INF/classes目录下,如图-8所示:
图-8
-
-
配置Servlet的对外访问路径
- 在web.xml配置servlet
-
配置Servlet的对外访问路径
我们还需要在web.xml中为这个Servlet配置一个对外访问路径。
打开web.xml文件,在根标签下进行如下配置,如图-9所示:
图-9
其中,servlet-class中为配置的Servlet类的全路径名。
servlet-name是为该servlet配置的名称,此名称没有特殊要求,为了便于识别此处取名和类名相同。
url-pattern是为该名称的servlet配置对外访问路径,浏览器可以通过该路径访问此servlet。
启动服务器,通过浏览器访问,如图-10所示:
图-10
发现成功输出了当前时间,多次刷新页面发现每次显示的都是最新的时间,不同的人在不同的时间看到的结果不同,说明这确实是一个动态web资源。
-
Servlet的调用过程和生命周期
-
servlet的调用过程
- Servlet调用过程图
-
servlet的调用过程
当我们在访问这个Servlet时,是如何看到时间输出的呢?整个过程是如何工作的呢?我们画图解释,如图-11所示:
图-11
(1)在浏览器输入地址,浏览器先去查找hosts文件,将主机名翻译为ip地址,如果找不到就再去查询dns服务器将主机名翻译成ip地址。
(2)浏览器根据ip地址和端口号访问服务器,组织http请求信息发送给服务器。
(3)服务器收到请求后首先根据Host请求头判断当前访问的是哪台虚拟主机。
(4)服务器根据http请求头中的请求URI判断当前访问的是哪个web应用。
(5)服务器根据http请求头中的请求URI判断当前访问的是web应用中的哪个web资源。
(6)检查web应用的web.xml文件,如果根据路径找到具体的servlet处理类的全路径名交给该servlet处理,如果找不到就交给缺省servlet处理。
(7)这个过程中浏览器只知道自己发出来http请求,不久就收到了http响应,浏览器不知道也不关心服务器内部是如何处理的。浏览器和服务器之间的关系是非常单纯的,只有HTTP协议。
(8)解析请求、封装RequestResponse对象、创建Servlet、调用Service方法都是服务器自动进行的,开发人员只需要写好Servlet配置进容器中即可,无需操心具体的底层实现。---这就是容器啊!多重要!多形象!
-
-
servlet的生命周期
- servlet生命周期详解
-
servlet的生命周期
(1)Servlet第一次被访问到时创建对象,创建出来后立即执行init方法执行初始化的操作。
(2)从此以后该对象一直驻留在内存中为后续的对这个Servlet的请求进行服务。
(3)直到服务器关闭或web应用移除出容器时,随着web应用的销毁Servlet对象销毁掉,在销毁之前调用destory方法执行善后工作。
(4)在存活期间,每次对Servlet 的调用都会导致Service方法的执行。
-
在myeclipse中开发Servlet
-
为什么需要myeclipse
- 为什么需要myeclipse
-
为什么需要myeclipse
上面的例子中我们用记事本实现了一个Servlet,这样做的目的是为了让大家更好的理解Servlet的本质,但是如果在真实开发中也用记事本开发,可以想见效率一定是非常低的。接下来我们来了解一下如何在Myeclipse环境中开发Servlet
-
-
在Myeclipse中开发Servlet
- 第一步:创建servlet
-
在Myeclipse中开发Servlet
在工程src目录上右键弹出菜单,选择new->Servlet,如图-12所示:
图-12
-
-
- 第二步:配置servlet类信息
-
在弹出的对话框中输入Servlet的包名、类名,默认继承HttpServlet,覆盖其中doGet和doPost方法。如图-13所示:
图-13
-
-
- 第三步:配置serlvet虚拟路径
-
进入下一界面,选择是否自动配置servlet到web.xml中,一旦勾选,则自动会用输入的信息在web.xml中为该Servlet配置对外访问路径,如图-14所示:
图-14
-
-
- 第四步:编写servlet处理逻辑
-
点击确定,创建出Servlet,发现该类继承了HttpServlet(此类是Servlet接口的实现类,我们的类继承他,自然也是个Servlet),并且覆写了其中的doGet和doPost方法。
当客户端用get方式访问该Servlet时会导致doGet方法执行
当客户端用post方式访问该Servlet时会导致doPost方法执行
我们只需要写代码处理对应的处理逻辑即可。
很多时候我们的处理代码对于get方式的请求和post方式的请求的处理是相同的,此时可以在doPost中调用doGet();然后将处理代码写在doGet中。这样无论是get还是post请求都可以进行处理了。
编写代码,如图-15所示:
图-15
-
-
- 第五步:将web应用发布到tomcat中
-
如图-16所示:
图-16
启动tomcat,如图-17所示:
图-17
-
-
- 第六步:通过浏览器访问
-
由于web应用是发布到了webapps中即localhost虚拟主机中,所以按照如下方式访问,注意此处的/Demox不是工程名,而是发布时的指定的web应用名,如图-18所示:
图-18
通过浏览器访问,如图-19所示:
图-19
-
Servlet的继承结构
-
Serlvet继承结构
- 概述
-
Serlvet继承结构
我们在手写Servlet时继承的是GenericServlet,而用Myeclipse生成的Servlet是继承了HttpServlet,那么他们之间的关系到底是什么样的呢?下面我们讨论下Servlet的继承结构:
-
-
- Servlet接口
-
定义了一个servlet应该具有的方法,所有的Servlet都应该直接或间接实现此接口
-
-
- GenericServlet抽象类
-
GenericServlet抽象类是对Servlet接口的默认实现,对Serlvet接口中的大部分方法都做了默认实现,只有service方法是一个抽象方法需要继承者自己实现。实现者只需要实现Service方法在其中写处理请求的代码即可。
-
-
- HttpServlet类
-
继承自GenericServlet类,在GenericServlet类的基础上对HTTP协议进行了优化,并且实现了其中的service抽象方法,在其中判断了请求的请求方式,并根据请求方式的不同分别调用不同的doXXX()方法。
通常我们在开发Servlet时,直接继承HttpServlet覆盖对应的doGet()doPost()方法即可,一般不推荐直接覆盖service()方法。
-
Servlet对外访问路径配置细节
-
Servlet配置细节
- 基本配置
-
Servlet配置细节
Servlet需要在web.xml中配置对外访问的路径。如图-20所示:
图-20
其中:
<servlet>标签配置Servlet。
<servlet-mapping>标签配置该Servlet的对外访问路径。
一个<servlet>可以对应多个<servlet-mapping>。
-
-
- 星号匹配符的使用
-
可以用*通配符配置<serlvet-mapping>,但是要注意,必须是 *.后缀 或者 /开头的以/*结尾的路径。
由于匹配符的引入有可能一个虚拟路径会对应多个<servlet-mapping>,此时的匹配优先原则为:哪个最像找哪个,*.后缀 优先级最低。
思考:
对于如下的一些映射关系:Servlet1 映射到 /abc/* Servlet2 映射到 /* Servlet3 映射到 /abc Servlet4 映射到 *.do 问题:当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应 Servlet引擎将调用Servlet1。当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应 Servlet引擎将调用Servlet3。当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应 Servlet引擎将调用Servlet1。当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应 Servlet引擎将调用Servlet2。当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应 Servlet引擎将调用Servlet2。
-
-
- 缺省Servlet
-
路径中有一个特殊的配置“/”,如果一个servlet的对外访问路径被设置为/,则该servlet就是一个缺省servlet,其他servlet不处理的请求都由它来处理。
在conf/web.xml(通用web.xml,参考tomcat章节)中配置了缺省servlet,对静态资源的访问和错误页面的输出就是由这个缺省servlet来处理的。
如果我们自己写一个缺省servlet把爸爸web.xml中的缺省servlet覆盖的话,会导致静态web资源无法访问。所以不推荐自己配置缺省Servlet。
-
创建EasyMall注册Servlet
-
注册Servlet实现
- 开发注册Servlet
-
注册Servlet实现
在前面的学习过程中我们已近开发好了EasyMall的注册页,现在我们学完了Servlet,可以试着开发后台代码了。
首先我们将之前开发好的注册页面导入工程中,放置在web应用根目录下。
创建注册用的RegistServlet,并配置对外访问路径。如图-21所示:
图-21
修改注册页面中注册表单的提交地址指向RegistServlet。如图-22所示:
图-22
通过测试发现表单可以正确提交到Servlet中。如图-23所示:
图-23
-
-
- 引申出的问题
-
所谓的注册其实就是获取提交过来的请求参数中的注册信息,进行处理后保存在服务器数据库中。
遇到的第一个问题就是,如何获取客户端提交的请求参数。
我们注意看doGet和doPost方法,他们有两个参数,其中HttpServletRequest代表请求,HttpServletResponse代表Http响应。
既然我们需要的是请求参数,根据面向对象的思想,我们可以想见HttpServletRequest必然提供了对应的方法。
-
Request对象
-
Request的继承结构
- Request的继承结构
-
Request的继承结构
我们查看api,如图-24所示:
图-24
虽然我们一直简称为Request,但实际上代表请求的接口为ServletRequest,其中定义了http请求相关的方法。
ServletRequest有一个子类HttpServletRequest,在ServletRequest的基础上增加了很多和http协议相关的方法。
既然Request代表HTTP,那么Http请求相关的请求行请求头实体内容等信息都可以通过这个对象获得。
-
-
Request获取客户机信息
- 通过request对象获取客户机相关的信息
-
Request获取客户机信息
getRequestURL方法 -- 返回客户端发出请求完整URL
getRequestURI方法 -- 返回请求行中的资源名部分
getQueryString方法 -- 返回请求行中的参数部分
getRemoteAddr方法 -- 返回发出请求的客户机的IP地址
getMethod方法 -- 返回客户机的请求方式
getContextPath方法 -- 获得当前web应用的虚拟目录名称
-
-
Request获取请求头信息
- 通过request对象获取请求头相关的信息
-
Request获取请求头信息
getHeader(name)方法 --- String
getHeaders(String name)方法 --- Enumeration<String>
getHeaderNames方法 --- Enumeration<String>
getIntHeader(name)方法 --- int
getDateHeader(name)方法 --- long(日期对应毫秒)
-
-
Request获取请求参数和乱码解决
- 获取请求参数。
-
Request获取请求参数和乱码解决
getParameter(String name) -- String 通过name获得值
getParameterValues(String name) -- String[] 通过name获得多值 如: 爱好
getParameterNames() -- Enumeration<String> 获得所有请求参数的name
getParameterMap() -- Map<String,String[ ]> key :name value: 多值
我们试着通过这些方法去获取请求参数。如图-25所示:
图-25
经过测试,发现如果请求参数中是英文字符,是可以正常获取的。
但如果请求参数是中文,获取到的却是乱码。,入图-26所示:
图-26
-
-
- 请求参数乱码的原因和解决方法
-
这些乱码是如何产生的呢?又该如何解决呢?
我们来画图分析一下乱码产生的原理。
图-27
我们知道计算机中并不能真的存储字符,计算机的底层所有的数据都是由01这样的二进制组成的。
将字符映射成对应二进制的表叫做码表。
而大部分情况下乱码都是由于编码-解码过程中码表不一致产生的。
我们来分析一下请求乱码产生的原因。
首先浏览器是用什么码表来将字符转编码成二进制进行发送的呢?浏览器用什么码表来打开表单页面就用什么编码来发送数据。当前我们的注册页面指定了用utf-8来打开。
如图-28所示:
图-28
这就决定了浏览器是用utf-8打开的页面,浏览器在提交表单时是用utf-8编码的。
而tomcat默认情况下会使用iso8859-1来进行解码。
我们知道全世界的码表都兼容iso8859-1,所以英文处理是没有问题的。
但是iso8859-1中并没有中文,iso8859-1对于无法处理的字节都使用?替代,所以我们看到打印的是很多的“?”。
那么该如何解决这类乱码呢?
既然这个问题是服务器处理的过程中产生的,那么只要通知服务器不要使用iso8859-1而是使用正确的utf-8解析数据就可以了。
ServletRequest中提供了setCharacterEncoding方法,可以通知服务器在处理请求时使用哪个指定编码。如图-29所示:
图-29
但是要注意这行代码必须在获取任何请求参数之前执行,如果之前获取过任何请求参数,则此方法失效。
我们设置这行代码,再进行测试,发现乱码已经解决。如图-30所示:
图-30
之前我们的表单是POST提交,我们再将请求方式改为GET,再次测试,发现乱码又出现了。如图-31所示:
图-31
很奇怪,明明已经设置过编码,为什么对POST可以,但GET不行呢?
原来setCharacterEncoding是通知服务器以什么编码处理请求实体内容中的数据,在POST提交时,数据在请求的实体内容中,这行方法可以起作用,而GET提交时,由于请求参数是赋在地址栏后的,这行代码管不到,所以仍然有乱码。
那么应该如何来处理这种乱码呢?回到问题的本质,由于客户端发送时使用的是utf-8编码而服务器用iso8859-1解码造成了乱码,虽然字符已经乱掉了,但底层的字节仍然是正确的,我们只要将乱码字符getBytes(“iso8859-1”)转换为字节,就是正确的字节,再将这些字节new String(bytes,“utf-8”)按照正确的码表编码,就可以转换回正确的字符了。从而解决了乱码。如图-32所示:
图-32
这种方式乱码的解决是从原理上手动编解码解决的乱码,对GET和POST方式的提交都有效。
-
EasyMall获取用户请求参数处理
-
Easymall注册功能请求参数处理
- 代码实现
-
Easymall注册功能请求参数处理
学会了如何处理请求参数,我们现在可以获取用户提交的注册相关信息了,如图-33所示:
图-33
-
-
- 引申出的问题
-
在如上处理中们将处理用的编码写死在了程序中,这是很不好的编程习惯,一旦未来需要修改工程的编码集还需要来修改源代码。
对于这种可能修改的程序相关的信息,最好不要写死在程序中,而应该采取可配置的方式配置到配置文件中。
我们当前可以自己写配置文件来读取,但其实我们也可以使用Servlet本身的机制将信息配置到web.xml中。
-
Response
-
Response概述
- Response概述
-
Response概述
Servlet中应该如何向用户输出数据呢?在doGet和doPost方法的参数中,HttpServletRequest代表的是http请求,而HttServletResponse代表的是http响应。想要获取请求中的信息时使用HttpServletRequest对象,而有数据需要发送给客户端时,就要用到HttpServletResponse对象了。
-
-
Response继承结构
- Response继承结构
-
Response继承结构
虽然我们经常简称为response,实际上是ServletResponse接口,其中定义了很多和响应对象相关的方法,HttpServletResponse是ServletResponse接口的子接口,在ServletResponse的基础上增加了很多和http协议相关的方法。如图-40所示:
图-40
-
-
Response常用方法
- 设置状态码
-
Response常用方法
setStatus(int sc)
-
-
- 设置响应头
-
setIntHeader(String name, int value)
setHeader(String name, String value)
setDateHeader(String name, long date)
-
-
- 获取输出流
-
PrintWriter getWriter()
ServletOutputStream getOutputStream();
-
-
Response输出信息到客户端
- 输出信息到客户端api
-
Response输出信息到客户端
查询api,在Response向外输出数据的方法有如下两个:
PrintWriter getWriter()
ServletOutputStream getOutputStream();
其中getWriter获取的是字符流,可以输出字符数据到客户端。
getOutputStream获取的是字节流,可以输出字节数据到客户端。
我们现在要将字符数据发送给客户端,可以调用getWriter方法向其中输出数据
经测试可以正确的输出。如图-41所示:
图-41
接着我们测试中文。发现输出时产生了乱码。如图-42所示:
图-42
-
-
- 响应乱码处理
-
这个乱码是如何产生的呢?乱码的产生大多是由于编码和解码时的码表不同产生的。
那么服务器是以什么码表来发送数据呢?我们发现乱码是以“?”的形式出现的。根据我们的经验,这种问题多半是由ISO8859-1编码导致的。
确实是的,如果不指定,服务器默认将用iso8859-1进行编码发送数据。浏览器用什么码表打开呢?一般来说如果不指定,浏览器默认会用所在的操作系统的平台码,我们当前的中文系统中,默认就是使用GB2312作为解码码表的。
首先iso8859-1中没有中文,对于无法表示的字符,iso8859-1会用“?”来替代,所以真正发送给浏览器的数据其实是“?”,世界上所有的码表都默认兼容iso8859-1,所以gb2312认识,显示为了“?”。如图-43所示:
图-43
在解决这个问题时,可以通过设置response.setCharacterEncoding(“gbk”)来指定服务器发送数据时使用的码表。同时要注意,此行代码必须出现在任何输出数据的代码之前,如果在这行代码之前已经有任何数据写入给了response,则此行代码无效。
设置过后再重新测试。发现仍然是乱码,但不再是“??”而是变成了“涓浗”。如图-44所示:
图-44
这种类型的乱码是怎么发生的呢?我们接着分析。服务器用utf-8发送数据给浏览器,而浏览器用平台码(当前为gbk)gbk打开自然产生了乱码。如图-45所示:
图-45
这种乱码的产生是由于浏览器没有使用正确的编码打开造成的,那么我们该如何控制浏览器用指定码表打开数据呢?
在http协议中有一个响应头叫做Content-Type可以用来通知浏览器当前服务器发送的数据的格式,如果是字符格式的数据还可以指定解析时使用的码表。所以我们可以通过如下方法通知浏览器用指定码表打开发送的数据,代码如下,经测试没有乱码。
我们通过response.setHeader("Content-Type", "text/html;charset=utf-8");通知服务器发送数据时的码表。
通过response.setCharacterEncoding("utf-8");通知浏览器解析时使用的码表。
两码相同就不会有乱码了。
如图-46所示:
图-46
另外response提供了setContentType()快捷方法,在它的底层,会同时做上面两件事,所以可以一行代码解决response产生的乱码问题。如图-47所示:
图-47
-
-
- Response输出数据时的细节
-
(1)getOutputStream和getWriter这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一方法。 (2)Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据将被Servlet引擎从response里面获取,Servlet引擎将这些数据当作响应消息的正文,然后再与响应状态行和各响应头组合后输出到客户端。 (3)Serlvet的service方法结束后,Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,Servlet引擎tomcat将调用close方法关闭该输出流对象。
-
-
EasyMall提示信息实现
- 代码实现
-
EasyMall提示信息实现
根据上面所学关于Resonse的知识,我们现在可以修改程序在校验数据出错时,向外输出提示信息了。如图-48所示:
图-48
-
-
- 引申出的问题
-
对于成功通过了数据校验的请求,我们应该将数据存储到数据库中,我们目前还没有学习数据库,所以可以用xml文件模拟数据库实现功能。
我们在工程中创建users.xml文件,放置在src目录下。如图-49所示:
图-49
当用户注册时将用户信息通过dom4j写入xml中。
首先将dom4j包引入工程。
写代码更新users.xml
package com.easymall;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Writer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
public class RegistServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//读取web.xml中的编码配置
ServletContext context = this.getServletContext();
String encode = context.getInitParameter("encode");
//请求参数编码设置
request.setCharacterEncoding(encode);
//响应输出编码配置
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String password2 = request.getParameter("password2");
String nickname = request.getParameter("nickname");
String email = request.getParameter("email");
String valistr = request.getParameter("valistr");
//验证验证码
//TODO 验证验证码是否正确
//验证字段非空
if(username == null || "".equals(username)){
response.getWriter().write("用户名不能为空!");
return;
}
if(password == null || "".equals(password)){
response.getWriter().write("密码不能为空!");
return;
}
if(password2 == null || "".equals(password2)){
response.getWriter().write("确认密码不能为空!");
return;
}
if(nickname == null || "".equals(nickname)){
response.getWriter().write("昵称不能为空!");
return;
}
if(email == null || "".equals(email)){
response.getWriter().write("邮箱不能为空!");
return;
}
//验证邮箱格式是否正确
if(!email.matches("^\\[email protected]\\w+(\\.\\w+)+$")){ response.getWriter().write("邮箱格式不正确!");
return;
}
//存入数据库
try {
//解析xml
SAXReader reader = new SAXReader();
Document dom = reader.read("users.xml");
Element root = dom.getRootElement();
//凭空创建userEle保存用户信息
Element userEle = DocumentHelper.createElement("user");
userEle.setAttributeValue("username", username);
userEle.setAttributeValue("password", password);
userEle.setAttributeValue("nickname", nickname);
userEle.setAttributeValue("email", email);
//挂载到根节点
root.add(userEle);
//更新xml
XMLWriter wrtier = new XMLWriter(new FileOutputStream("users.xml"),OutputFormat.createPrettyPrint());
wrtier.write(dom);
wrtier.close();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
在实现的过程中,我们发现无法读取到这个xml文件。如图-50所示:
图-50
这是为什么呢?
我们观察到,如果写一个相对路径程序是到tomcat的bin目录中寻找users.xml文件的,路径错误。
我们修改程序,写一个绝对路径,发现访问的是tomcat所在的磁盘根目录。路径错误。
如图-51所示:
图-51
为什么会这样呢?
原来,当java程序在计算路径的时候,如果写的是相对路径则会基于程序的启动目录进行相对路径计--对于web应用,由于程序是运行在tomcat中,而tomcat是由tomcat/bin中的startup.bat启动的,自然是相对于这里的。
如果写的是一个绝对路径,则访问的是程序启动根目--对于tomcat则是tomcat所在的根目录。
这两个位置当然找不到xml文件。
那么该如何读取这个资源文件呢?其实我们知道,这个xml文件就在E:\resource\tarena\EasyMall\WebRoot\WEB-INF\classes\users.xml,如果我们直接写这个盘符开始的绝对路径可不可以呢?
经测试是可以的。
但是这种写法将路径写死在了程序中的,一旦换了发布环境,这个路径很可能是错的。非常常见的场景是,开发阶段我们在自己的计算机中进行开发,我们把路径写成自己电脑中文件所在的路径,而开发完成后发布到生产环境服务器中,此时路径往往是错误的。所以这种写法虽然可以但是仍然不好。
相对路径、绝对路径、盘符开始的绝对路径都不行,我们发现陷入了一个路径难题,到底该如何读取这个资源文件呢?
这里就需要用到一个特别的对象ServletContext了。