servlet

6 篇文章 0 订阅

1. Servlet概述

1.1. Servlet概述

1.1.1. Servlet是什么

Servletsun公司提供的一门用于开发动态web资源的技术。

按照这套规范写出来的Servlet可以放置到web应用中在Servlet容器中运行。

1.1.2. 开发Servlet步骤

想要开发一个Servlet只需要两个步骤:

(1)写一个类实现javax.servlet接口。

(2)web.xml中为servlet配置对外访问路径。

2. 开发第一个Servlet

2.1. 写一个类实现Servlet接口

2.1.1. Servlet接口api

接下来我们就手动编写一个Servlet感受一下Servlet开发的过程。

我们用记事本,写一个类实现Servlet接口,我们打开api发现如果直接实现Servlet接口需要实现如下方法,如图-1所示:

 

-1

简单介绍下其中重要方法:

init(ServletConfig config)

初始化方法,会在Servlet被创建出来后立即执行,做一些初始化的工作

destroy()

销毁方法,会在Servlet被销毁之前执行,做一些善后工作

service(ServletRequest req, ServletResponse res)

服务方法,每当有请求访问Servlet时,此方法执行,处理请求的代码要写到这个方法里。

2.1.2. 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

再次编译。

报出了警告,是因为DatetoLocaleString方法已经过时,但是我们不关心,到此编译已经完成。编译成功后将包拷入web应用的WEB-INF/classes目录下,如图-8所示:

 

-8

 

2.2. 配置Servlet的对外访问路径

2.2.1. web.xml配置servlet

我们还需要在web.xml中为这个Servlet配置一个对外访问路径。

打开web.xml文件,在根标签下进行如下配置,如图-9所示:

 

-9

其中,servlet-class中为配置的Servlet类的全路径名。

servlet-name是为该servlet配置的名称,此名称没有特殊要求,为了便于识别此处取名和类名相同。

url-pattern是为该名称的servlet配置对外访问路径,浏览器可以通过该路径访问此servlet

启动服务器,通过浏览器访问,如图-10所示:

 

-10

发现成功输出了当前时间,多次刷新页面发现每次显示的都是最新的时间,不同的人在不同的时间看到的结果不同,说明这确实是一个动态web资源。

3. Servlet的调用过程和生命周期

3.1. servlet的调用过程

3.1.1. 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配置进容器中即可,无需操心具体的底层实现。---这就是容器啊!多重要!多形象!

3.2. servlet的生命周期

3.2.1. servlet生命周期详解

1Servlet第一次被访问到时创建对象,创建出来后立即执行init方法执行初始化的操作。

2)从此以后该对象一直驻留在内存中为后续的对这个Servlet的请求进行服务。

3)直到服务器关闭或web应用移除出容器时,随着web应用的销毁Servlet对象销毁掉,在销毁之前调用destory方法执行善后工作。

4)在存活期间,每次对Servlet 的调用都会导致Service方法的执行。

4. myeclipse中开发Servlet

4.1. 为什么需要myeclipse

4.1.1. 为什么需要myeclipse

上面的例子中我们用记事本实现了一个Servlet,这样做的目的是为了让大家更好的理解Servlet的本质,但是如果在真实开发中也用记事本开发,可以想见效率一定是非常低的。接下来我们来了解一下如何在Myeclipse环境中开发Servlet

4.2. Myeclipse中开发Servlet

4.2.1. 第一步:创建servlet

在工程src目录上右键弹出菜单,选择new->Servlet,如图-12所示:

 

-12

4.2.2. 第二步:配置servlet类信息

在弹出的对话框中输入Servlet的包名、类名,默认继承HttpServlet,覆盖其中doGetdoPost方法。如图-13所示:

 

-13

4.2.3. 第三步:配置serlvet虚拟路径

进入下一界面,选择是否自动配置servletweb.xml中,一旦勾选,则自动会用输入的信息在web.xml中为该Servlet配置对外访问路径,如图-14所示:

 

