Java web-Request、Response、ServletConfig、ServletContext

1.Request

代表http请求的对象

1.1.继承结构

ServletRequest – 提供一个request对象最基本的功能

|

|-- HttpServletRequest – 继承了ServletRequest接口, 并在其基础上添加了很多和Http协议相关的方法


1.2.request的功能

1.2.1.获取客户端的相关的信息

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

如: http://localhost/day09/servlet/SecondServlet

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

如: /day09/servlet/SecondServlet

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

如: username=zhangfei&password=123

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

如: 127.0.0.1  //可能会出现0:0:0:0:0:0:0:1形式,是ipv6的表现形式。

getMethod -- 得到客户机请求方式

如: GET或POST

!!getContextPath -- 获得当前web应用虚拟目录名称

如: /day09

注意:在写路径时不要将web应用的虚拟路径的名称写死, 应该在需要写web应用的名称的地方通过getContextPath方法动态获取

1.2.2.获取请求头信息

getHeader(name)方法 --- String

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

可以通过遍历枚举遍历每一个信息

例如:while (values.hasMoreElements()) {

String value = (String) values.nextElement();

System.out.println(value);

}

getHeaderNames方法 --- Enumeration<String>

getIntHeader(name)方法  --- int

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

1.2.3.获取请求参数

getParameter(String name) --- String 通过name获得值

getParameterValues(String name)  --- String[ ] 通过name获得多值 checkbox

getParameterMap()  --- Map<String,String[ ]> key :name value: 多值

getParameterNames()  --- Enumeration<String> 获得所有name

请求参数中的乱码问题

乱码分析: 编码时和解码时使用的码表不一致造成的!!

编码: 是在浏览器进行的, 浏览器发送数据使用的是什么编码?  

浏览器在打开当前页面时使用的是什么码表,也会使用相同的码表来发送数据.

打开页面使用utf-8, 所以发送数据也是用utf-8码表

解码: 是在服务器端进行的, 服务器端接收数据使用的又是什么编码?

如果不指定, 服务器会使用默认的码表来接收浏览器发送过来的数据, 默认的码表是iso8859-1.

解决方案:

乱码造成的原因是编码不一致, 所以应该让两端的编码保持一致!, 应该通知服务器使用utf-8来接受客户端发送过来的数据!!

request.setCharacterEncoding(“utf-8”);//用来通知服务器使用什么编码来接受请求实体内容中的数据, 如果使用的是POST提交, POST提交的请求参数就是在请求实体内容中!, 所有这个方法可以解决POST提交的乱码问题!!!

GET提交的请求参数由于不在请求实体内容中,而是在请求行中的请求资源路径后面拼接着, 所以这行代码对GET提交的参数乱码不起作用!!!

GET提交的参数乱码问题该如何解决???

GET提交的乱码问题可以通过手动编解码来解决!!

//>>username为乱码, 通过乱码反向编码得回二进制数组

byte[] bytes = username.getBytes("iso8859-1");

//>>通过二进制数组查询正确的码表, 得出正确的数据

username = new String(bytes, "utf-8");

1.2.4.实现请求转发

请求重定向: 302状态码+location响应头

请求转发: 和请求重定向都可以实现资源的跳转, 但是区别是请求转发是服务器内部的并且是同一个WEB应用内部的资源跳转

请求转发的特点:

一次请求对应一次响应

地址栏地址不会发生变化

请求转发只能在同一个WEB应用内部资源之间进行跳转! 不能是不同的WEB应用或者不同的主机!

实现代码:

 创建servlet:RequestDemo3和RequestDemo4,RequestDemo3转发到RequestDemo4
  在RequestDemo3中:
/*
 * 在web阶段写路径时,除了请求转发和请求包含在写
 * 路径是不用包含web应用的虚拟路径,其他地方都需要
 * 加上web应用的虚拟路径。
 * 完整路径:http://localhost/day09/servlet/RequestDemo4
 */
request.getRequestDispatcher("/servlet/RequestDemo4").forward(request, response);
 
  在RequestDemo4中响应:
       response.getWriter().write("1$")

request开发细节:

在转发之前, 如果response缓冲区被写入了数据但是还没有打给浏览器, 在转发时response缓冲区(数据)将会被清空!

例如:在RequestDemo3中添加代码:

response.getWriter().write("no money!");

发现浏览器中并未得到"no money!"这段字符串的响应。

在转发之前, 如果response缓冲区被写入了数据并且已经打给了浏览器, 转发将会失败!!

例如:在RequestDemo3中添加代码:

response.getWriter().write("no money!");

response.flushBuffer();

发现浏览器可以显示"no money!",因为强制刷新了,但是转发就会报错,因为已经响应过了,一次请求对应一次响应。

 

在同一个Servlet中转发不能进行多次!!(A既转发B, 又转发给C)

但是可以进行多重转发(比如A转发给B, B再转发给C)

1.2.5.作为域对象来使用

域对象:如果一个对象具有一个可以被看见的范围, 利用该对象上的map可以在整个范围内实现资源的共享!

