一、servlet的认识
1.1 servlet的基本概念和应用
(1)什么是servlet?
- servlet是SUN公司提供的一套规范,名称就叫servlet规范,它也是JavaEE规范之一,我们可以像学习Java基础一样,通过api来学习servlet,这里需要注意的是,在我们之前jdk的api中是没有servlet规范的相关内容的,需要使用JavaEE的api。目前在Oracle官网中的最新版本是JavaEE8,该网址中介绍了JavaEE8的一些新特性,当然我们也可以通过访问官方api,学习和查阅里面的内容。
- 打开官方api网址,在左上方部分找到javax.servlet包,在左下方部分找到servlet,如下图所示:
- 由此可知:
- ①servlet是一个运行在web服务端的java小程序
- ②它可以用于接收和响应客户端的请求
- ③要实现servlet功能,可以实现servlet接口或者继承GenericServlet功能类或者继承HttpServlet功能类。
- ④每次请求都会执行service方法
- ⑤servlet还支持配置
(2)servlet的入门
- servlet编码的步骤
- 第一步:前期准备,创建JavaWeb工程
- 第二步:编写一个普通的类继承GenericServlet并重写service方法
- 第三步:在web.xml配置servlet
- 第四步:在tomcat中部署项目
- 第五步:启动tomcat并用浏览器访问测试。
(2)servlet执行过程的分析
- 我们通过浏览器发送请求,请求首先到达Tomcat服务器,由服务器解析请求URL,然后在部署的应用列表中找到我们的应用。接下来,在我们的应用中找应用里的web.xml配置文件,在web.xml中找到FirstServlet的配置,找到后执行service方法,最后由FirstServlet响应客户浏览器。整个过程如下图所示:
一句话总结执行过程:
在Servlet的API介绍中,它提出了我们除了继承GenericServlet外还可以继承HttpServlet,通过查阅servlet的类视图,我们看到GenericServlet还有一个子类HttpServlet。同时,在service方法中还有参数ServletRequest和ServletResponse,它们的关系如下图所示:
(4)servlet编写方式
编写方式说明
- 第一种:实现Servlet接口,接口中的方法必须全部实现。 使用此种方式,表示接口中的所有方法在需求方面都有重写的必要。此种方式支持最大程度的自定义。
- 第二种:继承GenericServlet,service方法必须重写,其他方可根据需求,选择性重写。使用此种方式,表示只在接收和响应客户端请求这方面有重写的需求,而其他方法可根据实际需求选择性重写,使我们的开发Servlet变得简单。但是,此种方式是和HTTP协议无关的。
- 第三种:继承HttpServlet,它是javax.servlet.http包下的一个抽象类,是GenericServlet的子类。如果我们选择继承HttpServlet时,只需要重写doGet和doPost方法,不要覆盖service方法。使用此种方式,表示我们的请求和响应需要和HTTP协议相关。也就是说,我们是通过HTTP协议来访问的。那么每次请求和响应都符合HTTP协议的规范。请求的方式就是HTTP协议所支持的方式(目前我们只知道GET和POST,而实际HTTP协议支持7种请求方式,GET、POST、PUT、DELETE、TRACE、OPTIONS、HEAD )。(这种是比较常用的)
HttpServlet使用细节
- 第一步:自定义类继承HttpServlet;注意:不写任何的方法。如下图所示:
- 第二步:部署项目并测试
- 当我们在地址栏输入ServletDemo2的访问URL时,出现了访问错误,状态码是405。提示信息是:方法不允许。
- 第三步:分析原因
- 我们继承了HttpServlet,需要重写里面的doGet和doPost方法来接收get方式和post方式的请求。
- 为了实现代码的可重用性,我们只需要在doGet或者doPost方法中一个里面提供具体功能即可,而另外的那个方法只需要调用提供了功能的方法。
1.2 servlet使用细节
(1)servlet的生命周期
- 对象的生命周期,就是对象从生到死的过程,即:出生——活着——死亡。用更偏向于开发的官方说法就是对象创建到销毁的过程。
- 出生:请求第一次到达Servlet时,对象就创建出来,并且初始化成功。只出生一次,就放到内存中
- 活着:服务器提供服务的整个过程中,该对象一直存在,每次只是执行service方法。
- 死亡:当服务停止时,或者服务器宕机时,对象消亡
通过分析Servlet的生命周期我们发现,它的实例化和初始化只会在请求第一次到达Servlet时执行,而销毁只会在Tomcat服务器停止时执行,由此我们得出一个结论,Servlet对象只会创建一次,销毁一次。所以,Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就说它是单实例的,即运用了单例模式。
(2)servlet的线程安全
由于Servlet运用了单例模式,即整个应用中只有一个实例对象,所以我们需要分析这个唯一的实例中的类成员是否线程安全。接下来,我们来看下面的的示例:
/**
* 演示Servlet的线程安全问题:
* 示例需求:
* 模拟网上看书的翻页功能。
* (类似的有浏览商品的翻页,浏览论坛帖子的翻页)
* @author HausenLee
* @Company http://www.xiaoha.com
*/
public class ServletDemo4 extends HttpServlet {
/**
* 我们讨论的是类成员的线程安全问题,所以要定义一个类成员
*/
//定义浏览书籍的页码,都是从第一页开始的(公共资源)
private int currentPage = 1;
/**
* 真正翻页看书的功能
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取当前要看的书名(此处我们今天先来用以下,明天来着重讲解请求和响应对象)
String bookName = req.getParameter("bookName");
//2.输出书名和当前页码
System.out.println("您看的是:"+bookName+",当前页码是:"+currentPage);
//3.执行翻页
currentPage++;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
这是一个模拟在网上看书的示例,我们在Servlet中记录了当前要看的页码,理想状态下,用户每次请求都来看自己该看的页码。启动服务,测试一下:
- 通过上面的测试我们发现,在Servlet中定义了类成员之后,多个浏览器都会共享类成员的数据。其实每一个浏览器端发送请求,就代表是一个线程,那么多个浏览器就是多个线程,所以测试的结果说明了多个线程会共享Servlet类成员中的数据,其中任何一个线程修改了数据,都会影响其他线程。因此,我们可以认为Servlet它不是线程安全的。
- 分析产生这个问题的根本原因,其实就是因为Servlet是单例,单例对象的类成员只会随类实例化时初始化一次,之后的操作都是改变,而不会重新初始化。
- 解决这个问题也非常简单,就是在Servlet中定义类成员要慎重。如果类成员是共用的,并且只会在初始化时赋值,其余时间都是获取的话,那么是没问题。如果类成员并非共用,或者每次使用都有可能对其赋值,那么就要考虑线程安全问题了,把它定义到doGet或者doPost方法里面去就可以了。
(3)servlet的注意事项
映射servlet的细节
Servlet支持三种映射方式,以达到灵活配置的目的。首先编写一个Servlet,代码如下:
/**
* 演示Servlet的映射方式
* @author HausenLee
* @Company http://www.xiaoha.com
*/
public class ServletDemo5 extends HttpServlet {
/**
* doGet方法输出一句话
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletDemo5接收到了请求");
}
/**
* 调用doGet方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
第一种:指名道姓方式
- 此种方式,只有和映射配置一模一样时,Servlet才会接收和响应来自客户端的请求。
- 例如:映射为:/servletDemo5 访问URL:http://localhost:8585/servlet_demo/servletDemo5
- 第二种:/开头+通配符的方式
- 此种方式,只要符合目录结构即可,不用考虑结尾是什么。
- 例如:映射为:/servlet/* 访问URL:http://localhost:8585/servlet/itheima http://localhost:8585/servlet/itcast.do
- 第三种:通配符+固定格式结尾
- 此种方式,只要符合固定结尾格式即可,其前面的访问URI无须关心(注意协议,主机和端口必须正确)
- 例如:映射为:*.do 访问URL:http://localhost:8585/servlet/itcast.do http://localhost:8585/itheima.do
- 通过测试我们发现,Servlet支持多种配置方式,但是由此也引出了一个问题,当有两个及以上的Servlet映射都符合请求URL时,由谁来响应呢?注意:HTTP协议的特征是一请求一响应的规则。那么有一个请求,必然有且只有一个响应。所以,我们接下来明确一下,多种映射规则的优先级。
先说结论:指名道姓的方式优先级最高,带有通配符的映射方式,有/的比没/的优先级高;所以,我们前面讲解的三种映射方式的优先级为:第一种>第二种>第三种。
(4)servlet多路径映射
上一小节我们讲解了Servlet的多种映射方式,这一小节我们来介绍一下,一个Servlet的多种路径配置的支持。
- 它其实就是给一个Servlet配置多个访问映射,从而可以根据不同请求URL实现不同的功能。
首先,创建一个Servlet:
/**
* 演示Servlet的多路径映射
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class ServletDemo7 extends HttpServlet {
/**
* 根据不同的请求URL,做不同的处理规则
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取当前请求的URI
String uri = req.getRequestURI();
uri = uri.substring(uri.lastIndexOf("/"),uri.length());
//2.判断是1号请求还是2号请求
if("/servletDemo7".equals(uri)){
System.out.println("ServletDemo7执行1号请求的业务逻辑:商品单价7折显示");
}else if("/demo7".equals(uri)){
System.out.println("ServletDemo7执行2号请求的业务逻辑:商品单价8折显示");
}else {
System.out.println("ServletDemo7执行基本业务逻辑:商品单价原价显示");
}
}
/**
* 调用doGet方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
接下来,在web.xml配置Servlet:
<!--配置ServletDemo7-->
<servlet>
<servlet-name>servletDemo7</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo7</servlet-class>
</servlet>
<!--映射路径1-->
<servlet-mapping>
<servlet-name>servletDemo7</servlet-name>
<url-pattern>/demo7</url-pattern>
</servlet-mapping>
<!--映射路径2-->
<servlet-mapping>
<servlet-name>servletDemo7</servlet-name>
<url-pattern>/servletDemo7</url-pattern>
</servlet-mapping>
<!--映射路径3-->
<servlet-mapping>
<servlet-name>servletDemo7</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
最后,启动服务测试运行结果:
(5)启动tomcat时创建servlet
- 我们前面讲解了Servlet的生命周期,Servlet的创建默认情况下是请求第一次到达Servlet时创建的。但是我们都知道,Servlet是单例的,也就是说在应用中只有唯一的一个实例,所以在Tomcat启动加载应用的时候就创建也是一个很好的选择。那么两者有什么区别呢?
- 第一种:应用加载时创建Servlet,它的优势是在服务器启动时,就把需要的对象都创建完成了,从而在使用的时候减少了创建对象的时间,提高了首次执行的效率。它的弊端也同样明显,因为在应用加载时就创建了Servlet对象,因此,导致内存中充斥着大量用不上的Servlet对象,造成了内存的浪费。
- 第二种:请求第一次访问是创建Servlet,它的优势就是减少了对服务器内存的浪费,因为那些一直没有被访问过的Servlet对象都没有创建,因此也提高了服务器的启动时间。而它的弊端就是,如果有一些要在应用加载时就做的初始化操作,它都没法完成,从而要考虑其他技术实现。
通过上面的描述,能分析得出何时采用第一种方式,何时采用第二种方式。就是当需要在应用加载就要完成一些工作时,就需要选择第一种方式。当有很多Servlet的使用时机并不确定是,就选择第二种方式。在web.xml中是支持对Servlet的创建时机进行配置的,配置的方式如下:我们就以ServletDemo3为例。
<!--配置ServletDemo3-->
<servlet>
<servlet-name>servletDemo3</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo3</servlet-class>
<!--配置Servlet的创建顺序,当配置此标签时,Servlet就会改为应用加载时创建
配置项的取值只能是正整数(包括0),数值越小,表明创建的优先级越高
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletDemo3</servlet-name>
<url-pattern>/servletDemo3</url-pattern>
</servlet-mapping>
(6)默认servlet
默认Servlet是由服务器提供的一个Servlet,它配置在Tomcat的conf目录下的web.xml中。如下图所示:
- 它的映射路径是
/ ,我们在发送请求时,首先会在我们应用中的web.xml中查找映射配置,找到就执行,这块没有问题。但是当找不到对应的Servlet路径时,就去找默认的Servlet,由默认Servlet处理。所以,一切都是Servlet。 - servlet关系总图
1.3 servletconfig
(1)servletconfig的概述
- 它是servlet的配置参数对象,在servlet规范中,允许为每个servlet都提供一些初始化配置。所以每个servlet都有一个自己的servletconfig。它的作用是在servlet初始化期间,把一些配置信息传递给servlet。
servletconfig的生命周期:
servletconfig的获取:
首先,我们要清楚的认识到,它可以为每个Servlet都提供初始化参数,所以肯定可以在每个Servlet中都配置。那是配置在Servlet的声明部分,还是映射部分呢?我们接下来先准备一个Servlet
/**
* 演示Servlet的初始化参数对象
* @author HausenLee
* @Company http://www.xiaoha.com
*/
public class ServletDemo8 extends HttpServlet {
//定义Servlet配置对象ServletConfig
private ServletConfig servletConfig;
/**
* 在初始化时为ServletConfig赋值
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
this.servletConfig = config;
}
/**
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//输出ServletConfig
System.out.println(servletConfig);
}
/**
* 调用doGet方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
<!--配置ServletDemo8-->
<servlet>
<servlet-name>servletDemo8</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo8</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletDemo8</servlet-name>
<url-pattern>/servletDemo8</url-pattern>
</servlet-mapping>
配置servletconfig参数
- 在
标签中的 标签来配置。servlet的初始化参数都是配置在servlet的声明部分的(即在 标签中配置)。并且每个servlet都支持多个初始化参数,并且初始化参数都是以键值对的形式存在的。 <!--配置ServletDemo8-->
<servlet>
<servlet-name>servletDemo8</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo8</servlet-class>
<!--配置初始化参数-->
<init-param>
<!--用于获取初始化参数的key-->
<param-name>encoding</param-name>
<!--初始化参数的值-->
<param-value>UTF-8</param-value>
</init-param>
<!--每个初始化参数都需要用到init-param标签-->
<init-param>
<param-name>servletInfo</param-name>
<param-value>This is Demo8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>servletDemo8</servlet-name>
<url-pattern>/servletDemo8</url-pattern>
</servlet-mapping>
- 在
(3)servletconfig常用的方法
- 常用方法
/**
* 演示Servlet的初始化参数对象
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class ServletDemo8 extends HttpServlet {
//定义Servlet配置对象ServletConfig
private ServletConfig servletConfig;
/**
* 在初始化时为ServletConfig赋值
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
this.servletConfig = config;
}
/**
* doGet方法输出一句话
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.输出ServletConfig
System.out.println(servletConfig);
//2.获取Servlet的名称
String servletName= servletConfig.getServletName();
System.out.println(servletName);
//3.获取字符集编码
String encoding = servletConfig.getInitParameter("encoding");
System.out.println(encoding);
//4.获取所有初始化参数名称的枚举
Enumeration<String> names = servletConfig.getInitParameterNames();
//遍历names
while(names.hasMoreElements()){
//取出每个name
String name = names.nextElement();
//根据key获取value
String value = servletConfig.getInitParameter(name);
System.out.println("name:"+name+",value:"+value);
}
//5.获取ServletContext对象
ServletContext servletContext = servletConfig.getServletContext();
System.out.println(servletContext);
}
/**
* 调用doGet方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
1.4 servletcontext
(1)servletcontext的概述
- ServletContext对象,它是应用上下文对象。每一个应用有且只有一个ServletContext对象。它可以实现让应用中所有Servlet间的数据共享。
- servletcontext的生命周期
- 出生: 应用一加载,该对象就被创建出来了。一个应用只有一个实例对象。(Servlet和ServletContext都是单例的)
- 活着:只要应用一直提供服务,该对象就一直存在。
- 死亡:应用被卸载(或者服务器挂了),该对象消亡。
(2)域对象概念
- 域对象的概念,它指的是对象有作用域,即有作用范围。
- 域对象的作用,域对象可以实现数据共享。不同作用范围的域对象,共享数据的能力不一样。
- 在Servlet规范中,一共有4个域对象。ServletContext就是其中一个。它也是我们接触的第一个域对象。它是web应用中最大的作用域,叫application域。每个应用只有一个application域。它可以实现整个应用间的数据共享功能。
(3)servletcontext的使用
如何获取
通过servletconfig对象,调用getServletContext()方法就可以了。
/**
* 用于演示ServletContext对象的使用
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class ServletDemo9 extends HttpServlet {
//定义Servlet配置对象ServletConfig
private ServletConfig servletConfig;
/**
* 在初始化时为ServletConfig赋值
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
this.servletConfig = config;
}
/**
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取ServletContext对象
ServletContext servletContext = servletConfig.getServletContext();
System.out.println(servletContext);
}
/**
* 调用doGet方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
<!--配置ServletDemo9-->
<servlet>
<servlet-name>servletDemo9</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo9</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletDemo9</servlet-name>
<url-pattern>/servletDemo9</url-pattern>
</servlet-mapping>
在实际开发中,如果我们每个Servlet对ServletContext都使用频繁的话,那么每个Servlet里定义ServletConfig,再获取ServletContext的代码将非常多,造成大量的重复代码。Servlet规范的定义中也为我们想到了这一点,所以它在GenericServlet中,已经为我们声明好了ServletContext获取的方法,如下图所示:
- 我们的Servlet都是继承自HttpServlet,而HttpServlet又是GenericServlet的子类,所以我们在获取ServletContext时,如果当前Servlet没有用到它自己的初始化参数时,就可以不用再定义初始化参数了,而是直接改成下图所示的代码即可:
- 如何配置
- ServletContext既然被称之为应用上下文对象,所以它的配置是针对整个应用的配置,而非某个特定Servlet的配置。它的配置被称为应用的初始化参数配置。
- 配置的方式,需要在
标签中使用 来配置初始化参数。具体代码如下: <!--配置应用初始化参数-->
<context-param>
<!--用于获取初始化参数的key-->
<param-name>servletContextInfo</param-name>
<!--初始化参数的值-->
<param-value>This is application scope</param-value>
</context-param>
<!--每个应用初始化参数都需要用到context-param标签-->
<context-param>
<param-name>globalEncoding</param-name>
<param-value>UTF-8</param-value>
</context-param>
1.5 servlet注解开发
(1)servlet3.0规范
- 首先,我们要先跟同学们明确一件事情,我们在《Tomcat和HTTP协议》课程中已经介绍了,我们使用的是Tomcat9,JavaEE规范要求是8,对应的Servlet规范规范应该是JavaEE8包含的4.x版本。
- 在企业级应用的开发中,稳定远比追新版本重要的多。所以,我们虽然用到了Tomcat9和对应的JavaEE8,但是涉及的Servlet规范我们降板使用,用的是Servlet3.1版本。关于兼容性问题,同学们也无须担心,向下兼容的特性,在这里也依然适用。
- 接下来,同学还有可能疑惑的地方就是,我们课程中明明使用的是Servlet3.1版本的规范,但是却总听老师提Servlet3.0规范,这两个到底有怎样的联系呢?
- 现在就给同学们解惑,在大概十多年前,那会还是Servlet2.5的版本的天下,它最明显的特征就是Servlet的配置要求配在web.xml中,我们今天课程中在第4章节《注解开发Servlet》之前,全都是基于Servlet2.5规范编写的。从2007年开始到2009年底,在这个时间段,软件开发开始逐步的演变,基于注解的配置理念开始逐渐出现,大量注解配置思想开始用于各种框架的设计中,例如:Spring3.0版本的Java Based Configuration,JPA规范,apache旗下的struts2和mybatis的注解配置开发等等。
- JavaEE6规范也是在这个期间设计并推出的,与之对应就是它里面包含了新的Servlet规范:Servlet3.0版本!
(2)servlet注解开发入门案例
- 第一步:创建Javaweb工程,并移除web.xml文件
- 第二步:编写servlet类,并在servlet类上使用注解
- 第三步:测试
(3)WebServlet注解的详解
/**
* WebServlet注解
* @since Servlet 3.0 (Section 8.1.1)
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
/**
* 指定Servlet的名称。
* 相当于xml配置中<servlet>标签下的<servlet-name>
*/
String name() default "";
/**
* 用于映射Servlet访问的url映射
* 相当于xml配置时的<url-pattern>
*/
String[] value() default {};
/**
* 相当于xml配置时的<url-pattern>
*/
String[] urlPatterns() default {};
/**
* 用于配置Servlet的启动时机
* 相当于xml配置的<load-on-startup>
*/
int loadOnStartup() default -1;
/**
* 用于配置Servlet的初始化参数
* 相当于xml配置的<init-param>
*/
WebInitParam[] initParams() default {};
/**
* 用于配置Servlet是否支持异步
* 相当于xml配置的<async-supported>
*/
boolean asyncSupported() default false;
/**
* 用于指定Servlet的小图标
*/
String smallIcon() default "";
/**
* 用于指定Servlet的大图标
*/
String largeIcon() default "";
/**
* 用于指定Servlet的描述信息
*/
String description() default "";
/**
* 用于指定Servlet的显示名称
*/
String displayName() default "";
}
二、response响应类和request请求类
2.1 response响应类的认识与使用
(1)响应对象的概述
- 关于响应
- 响应,它表示了服务器端收到请求,同时也已经处理完成,把处理的结果告知用户。简单来说,指的就是服务器把请求的处理结果告知客户端。在B/S架构中,响应就是把结果带回浏览器。
- 响应对象,顾名思义就是用于在JavaWeb工程中实现上述功能的对象。
- 常用的响应对象
- 响应对象也是是Servlet规范中定义的,它包括了协议无关的和协议相关的。
- 协议无关的对象标准是:ServletResponse接口
- 协议相关的对象标准是:HttpServletResponse接口
- 类结构图如下:
一般我们用的响应对象是和HTTP协议相关的,即使用的是HttpServletResponse接口的实现类。像在tomcat中已经为我们提供好了该类的对象,无需我们自己定义。同时还会帮我们把对象传入至doGet和doPost方法中。
(2)HttpServletResponse常用的方法。
在HttpServletResponse接口中提供了很多方法,接下来我们通过API文档,来了解这些方法。
常用的状态码 | 状态码 | 说明 | | —- | —- | | 200 | 执行成功 | | 302 | 它和307一样,都是用于重定向的状态码。只是307目前已不再使用了。 | | 304 | 请求资源没改变,使用缓存 | | 400 | 请求错误,最常见的是请求参数有问题 | | 404 | 请求资源未找到 | | 405 | 请求方式不支持 | | 500 | 服务器运行内部错误 |
状态码首位数字的含义 | 状态码 | 说明 | | —- | —- | | 1xx | 消息相关 | | 2xx | 成功相关 | | 3xx | 重定向相关 | | 4xx | 客户端相关错误 | | 5xx | 服务器相关错误 |
(3)响应对象使用示例(响应中文乱码问题)
响应字节流输出中文乱码问题。
/**
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class ResponseDemo1 extends HttpServlet {
/**
* 演示字节流输出的乱码问题
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 问题:
* String str = "字节流中文乱码问题";
* 使用字节流输出,会不会产生中文乱码?
* 答案:
* 会产生乱码
* 原因:
* String str = "字节流中文乱码问题"; 在保存时用的是IDEA创建文件使用的字符集UTF-8。
* 到浏览器上显示,chrome浏览器和ie浏览器默认的字符集是GB2312(其实就是GBK),存和取用的不是同一个码表,就会产生乱码。
*
* 引申:
* 如果产生了乱码,就是存和取用的不是同一个码表
* 解决办法:
* 把存和取的码表统一。
*/
String str = "字节流输出中文的乱码问题";//UTF-8的字符集,此时浏览器显示也需要使用UTF-8的字符集。
//1.拿到字节流输出对象
ServletOutputStream sos = response.getOutputStream();
/**
* 解决办法:
* 第一种解决办法:
* 修改浏览器的编码,使用右键——编码——改成UTF-8。(不推荐使用,我们的应用尽量不要求用户取做什么事情)
* ie和火狐浏览器可以直接右键设置字符集。而chrome需要安装插件,很麻烦。
* 第二种解决办法: (不建议使用,因为不好记)
* 向页面上输出一个meta标签,内容如下: <meta http-equiv="content-type" content="text/html;charset=UTF-8">
* 其实它就是指挥了浏览器,使用哪个编码进行显示。
* 第三种解决办法:
* 设置响应消息头,告知浏览器响应正文的MIME类型和字符集
* response.setHeader("Content-Type","text/html;charset=UTF-8");
* 第四种解决办法:我们推荐使用的办法
* 它的本质就是设置了一个响应消息头
* response.setContentType("text/html;charset=UTF-8");
*/
//第二种解决办法:sos.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'>".getBytes());
//第三种解决办法:response.setHeader("Content-Type","text/html;charset=UTF-8");
//第四种解决办法:
response.setContentType("text/html;charset=UTF-8");
//2.把str转换成字节数组之后输出到浏览器
sos.write(str.getBytes("UTF-8"));
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
响应字符流输出中文乱码问题。
/**
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class ResponseDemo2 extends HttpServlet {
/**
* 字符流输出中文乱码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String str = "字符流输出中文乱码";
//response.setCharacterEncoding("UTF-8");
//设置响应正文的MIME类型和字符集
response.setContentType("text/html;charset=UTF-8");
//1.获取字符输出流
PrintWriter out = response.getWriter();
//2.使用字符流输出中文
/**
* 问题:
* out.write(str); 直接输出,会不会产生乱码
* 答案:
* 会产生乱码
* 原因:
* 存用的什么码表:UTF-8
* 在浏览器取之前,字符流PrintWriter已经获取过一次了,PrintWriter它在取的时候出现了乱码。
* 浏览器取默认用的是GBK。(本地系统字符集)
*
* UTF-8(存)————>PrintWriter ISO-8859-1(取) 乱
* PrintWirter ISO-8859-1(存)————>浏览器 GBK(取) 乱
*
* 解决办法:
* 改变PrintWriter的字符集,PrintWriter是从response对象中获取的,其实设置response的字符集。
* 注意:设置response的字符集,需要在拿流之前。
* response.setCharacterEncoding("UTF-8");
*
* response.setContentType("text/html;charset=UTF-8");
* 此方法,其实是做了两件事:
* 1.设置响应对象的字符集(包括响应对象取出的字符输出流)
* 2.告知浏览器响应正文的MIME类型和字符集
*/
out.write(str);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
(4)响应对象之图片验证码
package com.xiaoha.verification;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
/**
* @author HausenLee
* @Company http://www.xiaoha.com
*
*/
public class VerificationServlet extends HttpServlet {
/**
* 输出图片
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int width = 200;
int height = 35;
/**
* 实现步骤:
* 1.创建图像内存对象
* 2.拿到画笔
* 3.设置颜色,画矩形边框
* 4.设置颜色,填充矩形
* 5.设置颜色,画干扰线
* 6.设置颜色,画验证码
* 7.把内存图像输出到浏览器上
*/
//创建内存图像
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);//参数:宽度,高度 (指的都是像素),使用的格式(RGB)
Graphics g = image.getGraphics();//画笔就一根
//设置颜色
g.setColor(Color.BLUE);
//画边框
g.drawRect(0, 0, width, height);
//设置矩形图片底色颜色
g.setColor(Color.GRAY);
//将颜色填充到矩形
g.fillRect(1, 1, width-2, height-2);
//设置干扰线条的颜色
g.setColor(Color.WHITE);
//拿随机数对象
Random r = new Random();
//画干扰线 10条
for(int i=0;i<10;i++){
g.drawLine(r.nextInt(width), r.nextInt(height),r.nextInt(width), r.nextInt(height));
}
//设置字体颜色
g.setColor(Color.RED);
//改变字体大小
Font font = new Font("宋体", Font.BOLD,30);//参数:1字体名称。2.字体样式 3.字体大小
g.setFont(font);//设置字体
//画验证码 4个
int x = 35;//第一个数的横坐标是35像素
for(int i=0;i<4;i++){
//r.nextInt(10)+""这种写法效率是十分低的
g.drawString(String.valueOf(r.nextInt(10)), x, 25);
x+=35;
}
//输出到浏览器上
//参数: 1.内存对象。2.输出的图片格式。3.使用的输出流
//ImageIO是图像影像的操作对象
ImageIO.write(image, "jpg", response.getOutputStream());
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
(5)设置响应消息头—控制缓存
代码演示 ```java /**
- 设置缓存时间
- 使用缓存的一般都是静态资源
- 动态资源一般不能缓存。
- 我们现在目前只掌握了Servlet,所以用Servlet做演示
- @author 黑马程序员
@Company http://www.itheima.com / public class ResponseDemo4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String str = "设置缓存时间";
/*
* 设置缓存时间,其实就是设置响应消息头:Expires 但是值是一个毫秒数。
* 使用的是
* response.setDateHeader();
*
* 缓存1小时,是在当前时间的毫秒数上加上1小时之后的毫秒值
*/
response.setDateHeader("Expires",System.currentTimeMillis()+1*60*60*1000);
response.setContentType("text/html;charset=UTF-8");
response.getOutputStream().write(str.getBytes());
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<a name="BLh7p"></a>
### (6)设置响应消息头--定时刷新
- 代码演示
/**
- 设置响应消息头:
- 通过定时刷新演示添加消息头
- @author 黑马程序员
@Company http://www.itheima.com / public class ResponseDemo5 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String str = "用户名和密码不匹配,2秒后转向登录页面...";
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write(str);
//定时刷新,其实就是设置一个响应消息头
response.setHeader("Refresh", "2;URL=/login.html");//Refresh设置的时间单位是秒,如果刷新到其他地址,需要在时间后面拼接上地址
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<a name="a5PY8"></a>
### (7)请求重定向(地址栏会发生变化)
- 代码演示
/**
- 设置响应状态码,实现重定向
- 重定向的特点:
- 两次请求,地址栏改变,浏览器行为,xxxx
- @author 黑马程序员
@Company http://www.itheima.com / public class ResponseDemo6 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.设置响应状态码
// response.setStatus(302);
//2.定向到哪里去: 其实就是设置响应消息头,Location
// response.setHeader(“Location”, “ResponseDemo7”);
//使用重定向方法
response.sendRedirect("ResponseDemo7");//此行做了什么事,请看上面
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
/**
- 重定向的目的地
- @author 黑马程序员
@Company http://www.itheima.com */ public class ResponseDemo7 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("welcome to ResponseDemo7");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<a name="N9v6z"></a>
### (8)响应和消息头组合应用之文件下载
- 在工程的web目录新建一个目录uploads,并且拷贝一张图片到目录中,如下图所示:
![](https://cdn.nlark.com/yuque/0/2021/png/12492094/1624073615259-611eb663-c662-4054-868e-48e02e9af43a.png#id=OmqhV&originHeight=413&originWidth=406&originalType=binary&ratio=1&status=done&style=none)
- servlet代码如下:
/**
- 文件下载
- @author 黑马程序员
@Company http://www.itheima.com / public class ResponseDemo8 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/*
* 文件下载的思路:
* 1.获取文件路径
* 2.把文件读到字节输入流中
* 3.告知浏览器,以下载的方式打开(告知浏览器下载文件的MIME类型)
* 4.使用响应对象的字节输出流输出到浏览器上
*/
//1.获取文件路径(绝对路径)
ServletContext context = this.getServletContext();
String filePath = context.getRealPath("/uploads/6.jpg");//通过文件的虚拟路径,获取文件的绝对路径
//2.通过文件路径构建一个字节输入流
InputStream in = new FileInputStream(filePath);
//3.设置响应消息头
response.setHeader("Content-Type", "application/octet-stream");//注意下载的时候,设置响应正文的MIME类型,用application/octet-stream
response.setHeader("Content-Disposition", "attachment;filename=1.jpg");//告知浏览器以下载的方式打开
//4.使用响应对象的字节输出流输出
OutputStream out = response.getOutputStream();
int len = 0;
byte[] by = new byte[1024];
while((len = in.read(by)) != -1){
out.write(by, 0, len);
}
in.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<a name="nuJnf"></a>
### (0)响应对象的注意事项
- 第一:response得到的字符流和字节流是互斥的,只能选其一;
- 第二:response获取的流不用关闭,由服务器关闭。
<a name="hSG80"></a>
## 2.2 request请求对象的认识与使用
<a name="gatk8"></a>
### (1)请求对象的概述
- 请求,顾名思义,就是使用者希望从服务器端索取一些资源,向服务器发出询问。在B/S架构中,就是客户端浏览器向服务器发出询问。在我们的JavaEE工程中,客户端浏览器发出询问,要遵循HTTP协议所规定的。请求对象,就是在JavaEE工程中,用于发送请求的对象。我们常用的对象是ServletRequest和HttpServletRequest,他们的区别就是是否和HTTP协议有关。
- 请求对象的类关系图(类视图)
![](https://cdn.nlark.com/yuque/0/2021/png/12492094/1624074673330-3d65c2e1-967e-4171-bc7e-bea99772fb4e.png#id=k5imn&originHeight=375&originWidth=704&originalType=binary&ratio=1&status=done&style=none)
<a name="kyXRf"></a>
### (2)请求对象的常用方法
![](https://cdn.nlark.com/yuque/0/2021/png/12492094/1624074738566-52b40d03-fc10-4bf5-9647-566e083a16d0.png#id=QMGiG&originHeight=2044&originWidth=1223&originalType=binary&ratio=1&status=done&style=none)
<a name="Aw2l5"></a>
### (3)请求对象的使用示例(常用方法)
- 请求对象的常用方法——获取各种请求路径
/**
- 请求对象的各种信息获取
- @author 黑马程序员
@Company http://www.itheima.com */ public class RequestDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//本机地址:服务器地址
String localAddr = request.getLocalAddr();
//本机名称:服务器名称
String localName = request.getLocalName();
//本机端口:服务器端口
int localPort = request.getLocalPort();
//来访者ip
String remoteAddr = request.getRemoteAddr();
//来访者主机
String remoteHost = request.getRemoteHost();
//来访者端口
int remotePort = request.getRemotePort();
//统一资源标识符
String URI = request.getRequestURI();
//统一资源定位符
String URL = request.getRequestURL().toString();
//获取查询字符串
String queryString = request.getQueryString();
//获取Servlet映射路径
String servletPath = request.getServletPath();
//输出内容
System.out.println("getLocalAddr() is :"+localAddr);
System.out.println("getLocalName() is :"+localName);
System.out.println("getLocalPort() is :"+localPort);
System.out.println("getRemoteAddr() is :"+remoteAddr);
System.out.println("getRemoteHost() is :"+remoteHost);
System.out.println("getRemotePort() is :"+remotePort);
System.out.println("getRequestURI() is :"+URI);
System.out.println("getRequestURL() is :"+URL);
System.out.println("getQueryString() is :"+queryString);
System.out.println("getServletPath() is :"+servletPath);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
} } ```
获取请求头信息 ``` /**
- 获取请求消息头
- @author 黑马程序员
@Company http://www.itheima.com */ public class RequestDemo2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.根据名称获取头的值 一个消息头一个值
String value = request.getHeader("Accept-Encoding");
System.out.println("getHeader():"+value);
//2.根据名称获取头的值 一个头多个值
Enumeration<String> values = request.getHeaders("Accept");
while(values.hasMoreElements()){
System.out.println("getHeaders():"+values.nextElement());
}
//3.获取请求消息头的名称的枚举
Enumeration<String> names = request.getHeaderNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value1 = request.getHeader(name);
System.out.println(name+":"+value1);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
- 获取请求参数(重要)
- x先准备一个HTML表单页面
```html
<html>
<head>
<title>login to request demo 3</title>
</head>
<body>
<form action="/day10_1122_requestresponse/RequestDemo3" method="post">
用户名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br/>
性别:<input type="radio" name="gender" value="1" checked>男
<input type="radio" name="gender" value="0">女
<br/>
<input type="submit" value="注册" />
</form>
</body>
</html>
getParameter()方法获取请求参数的示例代码
/**
* 获取请求正文,一个名称对应一个值。 没有使用确认密码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test1(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文
String username = request.getParameter("username");
String password = request.getParameter("password");
String gender = request.getParameter("gender");
System.out.println(username+","+password+","+gender);
}
getParameterValues()方法的示例代码
/**
* 获取请求正文,一个名称可能对应多个值 使用了确认密码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test2(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文
String username = request.getParameter("username");
String[] password = request.getParameterValues("password");//当表单中有多个名称是一样时,得到是一个字符串数组
String gender = request.getParameter("gender");
System.out.println(username+","+Arrays.toString(password)+","+gender);
}
getParameterNames()方法的示例代码
/**
* 获取请求正文,一个名称一个值。但是先要获取正文名称的枚举(key的枚举) 没有使用确认密码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test3(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文名称的枚举
Enumeration<String> names = request.getParameterNames();
//2.遍历正文名称的枚举
while(names.hasMoreElements()){
String name = names.nextElement();
String value = request.getParameter(name);
System.out.println(name+":"+value);
}
}
(4)封装请求参数到实体类
我们通过上面的方法可以获取到请求参数,但是如果参数过多,在进行传递时,方法的形参定义将会变得非常难看。此时我们应该用一个对象来描述这些参数,它就是实体类。这种类的定义,从基础阶段我们就开始使用了。在基础阶段,我们做过一个学生管理系统,用到了一个Student的类,它就是用于描述一个学生的实体类。
第一种:最简单的直接封装法
/**
* 封装请求正文到User对象中 没有使用确认密码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test4(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文
String username = request.getParameter("username");
String password = request.getParameter("password");
String gender = request.getParameter("gender");
//2.创建一个User对象
User user = new User();
System.out.println("封装前:"+user.toString());
//3.把请求正文封装到user对象中
user.setUsername(username);
user.setPassword(password);
user.setGender(gender);
System.out.println("封装后:"+user.toString());
}
第二种方式:使用反射
/**
* 封装请求正文到javabean中 没有使用确认密码
* 使用反射+内省实现数据模型的封装
* 内省:是sun公司推出的一套简化反射操作的规范。把javabean中的元素都封装成一个属性描述器。
* 属性描述器中会有字段信息,get和set方法(取值或存值)
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test5(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文名称的枚举
Enumeration<String> names = request.getParameterNames();
User user = new User();
System.out.println("封装前:"+user.toString());
//2.遍历正文名称的枚举
while(names.hasMoreElements()){
String name = names.nextElement();
String value = request.getParameter(name);
try{
//1.拿到User对象中的属性描述器。是谁的属性描述器:是由构造函数的第一个参数决定的。第二个参数是指定javabean的字节码
PropertyDescriptor pd = new PropertyDescriptor(name, User.class);//参数指的就是拿哪个类的哪个属性的描述器
//2.设置javabean属性的值
Method method = pd.getWriteMethod();
//3.执行方法
method.invoke(user, value);//第一个参数是指的给哪个对象,第二个参数指的是赋什么值
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println("封装后:"+user.toString());
}
第三种:使用反射,同时请求参数是一个数组;此方法就是针对请求参数中包含name属性相同的参数;例如:密码和确认密码、兴趣爱好等。
/**
* 获取请求正文的关系映射Map<String,String[]> 使用确认密码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test6(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文的映射关系
Map<String,String[]> map = request.getParameterMap();
//2.遍历集合
for(Map.Entry<String,String[]> me : map.entrySet()){
String name = me.getKey();
String[] value = me.getValue();
System.out.println(name+":"+Arrays.toString(value));
}
}
当我们将请求参数取出来之后,就用反射进行封装
/**
* 封装请求正文到javabean。使用的是反射+内省 使用了确认密码
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test7(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文的映射关系
Map<String,String[]> map = request.getParameterMap();
Users user = new Users();
System.out.println("封装前:"+user.toString());
//2.遍历集合
for(Map.Entry<String,String[]> me : map.entrySet()){
String name = me.getKey();
String[] value = me.getValue();
try{
//1.拿到User对象中的属性描述器。是谁的属性描述器:是由构造函数的第一个参数决定的。第二个参数是指定javabean的字节码
PropertyDescriptor pd = new PropertyDescriptor(name, Users.class);//参数指的就是拿哪个类的哪个属性的描述器
//2.设置javabean属性的值
Method method = pd.getWriteMethod();
//3.执行方法
//判断参数到底是几个值
if(value.length > 1){//最少有2个元素
method.invoke(user, (Object)value);//第一个参数是指的给哪个对象,第二个参数指的是赋什么值
}else{
method.invoke(user, value);//第一个参数是指的给哪个对象,第二个参数指的是赋什么值
}
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println("封装后:"+user.toString());
}
当我们写完此种封装方式之后,同学们可以发现,我们绝大多数封装都可以使用这段代码来实现。并且,无论是谁来写这段通用的封装代码,其代码内容都是大同小异的。那么,我们就可以得出一个很有趣的结论:一般遇到这种情况时,肯定有人帮我们写好了,我们只需要用就行了。我们后面还会遇到类似这样的情况。
此时,帮我们写好这段封装代码的是apache软件基金会,我们前面学习的tomcat也是它提供的。它里面有一个开源工具包集合commons,里面有很多开源工具类,今天我们就来讲解第一个:commons-beanutils。
/**
* 终极方法:使用beanutils实现请求正文封装到javabean中 使用了确认密码
* 要想使用beanutils,需要先导包
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void test8(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Users user = new Users();
System.out.println("封装前:"+user.toString());
try{
BeanUtils.populate(user, request.getParameterMap());//就这一句话
}catch(Exception e){
e.printStackTrace();
}
System.out.println("封装后:"+user.toString());
}
用流的方式读取请求参数 ```java /**
- 使用流的方式读取请求正文
- @author 黑马程序员
@Company http://www.itheima.com */ public class RequestDemo4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文的字节输入流
ServletInputStream sis = request.getInputStream();
//2.读取流中的数据
int len = 0;
byte[] by = new byte[1024];
while((len = sis.read(by)) != -1){
System.out.println(new String(by,0,len));
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<a name="yrJ2X"></a>
### (5)请求正文乱码的问题
- POST方式乱码问题解决
```java
/**
* 请求正文的中文乱码问题
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class RequestDemo5 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取请求正文
/*POST方式:
* 问题:
* 取的时候会不会有乱码
* 答案:
* 获取请求正文,会有乱码问题。
* 是在获取的时候就已经乱码了。
* 解决办法:
* 是request对象的编码出问题了
* 设置request对象的字符集
* request.setCharacterEncoding("GBK");它只能解决POST的请求方式,GET方式解决不了
* 结论:
* 请求正文的字符集和响应正文的字符集没有关系。各是各的
*/
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
//输出到控制台
System.out.println(username);
//输出到浏览器:注意响应的乱码问题已经解决了
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write(username);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
- Get请求方式乱码问题解决
- GET方式请求的正文是在地址栏中,在Tomcat8.5版本及以后,Tomcat服务器已经帮我们解决了,所以不会有乱码问题了。
- 而如果我们使用的不是Tomcat服务器,或者Tomcat的版本是8.5以前,那么GET方式仍然会有乱码问题,解决方式如下:(以下代码了解即可,因为我们现在使用的是Tomcat9.0.27版本)
```java
/**
- 在Servlet的doGet方法中添加如下代码
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- 在Servlet的doGet方法中添加如下代码
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
/*
* GET方式:正文在地址栏
* username=%D5%C5%C8%FD
* %D5%C5%C8%FD是已经被编过一次码了
*
* 解决办法:
* 使用正确的码表对已经编过码的数据进行解码。
* 就是把取出的内容转成一个字节数组,但是要使用正确的码表。(ISO-8859-1)
* 再使用正确的码表进行编码
* 把字节数组再转成一个字符串,需要使用正确的码表,是看浏览器当时用的是什么码表
*/
String username = request.getParameter("username");
byte[] by = username.getBytes("ISO-8859-1");
username = new String(by,"GBK");
//输出到浏览器:注意响应的乱码问题已经解决了
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write(username);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
<a name="Ekhw9"></a>
### (6)请求转发(与重定向的区别)
- 重定向的特点:它是两次请求,属于浏览器行为,地址栏会改变,请求域中的数据会丢失
- 请求转发的特点:一次请求,属于服务器行为,地址栏不会改变,请求域中的数据不丢失
- 请求域的作用范围:当前请求(一次请求),和当前请求的转发之中。
- 请求转发示例代码
```java
public class RequestDemo6 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.拿到请求调度对象
RequestDispatcher rd = request.getRequestDispatcher("/RequestDemo7");//如果是给浏览器看的,/可写可不写。如果是给服务器看的,一般情况下,/都是必须的。
//放入数据到请求域中
request.setAttribute("CityCode", "bj-010");
//2.实现真正的转发操作
rd.forward(request, response);//实现真正的转发操作
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
/**
* 转发的目的地
* @author 黑马程序员
* @Company http://www.itheima.com
*/
public class RequestDemo7 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取请求域中的数据
String value = (String)request.getAttribute("CityCode");
response.getWriter().write("welcome to request demo 7 "+value);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
请求包含示例代码 ```java /**
- 请求包含 *
- 它是把两个Servlet的响应内容合并输出。
- 注意:
- 这种包含是动态包含。 *
- 动态包含的特点:
- 各编译各的,只是最后合并输出。
- @author 黑马程序员
@Company http://www.itheima.com */ public class RequestDemo8 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("I am request demo8 ");
//1.拿到请求调度对象
RequestDispatcher rd = request.getRequestDispatcher("/RequestDemo9");
//2.实现包含的操作
rd.include(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
} }
/**
- 被包含者
- @author 黑马程序员
@Company http://www.itheima.com */ public class RequestDemo9 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("include request demo 9 ");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
} ```
- 请求转发和请求包含的细节问题
- 请求转发的注意事项:负责转发的Servlet,转发前后的响应正文丢失,由转发目的地来响应浏览器。
- 请求包含的注意事项:被包含者的响应消息头丢失。因为它被包含起来了。