-14

 

4.2.4. 第四步:编写servlet处理逻辑

点击确定,创建出Servlet,发现该类继承了HttpServlet(此类是Servlet接口的实现类,我们的类继承他,自然也是个Servlet),并且覆写了其中的doGetdoPost方法。

当客户端用get方式访问该Servlet时会导致doGet方法执行

当客户端用post方式访问该Servlet时会导致doPost方法执行

我们只需要写代码处理对应的处理逻辑即可。

很多时候我们的处理代码对于get方式的请求和post方式的请求的处理是相同的,此时可以在doPost中调用doGet();然后将处理代码写在doGet中。这样无论是get还是post请求都可以进行处理了。

编写代码,如图-15所示:

 

-15

4.2.5. 第五步:将web应用发布到tomcat

如图-16所示:

 

-16

启动tomcat,如图-17所示:

 

-17

4.2.6. 第六步:通过浏览器访问

由于web应用是发布到了webapps中即localhost虚拟主机中,所以按照如下方式访问,注意此处的/Demox不是工程名,而是发布时的指定的web应用名,如图-18所示:

 

-18

通过浏览器访问,如图-19所示:

 

-19

5. Servlet的继承结构

5.1. Serlvet继承结构

5.1.1. 概述

我们在手写Servlet时继承的是GenericServlet,而用Myeclipse生成的Servlet是继承了HttpServlet,那么他们之间的关系到底是什么样的呢?下面我们讨论下Servlet的继承结构:

5.1.2. Servlet接口

定义了一个servlet应该具有的方法,所有的Servlet都应该直接或间接实现此接口

5.1.3. GenericServlet抽象类

GenericServlet抽象类是对Servlet接口的默认实现,对Serlvet接口中的大部分方法都做了默认实现,只有service方法是一个抽象方法需要继承者自己实现。实现者只需要实现Service方法在其中写处理请求的代码即可。

5.1.4. HttpServlet

继承自GenericServlet类,在GenericServlet类的基础上对HTTP协议进行了优化,并且实现了其中的service抽象方法,在其中判断了请求的请求方式,并根据请求方式的不同分别调用不同的doXXX()方法。

通常我们在开发Servlet时,直接继承HttpServlet覆盖对应的doGet()doPost()方法即可,一般不推荐直接覆盖service()方法。

6. Servlet对外访问路径配置细节

6.1. Servlet配置细节

6.1.1. 基本配置

Servlet需要在web.xml中配置对外访问的路径。如图-20所示:

 

-20

其中:

<servlet>标签配置Servlet

<servlet-mapping>标签配置该Servlet的对外访问路径。

一个<servlet>可以对应多个<servlet-mapping>

6.1.2. 星号匹配符的使用

可以用*通配符配置<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

 

6.1.3. 缺省Servlet

路径中有一个特殊的配置/,如果一个servlet的对外访问路径被设置为/,则该servlet就是一个缺省servlet,其他servlet不处理的请求都由它来处理。

conf/web.xml(通用web.xml,参考tomcat章节)中配置了缺省servlet,对静态资源的访问和错误页面的输出就是由这个缺省servlet来处理的。

如果我们自己写一个缺省servlet把爸爸web.xml中的缺省servlet覆盖的话,会导致静态web资源无法访问。所以不推荐自己配置缺省Servlet

7. 创建EasyMall注册Servlet

7.1. 注册Servlet实现

7.1.1. 开发注册Servlet

在前面的学习过程中我们已近开发好了EasyMall的注册页,现在我们学完了Servlet,可以试着开发后台代码了。

首先我们将之前开发好的注册页面导入工程中,放置在web应用根目录下。

创建注册用的RegistServlet,并配置对外访问路径。如图-21所示:

 

-21

修改注册页面中注册表单的提交地址指向RegistServlet。如图-22所示:

 

-22

通过测试发现表单可以正确提交到Servlet中。如图-23所示:

 

-23

7.1.2. 引申出的问题