域对象提供的方法(可以操作map中的数据):

setAttribute(String name, Object value);  用来存储一个对象,也可以称之为存储一个域属性

getAttribute(String name);   用来获取request中的数据

removeAttribute(String name);   用来移除request中的域属性

getAttributeNames();   获取所有域属性的名称

 

生命周期:

一次请求开始时创建request对象, 一次请求结束时销毁request对象

作用范围:

整个请求链

主要功能:

在整个范围内共享数据

带数据到目的地

 

例如:在RequestDemo3中可以设置一些属性,比如是从数据库查询出来的数据,转发到RequestDemo4后,在RequestDemo4中从request域中获取数据,并打印到页面中。

1.2.6.实现请求包含

请求包含是服务器内部资源合并的现象


如果浏览器访问Servlet A, 但是A不能独立的处理这次请求, 需要另外一个Servlet B帮忙, 于是在A中可以将B包含进来, 包含的代码如下:

request.getRequestDispatcher(“B的路径”).include(request, response);

 

将B包含进来后, 将会由A和B共同来处理这次请求, 处理的结果也会合并在一起, 一起打给浏览器!

2.Response

2.1.Response概述

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

客户端基于HTTP协议访问服务器时,服务器创建代表请求的Request对象和代表响应的Response对象
Request对象包含了很多操作请求相关数据的方法

Response对象包含了很多操作响应数据的方法

2.2.Response继承结构

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

2.3. Response常用方法

2.3.1. 设置状态码

setStatus(int sc)

setStatus(int sc, String sm)

2.3.2. 设置响应头

setIntHeader(String name, int value)

setHeader(String name, String value)

setDateHeader(String name, long date)

2.3.3. 获取输出流

PrintWriter getWriter()

ServletOutputStream getOutputStream();

2.4. Response输出信息到客户端

2.4.1. 输出信息到客户端api

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

PrintWriter getWriter()
ServletOutputStream getOutputStream();
其中getWriter获取的是字符流,可以输出字符数据到客户端。
getOutputStream获取的是字节流,可以输出字节数据到客户端。

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

经测试可以正确的输出。如图:

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



2.4.2. 响应乱码处理

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

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

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

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

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

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


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

这种乱码的产生是由于浏览器没有使用正确的编码打开造成的,那么我们该如何控制浏览器用指定码表打开数据呢?

在http协议中有一个响应头叫做Content-Type可以用来通知浏览器当前服务器发送的数据的格式,如果是字符格式的数据还可以指定解析时使用的码表。所以我们可以通过如下方法通知浏览器用指定码表打开发送的数据,代码如下,经测试没有乱码。

我们通过response.setHeader("Content-Type", "text/html;charset=utf-8");通知服务器发送数据时的码表。

通过response.setCharacterEncoding("utf-8");通知浏览器解析时使用的码表。

两码相同就不会有乱码了。

如图:

另外response提供了setContentType()快捷方法,在它的底层,会同时做上面两件事,所以可以一行代码解决response产生的乱码问题。如图:

2.4.3. Response输出数据时的细节

(1)getOutputStream和getWriter这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一方法。

(2)Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据将被Servlet引擎从response里面获取,Servlet引擎将这些数据当作响应消息的正文,然后再与响应状态行和各响应头组合后输出到客户端。

(3)Serlvet的service方法结束后,Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,Servlet引擎tomcat将调用close方法关闭该输出流对象。

1.5.Response实现重定向

在HTTP协议中提供了302状态码和Location响应头,通知浏览器收到响应后立即自动访问Location中指定的地址,从而跳转访问另一个资源。

 

response.setState(302);
response.setHeader(“Location”,“。。。”);

经测试可以实现跳转效果。

同时我们打开HTTPWatch,发现在整个重定向的过程中发生了两次请求响应。

1.6.Response实现定时刷新

在HTTP协议中,提供了refresh响应头,可以命令浏览器经过多少秒刷新页面到哪个地址。

我们经常见到这样的效果,当注册成功后提示“恭喜您注册成功。。3秒后回到主页。。”,紧接着经过3秒回到主页。这就是定时刷新的效果。

通过如下代码实现。

1.7.Response实现禁止缓存

浏览器为了减少对服务器的访问,会在第一次访问到资源后进行缓存,之后再访问,就直接用缓存中的数据,减少对服务器的访问。

这种缓存机制,提高了浏览器的响应数度,减少了服务器的访问量,在大多数情况下是好的,但是在某些场景下可能有问题。

比如:验证码 实时的数据 – 火车票余量 股票价格。。。

response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
===========================================================================

 资源的三种条转方式:
请求转发:
在服务器内发生的资源跳转
一次请求一次响应,一个request对象,可以用request域在转发时传递数据
浏览器不知道请求转发的存在 地址栏仍然是原来的地址 刷新操作时仍然访问的是第一个资源的地址
地址是内部使用地址 不需要写应用名称
request.getRequestDispatcher("/index.jsp").forward(request,response);
请求重定向:
在服务器外发生的资源跳转
两次请求两次响应,两个request对象,不可以用request域在重定向时传递数据
浏览器知道请求重定向的存在 地址栏发生变化 刷新操作时访问的是最后一个资源地址
地址是外部使用地址  一定要写应用名称
response.sendRedirect("/Day10/index.jsp")