所谓的注册其实就是获取提交过来的请求参数中的注册信息,进行处理后保存在服务器数据库中。

遇到的第一个问题就是,如何获取客户端提交的请求参数。

我们注意看doGetdoPost方法,他们有两个参数,其中HttpServletRequest代表请求,HttpServletResponse代表Http响应。

既然我们需要的是请求参数,根据面向对象的思想,我们可以想见HttpServletRequest必然提供了对应的方法。

8. Request对象

8.1. Request的继承结构

8.1.1. Request的继承结构

我们查看api,如图-24所示:

 

-24

虽然我们一直简称为Request,但实际上代表请求的接口为ServletRequest,其中定义了http请求相关的方法。

ServletRequest有一个子类HttpServletRequest,在ServletRequest的基础上增加了很多和http协议相关的方法。

既然Request代表HTTP,那么Http请求相关的请求行请求头实体内容等信息都可以通过这个对象获得。

8.2. Request获取客户机信息

8.2.1. 通过request对象获取客户机相关的信息

getRequestURL方法 -- 返回客户端发出请求完整URL

getRequestURI方法 -- 返回请求行中的资源名部分

getQueryString方法 -- 返回请求行中的参数部分

getRemoteAddr方法 -- 返回发出请求的客户机的IP地址

getMethod方法 -- 返回客户机的请求方式

getContextPath方法 -- 获得当前web应用的虚拟目录名称

8.3. Request获取请求头信息

8.3.1. 通过request对象获取请求头相关的信息

getHeader(name)方法 --- String

getHeaders(String name)方法 --- Enumeration<String>

getHeaderNames方法 --- Enumeration<String>

getIntHeader(name)方法  --- int

getDateHeader(name)方法 --- long(日期对应毫秒)

8.4. Request获取请求参数和乱码解决

8.4.1. 获取请求参数。

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

8.4.2. 请求参数乱码的原因和解决方法

这些乱码是如何产生的呢?又该如何解决呢?

我们来画图分析一下乱码产生的原理。

 

-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

这种方式乱码的解决是从原理上手动编解码解决的乱码,对GETPOST方式的提交都有效。

9. EasyMall获取用户请求参数处理

9.1. Easymall注册功能请求参数处理

9.1.1. 代码实现

学会了如何处理请求参数,我们现在可以获取用户提交的注册相关信息了,如图-33所示:

 

-33

9.1.2. 引申出的问题

在如上处理中们将处理用的编码写死在了程序中,这是很不好的编程习惯,一旦未来需要修改工程的编码集还需要来修改源代码。

对于这种可能修改的程序相关的信息,最好不要写死在程序中,而应该采取可配置的方式配置到配置文件中。

我们当前可以自己写配置文件来读取,但其实我们也可以使用Servlet本身的机制将信息配置到web.xml中。

10. ServletConfig

10.1. ServletConfig概述

10.1.1. ServletConfig是什么

ServletConfig代表当前Servletweb.xml中的配置信息。

10.2. 获取ServletConfig

10.2.1. 获取ServletConfig

Servlet接口中init方法参数就是ServletConfig,在Servlet创建出来时,init方法立即被容器调用,由容器传入ServletConfig对象。

GenericServlet中,实现了这个方法,将ServletConfig设置成了类的成员变量,并提供了getServletConfig方法,获取该对象。

我们的Servlet都间接继承子GenericServlet,所以可以在自己的Servlet中直接调用getServletConfig方法获取这个对象。

10.3. ServletConfig获取初始化参数

10.3.1. 配置初始化参数

web.xml中的<servlet>标签中可以配置零个或多个<init-param>标签,用来为当前Servlet配置一些自定义的参数--称为Serlvet的初始化参数。如图-34所示:

 

-34

10.3.2. 通过ServletConfig获取初始化参数

ServletConfig身上提供了如下方法获取该Servlet上配置的初始化参数。如图-35所示:

 

-35

通过实验发现可以获取Servlet中配置的初始化参数。

如图-36所示:

 

-36

10.4. EasyMall将编码配置为初始化参数

10.4.1. 代码实现

我们在注册Serlvet中配置初始化参数保存采用的编码。如图-37所示:

 

-37

Servlet中通过读取初始化参数中配置的编码。如图-38所示:

 

-38

我们可以将编码集配置在web.xml中,从而实现可可配置的效果,以后即使需要修改工程编码,只需要改配置文件即可,不需要修改代码了。

10.4.2. 引伸出的问题

在获取了这些信息后,我们应该做一些数据的校验,比如验证码必须正确、所有字段都不能为空、两次密码必须一致、邮箱必须符合一定格式等。

为什么明明在前台js中已经进行过数据格式的校验还要再在后台进行校验呢?因为js的校验是客户端的校验,服务器是无法控制,用户可以通过一些方法--如禁用js—-避开类似的校验,这样数据的安全是无法保证的,在web开发中有一条原则,永远不要轻易的相信客户端提交的数据,所以我们后端还要再此对数据的格式进行校验。

那既然后台已经有了校验,是不是前台校验就可以去掉了呢?也不可以。前台校验可以在数据不发送到服务器端的情况下就对数据进行预先检查,减少了对服务器的访问,可以减轻服务器压力。并且前台校验可以更快更便捷的和用户进行交互,提供更好的用户体验。如图-39所示:

 

-39

对于未通过数据校验的请求我们需要向客户端输出错误信息,这就需要代表响应的对象Response了。

 

11. Response

11.1. Response概述

11.1.1. Response概述

Servlet中应该如何向用户输出数据呢?在doGetdoPost方法的参数中,HttpServletRequest代表的是http请求,而HttServletResponse代表的是http响应。想要获取请求中的信息时使用HttpServletRequest对象,而有数据需要发送给客户端时,就要用到HttpServletResponse对象了。

11.2. Response继承结构

11.2.1. Response继承结构

虽然我们经常简称为response,实际上是ServletResponse接口,其中定义了很多和响应对象相关的方法,HttpServletResponseServletResponse接口的子接口,在ServletResponse的基础上增加了很多和http协议相关的方法。如图-40所示:

 

-40

11.3. Response常用方法

11.3.1. 设置状态码

setStatus(int sc)

setStatus(int sc, String sm)

11.3.2. 设置响应头

setIntHeader(String name, int value)

setHeader(String name, String value)

setDateHeader(String name, long date)

11.3.3. 获取输出流

PrintWriter getWriter()

ServletOutputStream getOutputStream();

11.4. Response输出信息到客户端

11.4.1. 输出信息到客户端api

查询api,在Response向外输出数据的方法有如下两个:

PrintWriter getWriter()

ServletOutputStream getOutputStream();

其中getWriter获取的是字符流,可以输出字符数据到客户端。

getOutputStream获取的是字节流,可以输出字节数据到客户端。

我们现在要将字符数据发送给客户端,可以调用getWriter方法向其中输出数据

经测试可以正确的输出。如图-41所示:

 

-41

接着我们测试中文。发现输出时产生了乱码。如图-42所示:

-42

11.4.2. 响应乱码处理

这个乱码是如何产生的呢?乱码的产生大多是由于编码和解码时的码表不同产生的。

那么服务器是以什么码表来发送数据呢?我们发现乱码是以“?”的形式出现的。根据我们的经验,这种问题多半是由ISO8859-1编码导致的。

确实是的,如果不指定,服务器默认将用iso8859-1进行编码发送数据。浏览器用什么码表打开呢?一般来说如果不指定,浏览器默认会用所在的操作系统的平台码,我们当前的中文系统中,默认就是使用GB2312作为解码码表的。

首先iso8859-1中没有中文,对于无法表示的字符,iso8859-1会用“?”来替代,所以真正发送给浏览器的数据其实是“?”,世界上所有的码表都默认兼容iso8859-1,所以gb2312认识,显示为了“?”。如图-43所示:

 