定时刷新:
在服务器外发生的资源跳转
两次请求两次响应,两个request对象,不可以用request域在重定向时传递数据
浏览器知道定时刷新的存在 地址栏发生变化 刷新操作时访问的是最后一个资源地址
地址是外部使用地址  一定要写应用名称
response.setHeader("refresh","3;url=/Day10/index.jsp")


        三种跳转方式的选择:
        如果需要利用request域传递数据 --> 必须请求转发
        如果想要在资源跳转时改变地址栏 或 刷新操作 -->  请求重定向 或 定时刷新
        如果想在资源跳转时,向用户提示一些信息 --> 定时刷新

如果以上三种需求都没有,三种方案都可以时,优先使用 请求转发,因为请求转发访问服务器的次数少,减轻服务器压力。

============================================================================

3.ServletConfig

3.1.概述

每个Servlet都有一个ServletConfig对象 代表当前Servlet在web.xml中的配置信息

3.2.获取ServletConfig

Servlet在初始化时候 init方法中传入了ServletConfig对象
我们通常写的Servlet继承自HttpServlet - GenericServlet - Servlet
在GenericServlet中 实现了 Servlet接口定义的init方法 在其中将ServletConfig对象保存到了类的内部 比提供了 getServletConfig方法 返回该对象

所以作为GenericServlet的子孙类 可以直接调用getServletConfig方法来获取次对象

3.3.ServletConfig的功能

3.3.1.获取Servlet名称

String getServletName() 

3.3.2.获取初始化参数

在Servlet在web.xml中的M<servlet>配置中 可以通过配置<init-param>来 配置当前Servlet的初始化参数 再在程序中通过ServletConfig对象来获取 从而 将一些不想写死在Servlet中的配置信息 提取到web.xml的当前Servlet的配置中
String getInitParameter(String name) 获取当前Servlet指定名称的初始化参数的值

Enumeration getInitParameterNames()  获取当前Servlet所有初始化参数的名字的枚举

4.ServletContext

4.1.概述

代表当前web应用

4.2.ServletContext生命周期

当服务器启动时,服务器在启动时会依次加载web应用,每一个web应用加载完成后都会创建一个ServletContext对象唯一代表该web应用,这个对象一直存活,直到web应用移除出容器或服务器关闭时,随着应用销毁,ServletContext对象跟着销毁。
ServletContext一个应用只有一个,唯一的代表当前web应用,生命周期和web应用一样,web应用创建它就创建 web应用销毁它就跟着被销毁。

4.3.获取ServletContext

方法1:
ServletConfig.getServletContext();
方法2:

this.getServletContext();

4.4.ServletContext功能

4.4.1.读取web应用初始化参数

可以在web.xml的根目录下 通过<context-param>来为整个web应用配置初始化参数
这些初始化参数可以通过ServletContext对象来获取
String getInitParameter(String name) 获取当前web应用指定名称的初始化参数的值

Enumeration getInitParameterNames()  获取当前web应用所有初始化参数的名字的枚举

4.4.2.作为域对象来使用

生命周期
和web应用命一样长
作用范围
整个web应用
主要功能
在整个web应用范围内 整个web应用生命周期范围内 共享数据

setAttribute(String name,Object obj);//向域中设置数据
Object getAttribute(String name);//从域中获取数据
removeAttribute(String name);//从域中移除数据

4.4.3.加载web资源

(1)路径难题

在web开发时,如果需要读取资源文件,需要写文件路径
如果写相对路径 - 则到当前程序的启动目录 tomcat/bin 下找文件 找不到 报错
如果写绝对路径 - 则到当前程序的启动目录 的根目录 下找文件 找不到 报错
如果写盘符开始的绝对路径 - 则从盘符开始找 能够找到这个文件 但是这种方法 将路径写死 一旦换一个发布环境 就无法使用了 所以不推荐

那么路径到底应该如何写呢?

(2)ServletContext解决路径难题

servletContext.getRealPath("xxxx")
此方法将会 在传入的路径前 拼接当前web应用的 盘符开始的绝对路径 从而得到 指定资源的盘符开始的绝对路径  从而访问到资源
因为应用的路劲格式getRealPath方法动态获取拼接的 传入的仅仅是资源相对于web应用根目录的路径 所以 并没有将路径写死 所以即使换了发布环境 路径也仍然正确

(3)利用类加载器加载资源

在web开发时,有时无法拿到ServletContext对象,此时如何解决路径难题呢?此时可以使用类加载器加载资源文件类加载器时虚拟机用来加载类的类此类额外提供了方法 getResource 来获取资源的真实路径需要注意的是,这个方法传入的路径必须是资源相对于类加载器默认加载类的路径