-43

在解决这个问题时,可以通过设置response.setCharacterEncoding(gbk)来指定服务器发送数据时使用的码表。同时要注意,此行代码必须出现在任何输出数据的代码之前,如果在这行代码之前已经有任何数据写入给了response,则此行代码无效。

设置过后再重新测试。发现仍然是乱码,但不再是“??”而是变成了“”。如图-44所示:

 

-44

这种类型的乱码是怎么发生的呢?我们接着分析。服务器用utf-8发送数据给浏览器,而浏览器用平台码(当前为gbkgbk打开自然产生了乱码。如图-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

11.4.3. Response输出数据时的细节

1getOutputStreamgetWriter这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一方法。      2Servlet程序向ServletOutputStreamPrintWriter对象中写入的数据将被Servlet引擎从response里面获取,Servlet引擎将这些数据当作响应消息的正文,然后再与响应状态行和各响应头组合后输出到客户端。     3Serlvetservice方法结束后,Servlet引擎将检查getWritergetOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,Servlet引擎tomcat将调用close方法关闭该输出流对象。

11.5. YCMall提示信息实现

11.5.1. 代码实现

根据上面所学关于Resonse的知识,我们现在可以修改程序在校验数据出错时,向外输出提示信息了。如图-48所示:

 

-48

11.5.2. l引申出的问题

对于成功通过了数据校验的请求,我们应该将数据存储到数据库中,我们目前还没有学习数据库,所以可以用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("^\\w+@\\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

这是为什么呢?

我们观察到,如果写一个相对路径程序是到tomcatbin目录中寻找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了。

实现重定向 (!!重点)

重定向的原理就是302+location, 通过设置状态码302location响应头就可以实现重定向的效果

response.setStatus(302);

response.setHeader("Location", "/Day09/index.jsp");

这两行代码等价于

response.sendRedirect("/Day09/index.jsp");

定时刷新

定时刷新是通过Refresh响应头, 可以实现在多少秒之后跳转到另外一个资源

response.setHeader("Refresh", "3;url=/Day09/index.jsp");

 

 

总结

请求转发/请求重定向/定时刷新都可以实现资源的跳转, 区别是什么呢?

 

Ø 请求转发:

一次请求,一次响应 request对象是同一个

地址栏不会发生变化

只能用于服务器内部的资源跳转, 并且只能是同一应用中的不同资源上进行跳转, 不可用在不同应用和不同服务器中的资源跳转

可以共享request数据

 

Ø 请求重定向:

两次请求,两次响应 request对象不是同一个

地址栏会发生变化

可以用于服务器内部的资源跳转, 也可以用于不同应用和不同服务器之间的资源跳转

request不能共享数据

 

Ø 定时刷新:

两次请求,两次响应 request对象不是同一个

地址栏会发生变化

可以用于服务器内部的资源跳转, 也可以用于不同应用和不同服务器之间的资源跳转

和重定向不同的是, 定时刷新可以在刷新到新的地址之间设置一个时间, 在间隔的这段时间内可以输出文本到浏览器并维系一段时间

 

那什么时候用哪种方式进行资源的跳转呢?

(1)如果是同一服务器中的同一应用内部的资源跳转:

~如果需要利用request域在跳转的资源之间传输数据, 只能用请求转发

~如果不想让地址栏发生变化, 只能用请求转发

~如果需要地址栏发生变化, 只能用重定向或定时刷新

~如果没有什么特殊需求, 三种方式都可以, 但是推荐使用转发, 可以减少请求次数降低服务器的压力.

 

(2)如果是不同服务器或不同应用内部的资源跳转, 只能用重定向或这定时刷新:

重定向和定时刷新的主要区别在于: 重定向会立即跳转, 而定时刷新可以设置一个时间间隔, 在指定时间后再进行跳转.

如果在跳转之前需要输出提示信息(: 注册成功, xx秒后跳转到xxx)只能用定时刷新, 否则两种方式都可以.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值