一、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相关的笔记 - 图1

  • 由此可知:
    • ①servlet是一个运行在web服务端的java小程序
    • ②它可以用于接收和响应客户端的请求
    • ③要实现servlet功能,可以实现servlet接口或者继承GenericServlet功能类或者继承HttpServlet功能类。
    • ④每次请求都会执行service方法
    • ⑤servlet还支持配置

servlet相关的笔记 - 图2

(2)servlet的入门

  • servlet编码的步骤
    • 第一步:前期准备,创建JavaWeb工程
    • 第二步:编写一个普通的类继承GenericServlet并重写service方法
    • 第三步:在web.xml配置servlet
    • 第四步:在tomcat中部署项目
    • 第五步:启动tomcat并用浏览器访问测试。

servlet相关的笔记 - 图3

(2)servlet执行过程的分析

  • 我们通过浏览器发送请求,请求首先到达Tomcat服务器,由服务器解析请求URL,然后在部署的应用列表中找到我们的应用。接下来,在我们的应用中找应用里的web.xml配置文件,在web.xml中找到FirstServlet的配置,找到后执行service方法,最后由FirstServlet响应客户浏览器。整个过程如下图所示:

servlet相关的笔记 - 图4

  • 一句话总结执行过程:

    • 浏览器——>Tomcat服务器——>我们的应用——>应用中的web.xml——>FirstServlet——>响应浏览器

      (3)servlet类视图

  • 在Servlet的API介绍中,它提出了我们除了继承GenericServlet外还可以继承HttpServlet,通过查阅servlet的类视图,我们看到GenericServlet还有一个子类HttpServlet。同时,在service方法中还有参数ServletRequest和ServletResponse,它们的关系如下图所示:

servlet相关的笔记 - 图5

(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;注意:不写任何的方法。如下图所示:

servlet相关的笔记 - 图6
servlet相关的笔记 - 图7

  • 第二步:部署项目并测试
    • 当我们在地址栏输入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运用了单例模式,即整个应用中只有一个实例对象,所以我们需要分析这个唯一的实例中的类成员是否线程安全。接下来,我们来看下面的的示例:

    1. /**
    2. * 演示Servlet的线程安全问题:
    3. * 示例需求:
    4. * 模拟网上看书的翻页功能。
    5. * (类似的有浏览商品的翻页,浏览论坛帖子的翻页)
    6. * @author HausenLee
    7. * @Company http://www.xiaoha.com
    8. */
    9. public class ServletDemo4 extends HttpServlet {
    10. /**
    11. * 我们讨论的是类成员的线程安全问题,所以要定义一个类成员
    12. */
    13. //定义浏览书籍的页码,都是从第一页开始的(公共资源)
    14. private int currentPage = 1;
    15. /**
    16. * 真正翻页看书的功能
    17. * @param req
    18. * @param resp
    19. * @throws ServletException
    20. * @throws IOException
    21. */
    22. @Override
    23. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    24. //1.获取当前要看的书名(此处我们今天先来用以下,明天来着重讲解请求和响应对象)
    25. String bookName = req.getParameter("bookName");
    26. //2.输出书名和当前页码
    27. System.out.println("您看的是:"+bookName+",当前页码是:"+currentPage);
    28. //3.执行翻页
    29. currentPage++;
    30. }
    31. @Override
    32. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    33. doGet(req,resp);
    34. }
    35. }
  • 这是一个模拟在网上看书的示例,我们在Servlet中记录了当前要看的页码,理想状态下,用户每次请求都来看自己该看的页码。启动服务,测试一下:

servlet相关的笔记 - 图8

  • 通过上面的测试我们发现,在Servlet中定义了类成员之后,多个浏览器都会共享类成员的数据。其实每一个浏览器端发送请求,就代表是一个线程,那么多个浏览器就是多个线程,所以测试的结果说明了多个线程会共享Servlet类成员中的数据,其中任何一个线程修改了数据,都会影响其他线程。因此,我们可以认为Servlet它不是线程安全的。
  • 分析产生这个问题的根本原因,其实就是因为Servlet是单例,单例对象的类成员只会随类实例化时初始化一次,之后的操作都是改变,而不会重新初始化。
  • 解决这个问题也非常简单,就是在Servlet中定义类成员要慎重。如果类成员是共用的,并且只会在初始化时赋值,其余时间都是获取的话,那么是没问题。如果类成员并非共用,或者每次使用都有可能对其赋值,那么就要考虑线程安全问题了,把它定义到doGet或者doPost方法里面去就可以了。

(3)servlet的注意事项

  • 映射servlet的细节

    • Servlet支持三种映射方式,以达到灵活配置的目的。首先编写一个Servlet,代码如下:

      1. /**
      2. * 演示Servlet的映射方式
      3. * @author HausenLee
      4. * @Company http://www.xiaoha.com
      5. */
      6. public class ServletDemo5 extends HttpServlet {
      7. /**
      8. * doGet方法输出一句话
      9. * @param req
      10. * @param resp
      11. * @throws ServletException
      12. * @throws IOException
      13. */
      14. @Override
      15. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      16. System.out.println("ServletDemo5接收到了请求");
      17. }
      18. /**
      19. * 调用doGet方法
      20. * @param req
      21. * @param resp
      22. * @throws ServletException
      23. * @throws IOException
      24. */
      25. @Override
      26. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      27. doGet(req,resp);
      28. }
      29. }
  • 第一种:指名道姓方式

servlet相关的笔记 - 图9

servlet相关的笔记 - 图10

servlet相关的笔记 - 图11

  • 通过测试我们发现,Servlet支持多种配置方式,但是由此也引出了一个问题,当有两个及以上的Servlet映射都符合请求URL时,由谁来响应呢?注意:HTTP协议的特征是一请求一响应的规则。那么有一个请求,必然有且只有一个响应。所以,我们接下来明确一下,多种映射规则的优先级。
  • 先说结论:指名道姓的方式优先级最高,带有通配符的映射方式,有/的比没/的优先级高;所以,我们前面讲解的三种映射方式的优先级为:第一种>第二种>第三种。

    (4)servlet多路径映射

  • 上一小节我们讲解了Servlet的多种映射方式,这一小节我们来介绍一下,一个Servlet的多种路径配置的支持。

  • 它其实就是给一个Servlet配置多个访问映射,从而可以根据不同请求URL实现不同的功能。
  • 首先,创建一个Servlet:

    1. /**
    2. * 演示Servlet的多路径映射
    3. * @author 黑马程序员
    4. * @Company http://www.itheima.com
    5. */
    6. public class ServletDemo7 extends HttpServlet {
    7. /**
    8. * 根据不同的请求URL,做不同的处理规则
    9. * @param req
    10. * @param resp
    11. * @throws ServletException
    12. * @throws IOException
    13. */
    14. @Override
    15. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    16. //1.获取当前请求的URI
    17. String uri = req.getRequestURI();
    18. uri = uri.substring(uri.lastIndexOf("/"),uri.length());
    19. //2.判断是1号请求还是2号请求
    20. if("/servletDemo7".equals(uri)){
    21. System.out.println("ServletDemo7执行1号请求的业务逻辑:商品单价7折显示");
    22. }else if("/demo7".equals(uri)){
    23. System.out.println("ServletDemo7执行2号请求的业务逻辑:商品单价8折显示");
    24. }else {
    25. System.out.println("ServletDemo7执行基本业务逻辑:商品单价原价显示");
    26. }
    27. }
    28. /**
    29. * 调用doGet方法
    30. * @param req
    31. * @param resp
    32. * @throws ServletException
    33. * @throws IOException
    34. */
    35. @Override
    36. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    37. doGet(req,resp);
    38. }
    39. }
  • 接下来,在web.xml配置Servlet:

    1. <!--配置ServletDemo7-->
    2. <servlet>
    3. <servlet-name>servletDemo7</servlet-name>
    4. <servlet-class>com.itheima.web.servlet.ServletDemo7</servlet-class>
    5. </servlet>
    6. <!--映射路径1-->
    7. <servlet-mapping>
    8. <servlet-name>servletDemo7</servlet-name>
    9. <url-pattern>/demo7</url-pattern>
    10. </servlet-mapping>
    11. <!--映射路径2-->
    12. <servlet-mapping>
    13. <servlet-name>servletDemo7</servlet-name>
    14. <url-pattern>/servletDemo7</url-pattern>
    15. </servlet-mapping>
    16. <!--映射路径3-->
    17. <servlet-mapping>
    18. <servlet-name>servletDemo7</servlet-name>
    19. <url-pattern>/servlet/*</url-pattern>
    20. </servlet-mapping>
  • 最后,启动服务测试运行结果:

servlet相关的笔记 - 图12

(5)启动tomcat时创建servlet

  • 我们前面讲解了Servlet的生命周期,Servlet的创建默认情况下是请求第一次到达Servlet时创建的。但是我们都知道,Servlet是单例的,也就是说在应用中只有唯一的一个实例,所以在Tomcat启动加载应用的时候就创建也是一个很好的选择。那么两者有什么区别呢?
    • 第一种:应用加载时创建Servlet,它的优势是在服务器启动时,就把需要的对象都创建完成了,从而在使用的时候减少了创建对象的时间,提高了首次执行的效率。它的弊端也同样明显,因为在应用加载时就创建了Servlet对象,因此,导致内存中充斥着大量用不上的Servlet对象,造成了内存的浪费。
    • 第二种:请求第一次访问是创建Servlet,它的优势就是减少了对服务器内存的浪费,因为那些一直没有被访问过的Servlet对象都没有创建,因此也提高了服务器的启动时间。而它的弊端就是,如果有一些要在应用加载时就做的初始化操作,它都没法完成,从而要考虑其他技术实现。
  • 通过上面的描述,能分析得出何时采用第一种方式,何时采用第二种方式。就是当需要在应用加载就要完成一些工作时,就需要选择第一种方式。当有很多Servlet的使用时机并不确定是,就选择第二种方式。在web.xml中是支持对Servlet的创建时机进行配置的,配置的方式如下:我们就以ServletDemo3为例。

    1. <!--配置ServletDemo3-->
    2. <servlet>
    3. <servlet-name>servletDemo3</servlet-name>
    4. <servlet-class>com.itheima.web.servlet.ServletDemo3</servlet-class>
    5. <!--配置Servlet的创建顺序,当配置此标签时,Servlet就会改为应用加载时创建
    6. 配置项的取值只能是正整数(包括0),数值越小,表明创建的优先级越高
    7. -->
    8. <load-on-startup>1</load-on-startup>
    9. </servlet>
    10. <servlet-mapping>
    11. <servlet-name>servletDemo3</servlet-name>
    12. <url-pattern>/servletDemo3</url-pattern>
    13. </servlet-mapping>

    servlet相关的笔记 - 图13

    (6)默认servlet

  • 默认Servlet是由服务器提供的一个Servlet,它配置在Tomcat的conf目录下的web.xml中。如下图所示:

servlet相关的笔记 - 图14

  • 它的映射路径是/,我们在发送请求时,首先会在我们应用中的web.xml中查找映射配置,找到就执行,这块没有问题。但是当找不到对应的Servlet路径时,就去找默认的Servlet,由默认Servlet处理。所以,一切都是Servlet。
  • servlet关系总图

servlet相关的笔记 - 图15

1.3 servletconfig

(1)servletconfig的概述

  • 它是servlet的配置参数对象,在servlet规范中,允许为每个servlet都提供一些初始化配置。所以每个servlet都有一个自己的servletconfig。它的作用是在servlet初始化期间,把一些配置信息传递给servlet。
  • servletconfig的生命周期:

    • 由于它是在初始化阶段读取了web.xml中为Servlet准备的初始化配置,并把配置信息传递给Servlet,所以生命周期与Servlet相同。这里需要注意的是,如果Servlet配置了1,那么ServletConfig也会在应用加载时创建。

      (2)servletconfig的使用

  • servletconfig的获取:

    • 首先,我们要清楚的认识到,它可以为每个Servlet都提供初始化参数,所以肯定可以在每个Servlet中都配置。那是配置在Servlet的声明部分,还是映射部分呢?我们接下来先准备一个Servlet

      1. /**
      2. * 演示Servlet的初始化参数对象
      3. * @author HausenLee
      4. * @Company http://www.xiaoha.com
      5. */
      6. public class ServletDemo8 extends HttpServlet {
      7. //定义Servlet配置对象ServletConfig
      8. private ServletConfig servletConfig;
      9. /**
      10. * 在初始化时为ServletConfig赋值
      11. * @param config
      12. * @throws ServletException
      13. */
      14. @Override
      15. public void init(ServletConfig config) throws ServletException {
      16. this.servletConfig = config;
      17. }
      18. /**
      19. * @param req
      20. * @param resp
      21. * @throws ServletException
      22. * @throws IOException
      23. */
      24. @Override
      25. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      26. //输出ServletConfig
      27. System.out.println(servletConfig);
      28. }
      29. /**
      30. * 调用doGet方法
      31. * @param req
      32. * @param resp
      33. * @throws ServletException
      34. * @throws IOException
      35. */
      36. @Override
      37. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      38. doGet(req,resp);
      39. }
      40. }
      1. <!--配置ServletDemo8-->
      2. <servlet>
      3. <servlet-name>servletDemo8</servlet-name>
      4. <servlet-class>com.itheima.web.servlet.ServletDemo8</servlet-class>
      5. </servlet>
      6. <servlet-mapping>
      7. <servlet-name>servletDemo8</servlet-name>
      8. <url-pattern>/servletDemo8</url-pattern>
      9. </servlet-mapping>
  • 配置servletconfig参数

    • 标签中的标签来配置。servlet的初始化参数都是配置在servlet的声明部分的(即在标签中配置)。并且每个servlet都支持多个初始化参数,并且初始化参数都是以键值对的形式存在的。
      1. <!--配置ServletDemo8-->
      2. <servlet>
      3. <servlet-name>servletDemo8</servlet-name>
      4. <servlet-class>com.itheima.web.servlet.ServletDemo8</servlet-class>
      5. <!--配置初始化参数-->
      6. <init-param>
      7. <!--用于获取初始化参数的key-->
      8. <param-name>encoding</param-name>
      9. <!--初始化参数的值-->
      10. <param-value>UTF-8</param-value>
      11. </init-param>
      12. <!--每个初始化参数都需要用到init-param标签-->
      13. <init-param>
      14. <param-name>servletInfo</param-name>
      15. <param-value>This is Demo8</param-value>
      16. </init-param>
      17. </servlet>
      18. <servlet-mapping>
      19. <servlet-name>servletDemo8</servlet-name>
      20. <url-pattern>/servletDemo8</url-pattern>
      21. </servlet-mapping>

(3)servletconfig常用的方法

  • 常用方法

servlet相关的笔记 - 图16

  1. /**
  2. * 演示Servlet的初始化参数对象
  3. * @author 黑马程序员
  4. * @Company http://www.itheima.com
  5. */
  6. public class ServletDemo8 extends HttpServlet {
  7. //定义Servlet配置对象ServletConfig
  8. private ServletConfig servletConfig;
  9. /**
  10. * 在初始化时为ServletConfig赋值
  11. * @param config
  12. * @throws ServletException
  13. */
  14. @Override
  15. public void init(ServletConfig config) throws ServletException {
  16. this.servletConfig = config;
  17. }
  18. /**
  19. * doGet方法输出一句话
  20. * @param req
  21. * @param resp
  22. * @throws ServletException
  23. * @throws IOException
  24. */
  25. @Override
  26. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  27. //1.输出ServletConfig
  28. System.out.println(servletConfig);
  29. //2.获取Servlet的名称
  30. String servletName= servletConfig.getServletName();
  31. System.out.println(servletName);
  32. //3.获取字符集编码
  33. String encoding = servletConfig.getInitParameter("encoding");
  34. System.out.println(encoding);
  35. //4.获取所有初始化参数名称的枚举
  36. Enumeration<String> names = servletConfig.getInitParameterNames();
  37. //遍历names
  38. while(names.hasMoreElements()){
  39. //取出每个name
  40. String name = names.nextElement();
  41. //根据key获取value
  42. String value = servletConfig.getInitParameter(name);
  43. System.out.println("name:"+name+",value:"+value);
  44. }
  45. //5.获取ServletContext对象
  46. ServletContext servletContext = servletConfig.getServletContext();
  47. System.out.println(servletContext);
  48. }
  49. /**
  50. * 调用doGet方法
  51. * @param req
  52. * @param resp
  53. * @throws ServletException
  54. * @throws IOException
  55. */
  56. @Override
  57. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  58. doGet(req,resp);
  59. }
  60. }

1.4 servletcontext

(1)servletcontext的概述

  • ServletContext对象,它是应用上下文对象。每一个应用有且只有一个ServletContext对象。它可以实现让应用中所有Servlet间的数据共享。
  • servletcontext的生命周期
    • 出生: 应用一加载,该对象就被创建出来了。一个应用只有一个实例对象。(Servlet和ServletContext都是单例的)
    • 活着:只要应用一直提供服务,该对象就一直存在。
    • 死亡:应用被卸载(或者服务器挂了),该对象消亡。

(2)域对象概念

  • 域对象的概念,它指的是对象有作用域,即有作用范围。
  • 域对象的作用,域对象可以实现数据共享。不同作用范围的域对象,共享数据的能力不一样。
  • 在Servlet规范中,一共有4个域对象。ServletContext就是其中一个。它也是我们接触的第一个域对象。它是web应用中最大的作用域,叫application域。每个应用只有一个application域。它可以实现整个应用间的数据共享功能。

(3)servletcontext的使用

  • 如何获取

    • 通过servletconfig对象,调用getServletContext()方法就可以了。

      1. /**
      2. * 用于演示ServletContext对象的使用
      3. * @author 黑马程序员
      4. * @Company http://www.itheima.com
      5. */
      6. public class ServletDemo9 extends HttpServlet {
      7. //定义Servlet配置对象ServletConfig
      8. private ServletConfig servletConfig;
      9. /**
      10. * 在初始化时为ServletConfig赋值
      11. * @param config
      12. * @throws ServletException
      13. */
      14. @Override
      15. public void init(ServletConfig config) throws ServletException {
      16. this.servletConfig = config;
      17. }
      18. /**
      19. *
      20. * @param req
      21. * @param resp
      22. * @throws ServletException
      23. * @throws IOException
      24. */
      25. @Override
      26. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      27. //1.获取ServletContext对象
      28. ServletContext servletContext = servletConfig.getServletContext();
      29. System.out.println(servletContext);
      30. }
      31. /**
      32. * 调用doGet方法
      33. * @param req
      34. * @param resp
      35. * @throws ServletException
      36. * @throws IOException
      37. */
      38. @Override
      39. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      40. doGet(req,resp);
      41. }
      42. }
      1. <!--配置ServletDemo9-->
      2. <servlet>
      3. <servlet-name>servletDemo9</servlet-name>
      4. <servlet-class>com.itheima.web.servlet.ServletDemo9</servlet-class>
      5. </servlet>
      6. <servlet-mapping>
      7. <servlet-name>servletDemo9</servlet-name>
      8. <url-pattern>/servletDemo9</url-pattern>
      9. </servlet-mapping>
  • 在实际开发中,如果我们每个Servlet对ServletContext都使用频繁的话,那么每个Servlet里定义ServletConfig,再获取ServletContext的代码将非常多,造成大量的重复代码。Servlet规范的定义中也为我们想到了这一点,所以它在GenericServlet中,已经为我们声明好了ServletContext获取的方法,如下图所示:

servlet相关的笔记 - 图17

  • 我们的Servlet都是继承自HttpServlet,而HttpServlet又是GenericServlet的子类,所以我们在获取ServletContext时,如果当前Servlet没有用到它自己的初始化参数时,就可以不用再定义初始化参数了,而是直接改成下图所示的代码即可:

servlet相关的笔记 - 图18

  • 如何配置
    • ServletContext既然被称之为应用上下文对象,所以它的配置是针对整个应用的配置,而非某个特定Servlet的配置。它的配置被称为应用的初始化参数配置。
    • 配置的方式,需要在标签中使用来配置初始化参数。具体代码如下:
      1. <!--配置应用初始化参数-->
      2. <context-param>
      3. <!--用于获取初始化参数的key-->
      4. <param-name>servletContextInfo</param-name>
      5. <!--初始化参数的值-->
      6. <param-value>This is application scope</param-value>
      7. </context-param>
      8. <!--每个应用初始化参数都需要用到context-param标签-->
      9. <context-param>
      10. <param-name>globalEncoding</param-name>
      11. <param-value>UTF-8</param-value>
      12. </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相关的笔记 - 图19
servlet相关的笔记 - 图20
servlet相关的笔记 - 图21

  • 第二步:编写servlet类,并在servlet类上使用注解

servlet相关的笔记 - 图22

  • 第三步:测试

servlet相关的笔记 - 图23

(3)WebServlet注解的详解

  1. /**
  2. * WebServlet注解
  3. * @since Servlet 3.0 (Section 8.1.1)
  4. */
  5. @Target(ElementType.TYPE)
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Documented
  8. public @interface WebServlet {
  9. /**
  10. * 指定Servlet的名称。
  11. * 相当于xml配置中<servlet>标签下的<servlet-name>
  12. */
  13. String name() default "";
  14. /**
  15. * 用于映射Servlet访问的url映射
  16. * 相当于xml配置时的<url-pattern>
  17. */
  18. String[] value() default {};
  19. /**
  20. * 相当于xml配置时的<url-pattern>
  21. */
  22. String[] urlPatterns() default {};
  23. /**
  24. * 用于配置Servlet的启动时机
  25. * 相当于xml配置的<load-on-startup>
  26. */
  27. int loadOnStartup() default -1;
  28. /**
  29. * 用于配置Servlet的初始化参数
  30. * 相当于xml配置的<init-param>
  31. */
  32. WebInitParam[] initParams() default {};
  33. /**
  34. * 用于配置Servlet是否支持异步
  35. * 相当于xml配置的<async-supported>
  36. */
  37. boolean asyncSupported() default false;
  38. /**
  39. * 用于指定Servlet的小图标
  40. */
  41. String smallIcon() default "";
  42. /**
  43. * 用于指定Servlet的大图标
  44. */
  45. String largeIcon() default "";
  46. /**
  47. * 用于指定Servlet的描述信息
  48. */
  49. String description() default "";
  50. /**
  51. * 用于指定Servlet的显示名称
  52. */
  53. String displayName() default "";
  54. }

二、response响应类和request请求类

2.1 response响应类的认识与使用

(1)响应对象的概述

  • 关于响应
    • 响应,它表示了服务器端收到请求,同时也已经处理完成,把处理的结果告知用户。简单来说,指的就是服务器把请求的处理结果告知客户端。在B/S架构中,响应就是把结果带回浏览器。
    • 响应对象,顾名思义就是用于在JavaWeb工程中实现上述功能的对象。
  • 常用的响应对象
    • 响应对象也是是Servlet规范中定义的,它包括了协议无关的和协议相关的。
    • 协议无关的对象标准是:ServletResponse接口
    • 协议相关的对象标准是:HttpServletResponse接口
    • 类结构图如下:

servlet相关的笔记 - 图24

  • 一般我们用的响应对象是和HTTP协议相关的,即使用的是HttpServletResponse接口的实现类。像在tomcat中已经为我们提供好了该类的对象,无需我们自己定义。同时还会帮我们把对象传入至doGet和doPost方法中。

    (2)HttpServletResponse常用的方法。

  • 在HttpServletResponse接口中提供了很多方法,接下来我们通过API文档,来了解这些方法。

servlet相关的笔记 - 图25

  • 常用的状态码 | 状态码 | 说明 | | —- | —- | | 200 | 执行成功 | | 302 | 它和307一样,都是用于重定向的状态码。只是307目前已不再使用了。 | | 304 | 请求资源没改变,使用缓存 | | 400 | 请求错误,最常见的是请求参数有问题 | | 404 | 请求资源未找到 | | 405 | 请求方式不支持 | | 500 | 服务器运行内部错误 |

  • 状态码首位数字的含义 | 状态码 | 说明 | | —- | —- | | 1xx | 消息相关 | | 2xx | 成功相关 | | 3xx | 重定向相关 | | 4xx | 客户端相关错误 | | 5xx | 服务器相关错误 |

(3)响应对象使用示例(响应中文乱码问题)

  • 响应字节流输出中文乱码问题。

    1. /**
    2. * @author 黑马程序员
    3. * @Company http://www.itheima.com
    4. */
    5. public class ResponseDemo1 extends HttpServlet {
    6. /**
    7. * 演示字节流输出的乱码问题
    8. */
    9. public void doGet(HttpServletRequest request, HttpServletResponse response)
    10. throws ServletException, IOException {
    11. /**
    12. * 问题:
    13. * String str = "字节流中文乱码问题";
    14. * 使用字节流输出,会不会产生中文乱码?
    15. * 答案:
    16. * 会产生乱码
    17. * 原因:
    18. * String str = "字节流中文乱码问题"; 在保存时用的是IDEA创建文件使用的字符集UTF-8。
    19. * 到浏览器上显示,chrome浏览器和ie浏览器默认的字符集是GB2312(其实就是GBK),存和取用的不是同一个码表,就会产生乱码。
    20. *
    21. * 引申:
    22. * 如果产生了乱码,就是存和取用的不是同一个码表
    23. * 解决办法:
    24. * 把存和取的码表统一。
    25. */
    26. String str = "字节流输出中文的乱码问题";//UTF-8的字符集,此时浏览器显示也需要使用UTF-8的字符集。
    27. //1.拿到字节流输出对象
    28. ServletOutputStream sos = response.getOutputStream();
    29. /**
    30. * 解决办法:
    31. * 第一种解决办法:
    32. * 修改浏览器的编码,使用右键——编码——改成UTF-8。(不推荐使用,我们的应用尽量不要求用户取做什么事情)
    33. * ie和火狐浏览器可以直接右键设置字符集。而chrome需要安装插件,很麻烦。
    34. * 第二种解决办法: (不建议使用,因为不好记)
    35. * 向页面上输出一个meta标签,内容如下: <meta http-equiv="content-type" content="text/html;charset=UTF-8">
    36. * 其实它就是指挥了浏览器,使用哪个编码进行显示。
    37. * 第三种解决办法:
    38. * 设置响应消息头,告知浏览器响应正文的MIME类型和字符集
    39. * response.setHeader("Content-Type","text/html;charset=UTF-8");
    40. * 第四种解决办法:我们推荐使用的办法
    41. * 它的本质就是设置了一个响应消息头
    42. * response.setContentType("text/html;charset=UTF-8");
    43. */
    44. //第二种解决办法:sos.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'>".getBytes());
    45. //第三种解决办法:response.setHeader("Content-Type","text/html;charset=UTF-8");
    46. //第四种解决办法:
    47. response.setContentType("text/html;charset=UTF-8");
    48. //2.把str转换成字节数组之后输出到浏览器
    49. sos.write(str.getBytes("UTF-8"));
    50. }
    51. public void doPost(HttpServletRequest request, HttpServletResponse response)
    52. throws ServletException, IOException {
    53. doGet(request, response);
    54. }
    55. }
  • 响应字符流输出中文乱码问题。

    1. /**
    2. * @author 黑马程序员
    3. * @Company http://www.itheima.com
    4. */
    5. public class ResponseDemo2 extends HttpServlet {
    6. /**
    7. * 字符流输出中文乱码
    8. * @param request
    9. * @param response
    10. * @throws ServletException
    11. * @throws IOException
    12. */
    13. public void doGet(HttpServletRequest request, HttpServletResponse response)
    14. throws ServletException, IOException {
    15. String str = "字符流输出中文乱码";
    16. //response.setCharacterEncoding("UTF-8");
    17. //设置响应正文的MIME类型和字符集
    18. response.setContentType("text/html;charset=UTF-8");
    19. //1.获取字符输出流
    20. PrintWriter out = response.getWriter();
    21. //2.使用字符流输出中文
    22. /**
    23. * 问题:
    24. * out.write(str); 直接输出,会不会产生乱码
    25. * 答案:
    26. * 会产生乱码
    27. * 原因:
    28. * 存用的什么码表:UTF-8
    29. * 在浏览器取之前,字符流PrintWriter已经获取过一次了,PrintWriter它在取的时候出现了乱码。
    30. * 浏览器取默认用的是GBK。(本地系统字符集)
    31. *
    32. * UTF-8(存)————>PrintWriter ISO-8859-1(取) 乱
    33. * PrintWirter ISO-8859-1(存)————>浏览器 GBK(取) 乱
    34. *
    35. * 解决办法:
    36. * 改变PrintWriter的字符集,PrintWriter是从response对象中获取的,其实设置response的字符集。
    37. * 注意:设置response的字符集,需要在拿流之前。
    38. * response.setCharacterEncoding("UTF-8");
    39. *
    40. * response.setContentType("text/html;charset=UTF-8");
    41. * 此方法,其实是做了两件事:
    42. * 1.设置响应对象的字符集(包括响应对象取出的字符输出流)
    43. * 2.告知浏览器响应正文的MIME类型和字符集
    44. */
    45. out.write(str);
    46. }
    47. public void doPost(HttpServletRequest request, HttpServletResponse response)
    48. throws ServletException, IOException {
    49. doGet(request, response);
    50. }
    51. }

(4)响应对象之图片验证码

  1. package com.xiaoha.verification;
  2. import javax.imageio.ImageIO;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.awt.*;
  8. import java.awt.image.BufferedImage;
  9. import java.io.IOException;
  10. import java.util.Random;
  11. /**
  12. * @author HausenLee
  13. * @Company http://www.xiaoha.com
  14. *
  15. */
  16. public class VerificationServlet extends HttpServlet {
  17. /**
  18. * 输出图片
  19. * @param request
  20. * @param response
  21. * @throws ServletException
  22. * @throws IOException
  23. */
  24. public void doGet(HttpServletRequest request, HttpServletResponse response)
  25. throws ServletException, IOException {
  26. int width = 200;
  27. int height = 35;
  28. /**
  29. * 实现步骤:
  30. * 1.创建图像内存对象
  31. * 2.拿到画笔
  32. * 3.设置颜色,画矩形边框
  33. * 4.设置颜色,填充矩形
  34. * 5.设置颜色,画干扰线
  35. * 6.设置颜色,画验证码
  36. * 7.把内存图像输出到浏览器上
  37. */
  38. //创建内存图像
  39. BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);//参数:宽度,高度 (指的都是像素),使用的格式(RGB)
  40. Graphics g = image.getGraphics();//画笔就一根
  41. //设置颜色
  42. g.setColor(Color.BLUE);
  43. //画边框
  44. g.drawRect(0, 0, width, height);
  45. //设置矩形图片底色颜色
  46. g.setColor(Color.GRAY);
  47. //将颜色填充到矩形
  48. g.fillRect(1, 1, width-2, height-2);
  49. //设置干扰线条的颜色
  50. g.setColor(Color.WHITE);
  51. //拿随机数对象
  52. Random r = new Random();
  53. //画干扰线 10条
  54. for(int i=0;i<10;i++){
  55. g.drawLine(r.nextInt(width), r.nextInt(height),r.nextInt(width), r.nextInt(height));
  56. }
  57. //设置字体颜色
  58. g.setColor(Color.RED);
  59. //改变字体大小
  60. Font font = new Font("宋体", Font.BOLD,30);//参数:1字体名称。2.字体样式 3.字体大小
  61. g.setFont(font);//设置字体
  62. //画验证码 4个
  63. int x = 35;//第一个数的横坐标是35像素
  64. for(int i=0;i<4;i++){
  65. //r.nextInt(10)+""这种写法效率是十分低的
  66. g.drawString(String.valueOf(r.nextInt(10)), x, 25);
  67. x+=35;
  68. }
  69. //输出到浏览器上
  70. //参数: 1.内存对象。2.输出的图片格式。3.使用的输出流
  71. //ImageIO是图像影像的操作对象
  72. ImageIO.write(image, "jpg", response.getOutputStream());
  73. }
  74. public void doPost(HttpServletRequest request, HttpServletResponse response)
  75. throws ServletException, IOException {
  76. doGet(request, response);
  77. }
  78. }

(5)设置响应消息头—控制缓存

  • 代码演示 ```java /**

    • 设置缓存时间
    • 使用缓存的一般都是静态资源
    • 动态资源一般不能缓存。
    • 我们现在目前只掌握了Servlet,所以用Servlet做演示
    • @author 黑马程序员
    • @Company http://www.itheima.com / public class ResponseDemo4 extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. String str = "设置缓存时间";
      3. /*
      4. * 设置缓存时间,其实就是设置响应消息头:Expires 但是值是一个毫秒数。
      5. * 使用的是
      6. * response.setDateHeader();
      7. *
      8. * 缓存1小时,是在当前时间的毫秒数上加上1小时之后的毫秒值
      9. */
      10. response.setDateHeader("Expires",System.currentTimeMillis()+1*60*60*1000);
      11. response.setContentType("text/html;charset=UTF-8");
      12. response.getOutputStream().write(str.getBytes());

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. doGet(request, response);

      }

}

  1. <a name="BLh7p"></a>
  2. ### (6)设置响应消息头--定时刷新
  3. - 代码演示

/**

  • 设置响应消息头:
  • 通过定时刷新演示添加消息头
  • @author 黑马程序员
  • @Company http://www.itheima.com / public class ResponseDemo5 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. String str = "用户名和密码不匹配,2秒后转向登录页面...";
    3. response.setContentType("text/html;charset=UTF-8");
    4. PrintWriter out = response.getWriter();
    5. out.write(str);
    6. //定时刷新,其实就是设置一个响应消息头
    7. response.setHeader("Refresh", "2;URL=/login.html");//Refresh设置的时间单位是秒,如果刷新到其他地址,需要在时间后面拼接上地址

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. doGet(request, response);

    }

}

  1. <a name="a5PY8"></a>
  2. ### (7)请求重定向(地址栏会发生变化)
  3. - 代码演示

/**

  • 设置响应状态码,实现重定向
  • 重定向的特点:
  • 两次请求,地址栏改变,浏览器行为,xxxx
  • @author 黑马程序员
  • @Company http://www.itheima.com / public class ResponseDemo6 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. //1.设置响应状态码

    // response.setStatus(302);

    1. //2.定向到哪里去: 其实就是设置响应消息头,Location

    // response.setHeader(“Location”, “ResponseDemo7”);

    1. //使用重定向方法
    2. response.sendRedirect("ResponseDemo7");//此行做了什么事,请看上面

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. doGet(request, response);

    }

}

/**

  • 重定向的目的地
  • @author 黑马程序员
  • @Company http://www.itheima.com */ public class ResponseDemo7 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. response.getWriter().write("welcome to ResponseDemo7");

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. doGet(request, response);

    }

}

  1. <a name="N9v6z"></a>
  2. ### (8)响应和消息头组合应用之文件下载
  3. - 在工程的web目录新建一个目录uploads,并且拷贝一张图片到目录中,如下图所示:
  4. ![](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)
  5. - servlet代码如下:

/**

  • 文件下载
  • @author 黑马程序员
  • @Company http://www.itheima.com / public class ResponseDemo8 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. /*
    3. * 文件下载的思路:
    4. * 1.获取文件路径
    5. * 2.把文件读到字节输入流中
    6. * 3.告知浏览器,以下载的方式打开(告知浏览器下载文件的MIME类型)
    7. * 4.使用响应对象的字节输出流输出到浏览器上
    8. */
    9. //1.获取文件路径(绝对路径)
    10. ServletContext context = this.getServletContext();
    11. String filePath = context.getRealPath("/uploads/6.jpg");//通过文件的虚拟路径,获取文件的绝对路径
    12. //2.通过文件路径构建一个字节输入流
    13. InputStream in = new FileInputStream(filePath);
    14. //3.设置响应消息头
    15. response.setHeader("Content-Type", "application/octet-stream");//注意下载的时候,设置响应正文的MIME类型,用application/octet-stream
    16. response.setHeader("Content-Disposition", "attachment;filename=1.jpg");//告知浏览器以下载的方式打开
    17. //4.使用响应对象的字节输出流输出
    18. OutputStream out = response.getOutputStream();
    19. int len = 0;
    20. byte[] by = new byte[1024];
    21. while((len = in.read(by)) != -1){
    22. out.write(by, 0, len);
    23. }
    24. in.close();

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. doGet(request, response);

    }

}

  1. <a name="nuJnf"></a>
  2. ### (0)响应对象的注意事项
  3. - 第一:response得到的字符流和字节流是互斥的,只能选其一;
  4. - 第二:response获取的流不用关闭,由服务器关闭。
  5. <a name="hSG80"></a>
  6. ## 2.2 request请求对象的认识与使用
  7. <a name="gatk8"></a>
  8. ### (1)请求对象的概述
  9. - 请求,顾名思义,就是使用者希望从服务器端索取一些资源,向服务器发出询问。在B/S架构中,就是客户端浏览器向服务器发出询问。在我们的JavaEE工程中,客户端浏览器发出询问,要遵循HTTP协议所规定的。请求对象,就是在JavaEE工程中,用于发送请求的对象。我们常用的对象是ServletRequest和HttpServletRequest,他们的区别就是是否和HTTP协议有关。
  10. - 请求对象的类关系图(类视图)
  11. ![](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)
  12. <a name="kyXRf"></a>
  13. ### (2)请求对象的常用方法
  14. ![](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)
  15. <a name="Aw2l5"></a>
  16. ### (3)请求对象的使用示例(常用方法)
  17. - 请求对象的常用方法——获取各种请求路径

/**

  • 请求对象的各种信息获取
  • @author 黑马程序员
  • @Company http://www.itheima.com */ public class RequestDemo1 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. //本机地址:服务器地址
    3. String localAddr = request.getLocalAddr();
    4. //本机名称:服务器名称
    5. String localName = request.getLocalName();
    6. //本机端口:服务器端口
    7. int localPort = request.getLocalPort();
    8. //来访者ip
    9. String remoteAddr = request.getRemoteAddr();
    10. //来访者主机
    11. String remoteHost = request.getRemoteHost();
    12. //来访者端口
    13. int remotePort = request.getRemotePort();
    14. //统一资源标识符
    15. String URI = request.getRequestURI();
    16. //统一资源定位符
    17. String URL = request.getRequestURL().toString();
    18. //获取查询字符串
    19. String queryString = request.getQueryString();
    20. //获取Servlet映射路径
    21. String servletPath = request.getServletPath();
    22. //输出内容
    23. System.out.println("getLocalAddr() is :"+localAddr);
    24. System.out.println("getLocalName() is :"+localName);
    25. System.out.println("getLocalPort() is :"+localPort);
    26. System.out.println("getRemoteAddr() is :"+remoteAddr);
    27. System.out.println("getRemoteHost() is :"+remoteHost);
    28. System.out.println("getRemotePort() is :"+remotePort);
    29. System.out.println("getRequestURI() is :"+URI);
    30. System.out.println("getRequestURL() is :"+URL);
    31. System.out.println("getQueryString() is :"+queryString);
    32. System.out.println("getServletPath() is :"+servletPath);

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. doGet(request, response);

    } } ```

  • 获取请求头信息 ``` /**

    • 获取请求消息头
    • @author 黑马程序员
    • @Company http://www.itheima.com */ public class RequestDemo2 extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. //1.根据名称获取头的值 一个消息头一个值
      3. String value = request.getHeader("Accept-Encoding");
      4. System.out.println("getHeader():"+value);
      5. //2.根据名称获取头的值 一个头多个值
      6. Enumeration<String> values = request.getHeaders("Accept");
      7. while(values.hasMoreElements()){
      8. System.out.println("getHeaders():"+values.nextElement());
      9. }
      10. //3.获取请求消息头的名称的枚举
      11. Enumeration<String> names = request.getHeaderNames();
      12. while(names.hasMoreElements()){
      13. String name = names.nextElement();
      14. String value1 = request.getHeader(name);
      15. System.out.println(name+":"+value1);
      16. }

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. doGet(request, response);

      }

}

  1. - 获取请求参数(重要)
  2. - x先准备一个HTML表单页面
  3. ```html
  4. <html>
  5. <head>
  6. <title>login to request demo 3</title>
  7. </head>
  8. <body>
  9. <form action="/day10_1122_requestresponse/RequestDemo3" method="post">
  10. 用户名:<input type="text" name="username" /><br/>
  11. 密码:<input type="password" name="password" /><br/>
  12. 性别:<input type="radio" name="gender" value="1" checked>男
  13. <input type="radio" name="gender" value="0">女
  14. <br/>
  15. <input type="submit" value="注册" />
  16. </form>
  17. </body>
  18. </html>
  • getParameter()方法获取请求参数的示例代码

    1. /**
    2. * 获取请求正文,一个名称对应一个值。 没有使用确认密码
    3. * @param request
    4. * @param response
    5. * @throws ServletException
    6. * @throws IOException
    7. */
    8. private void test1(HttpServletRequest request, HttpServletResponse response)
    9. throws ServletException, IOException {
    10. //1.获取请求正文
    11. String username = request.getParameter("username");
    12. String password = request.getParameter("password");
    13. String gender = request.getParameter("gender");
    14. System.out.println(username+","+password+","+gender);
    15. }
  • getParameterValues()方法的示例代码

    1. /**
    2. * 获取请求正文,一个名称可能对应多个值 使用了确认密码
    3. * @param request
    4. * @param response
    5. * @throws ServletException
    6. * @throws IOException
    7. */
    8. private void test2(HttpServletRequest request, HttpServletResponse response)
    9. throws ServletException, IOException {
    10. //1.获取请求正文
    11. String username = request.getParameter("username");
    12. String[] password = request.getParameterValues("password");//当表单中有多个名称是一样时,得到是一个字符串数组
    13. String gender = request.getParameter("gender");
    14. System.out.println(username+","+Arrays.toString(password)+","+gender);
    15. }
  • getParameterNames()方法的示例代码

    1. /**
    2. * 获取请求正文,一个名称一个值。但是先要获取正文名称的枚举(key的枚举) 没有使用确认密码
    3. * @param request
    4. * @param response
    5. * @throws ServletException
    6. * @throws IOException
    7. */
    8. private void test3(HttpServletRequest request, HttpServletResponse response)
    9. throws ServletException, IOException {
    10. //1.获取请求正文名称的枚举
    11. Enumeration<String> names = request.getParameterNames();
    12. //2.遍历正文名称的枚举
    13. while(names.hasMoreElements()){
    14. String name = names.nextElement();
    15. String value = request.getParameter(name);
    16. System.out.println(name+":"+value);
    17. }
    18. }

    (4)封装请求参数到实体类

  • 我们通过上面的方法可以获取到请求参数,但是如果参数过多,在进行传递时,方法的形参定义将会变得非常难看。此时我们应该用一个对象来描述这些参数,它就是实体类。这种类的定义,从基础阶段我们就开始使用了。在基础阶段,我们做过一个学生管理系统,用到了一个Student的类,它就是用于描述一个学生的实体类。

  • 第一种:最简单的直接封装法

    1. /**
    2. * 封装请求正文到User对象中 没有使用确认密码
    3. * @param request
    4. * @param response
    5. * @throws ServletException
    6. * @throws IOException
    7. */
    8. private void test4(HttpServletRequest request, HttpServletResponse response)
    9. throws ServletException, IOException {
    10. //1.获取请求正文
    11. String username = request.getParameter("username");
    12. String password = request.getParameter("password");
    13. String gender = request.getParameter("gender");
    14. //2.创建一个User对象
    15. User user = new User();
    16. System.out.println("封装前:"+user.toString());
    17. //3.把请求正文封装到user对象中
    18. user.setUsername(username);
    19. user.setPassword(password);
    20. user.setGender(gender);
    21. System.out.println("封装后:"+user.toString());
    22. }
  • 第二种方式:使用反射

    1. /**
    2. * 封装请求正文到javabean中 没有使用确认密码
    3. * 使用反射+内省实现数据模型的封装
    4. * 内省:是sun公司推出的一套简化反射操作的规范。把javabean中的元素都封装成一个属性描述器。
    5. * 属性描述器中会有字段信息,get和set方法(取值或存值)
    6. * @param request
    7. * @param response
    8. * @throws ServletException
    9. * @throws IOException
    10. */
    11. private void test5(HttpServletRequest request, HttpServletResponse response)
    12. throws ServletException, IOException {
    13. //1.获取请求正文名称的枚举
    14. Enumeration<String> names = request.getParameterNames();
    15. User user = new User();
    16. System.out.println("封装前:"+user.toString());
    17. //2.遍历正文名称的枚举
    18. while(names.hasMoreElements()){
    19. String name = names.nextElement();
    20. String value = request.getParameter(name);
    21. try{
    22. //1.拿到User对象中的属性描述器。是谁的属性描述器:是由构造函数的第一个参数决定的。第二个参数是指定javabean的字节码
    23. PropertyDescriptor pd = new PropertyDescriptor(name, User.class);//参数指的就是拿哪个类的哪个属性的描述器
    24. //2.设置javabean属性的值
    25. Method method = pd.getWriteMethod();
    26. //3.执行方法
    27. method.invoke(user, value);//第一个参数是指的给哪个对象,第二个参数指的是赋什么值
    28. }catch(Exception e){
    29. e.printStackTrace();
    30. }
    31. }
    32. System.out.println("封装后:"+user.toString());
    33. }
  • 第三种:使用反射,同时请求参数是一个数组;此方法就是针对请求参数中包含name属性相同的参数;例如:密码和确认密码、兴趣爱好等。

    1. /**
    2. * 获取请求正文的关系映射Map<String,String[]> 使用确认密码
    3. * @param request
    4. * @param response
    5. * @throws ServletException
    6. * @throws IOException
    7. */
    8. private void test6(HttpServletRequest request, HttpServletResponse response)
    9. throws ServletException, IOException {
    10. //1.获取请求正文的映射关系
    11. Map<String,String[]> map = request.getParameterMap();
    12. //2.遍历集合
    13. for(Map.Entry<String,String[]> me : map.entrySet()){
    14. String name = me.getKey();
    15. String[] value = me.getValue();
    16. System.out.println(name+":"+Arrays.toString(value));
    17. }
    18. }
  • 当我们将请求参数取出来之后,就用反射进行封装

    1. /**
    2. * 封装请求正文到javabean。使用的是反射+内省 使用了确认密码
    3. * @param request
    4. * @param response
    5. * @throws ServletException
    6. * @throws IOException
    7. */
    8. private void test7(HttpServletRequest request, HttpServletResponse response)
    9. throws ServletException, IOException {
    10. //1.获取请求正文的映射关系
    11. Map<String,String[]> map = request.getParameterMap();
    12. Users user = new Users();
    13. System.out.println("封装前:"+user.toString());
    14. //2.遍历集合
    15. for(Map.Entry<String,String[]> me : map.entrySet()){
    16. String name = me.getKey();
    17. String[] value = me.getValue();
    18. try{
    19. //1.拿到User对象中的属性描述器。是谁的属性描述器:是由构造函数的第一个参数决定的。第二个参数是指定javabean的字节码
    20. PropertyDescriptor pd = new PropertyDescriptor(name, Users.class);//参数指的就是拿哪个类的哪个属性的描述器
    21. //2.设置javabean属性的值
    22. Method method = pd.getWriteMethod();
    23. //3.执行方法
    24. //判断参数到底是几个值
    25. if(value.length > 1){//最少有2个元素
    26. method.invoke(user, (Object)value);//第一个参数是指的给哪个对象,第二个参数指的是赋什么值
    27. }else{
    28. method.invoke(user, value);//第一个参数是指的给哪个对象,第二个参数指的是赋什么值
    29. }
    30. }catch(Exception e){
    31. e.printStackTrace();
    32. }
    33. }
    34. System.out.println("封装后:"+user.toString());
    35. }
  • 当我们写完此种封装方式之后,同学们可以发现,我们绝大多数封装都可以使用这段代码来实现。并且,无论是谁来写这段通用的封装代码,其代码内容都是大同小异的。那么,我们就可以得出一个很有趣的结论:一般遇到这种情况时,肯定有人帮我们写好了,我们只需要用就行了。我们后面还会遇到类似这样的情况。

  • 此时,帮我们写好这段封装代码的是apache软件基金会,我们前面学习的tomcat也是它提供的。它里面有一个开源工具包集合commons,里面有很多开源工具类,今天我们就来讲解第一个:commons-beanutils

    1. /**
    2. * 终极方法:使用beanutils实现请求正文封装到javabean中 使用了确认密码
    3. * 要想使用beanutils,需要先导包
    4. * @param request
    5. * @param response
    6. * @throws ServletException
    7. * @throws IOException
    8. */
    9. private void test8(HttpServletRequest request, HttpServletResponse response)
    10. throws ServletException, IOException {
    11. Users user = new Users();
    12. System.out.println("封装前:"+user.toString());
    13. try{
    14. BeanUtils.populate(user, request.getParameterMap());//就这一句话
    15. }catch(Exception e){
    16. e.printStackTrace();
    17. }
    18. System.out.println("封装后:"+user.toString());
    19. }
  • 用流的方式读取请求参数 ```java /**

    • 使用流的方式读取请求正文
    • @author 黑马程序员
    • @Company http://www.itheima.com */ public class RequestDemo4 extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. //1.获取请求正文的字节输入流
      3. ServletInputStream sis = request.getInputStream();
      4. //2.读取流中的数据
      5. int len = 0;
      6. byte[] by = new byte[1024];
      7. while((len = sis.read(by)) != -1){
      8. System.out.println(new String(by,0,len));
      9. }

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. doGet(request, response);

      }

}

  1. <a name="yrJ2X"></a>
  2. ### (5)请求正文乱码的问题
  3. - POST方式乱码问题解决
  4. ```java
  5. /**
  6. * 请求正文的中文乱码问题
  7. * @author 黑马程序员
  8. * @Company http://www.itheima.com
  9. */
  10. public class RequestDemo5 extends HttpServlet {
  11. public void doGet(HttpServletRequest request, HttpServletResponse response)
  12. throws ServletException, IOException {
  13. //1.获取请求正文
  14. /*POST方式:
  15. * 问题:
  16. * 取的时候会不会有乱码
  17. * 答案:
  18. * 获取请求正文,会有乱码问题。
  19. * 是在获取的时候就已经乱码了。
  20. * 解决办法:
  21. * 是request对象的编码出问题了
  22. * 设置request对象的字符集
  23. * request.setCharacterEncoding("GBK");它只能解决POST的请求方式,GET方式解决不了
  24. * 结论:
  25. * 请求正文的字符集和响应正文的字符集没有关系。各是各的
  26. */
  27. request.setCharacterEncoding("UTF-8");
  28. String username = request.getParameter("username");
  29. //输出到控制台
  30. System.out.println(username);
  31. //输出到浏览器:注意响应的乱码问题已经解决了
  32. response.setContentType("text/html;charset=UTF-8");
  33. PrintWriter out = response.getWriter();
  34. out.write(username);
  35. }
  36. public void doPost(HttpServletRequest request, HttpServletResponse response)
  37. throws ServletException, IOException {
  38. doGet(request, response);
  39. }
  40. }
  • Get请求方式乱码问题解决
    • GET方式请求的正文是在地址栏中,在Tomcat8.5版本及以后,Tomcat服务器已经帮我们解决了,所以不会有乱码问题了。
    • 而如果我们使用的不是Tomcat服务器,或者Tomcat的版本是8.5以前,那么GET方式仍然会有乱码问题,解决方式如下:(以下代码了解即可,因为我们现在使用的是Tomcat9.0.27版本) ```java /**
      • 在Servlet的doGet方法中添加如下代码 */ public void doGet(HttpServletRequest request, HttpServletResponse response)
        1. throws ServletException, IOException {
  1. /*
  2. * GET方式:正文在地址栏
  3. * username=%D5%C5%C8%FD
  4. * %D5%C5%C8%FD是已经被编过一次码了
  5. *
  6. * 解决办法:
  7. * 使用正确的码表对已经编过码的数据进行解码。
  8. * 就是把取出的内容转成一个字节数组,但是要使用正确的码表。(ISO-8859-1)
  9. * 再使用正确的码表进行编码
  10. * 把字节数组再转成一个字符串,需要使用正确的码表,是看浏览器当时用的是什么码表
  11. */
  12. String username = request.getParameter("username");
  13. byte[] by = username.getBytes("ISO-8859-1");
  14. username = new String(by,"GBK");
  15. //输出到浏览器:注意响应的乱码问题已经解决了
  16. response.setContentType("text/html;charset=UTF-8");
  17. PrintWriter out = response.getWriter();
  18. out.write(username);

}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }

  1. <a name="Ekhw9"></a>
  2. ### (6)请求转发(与重定向的区别)
  3. - 重定向的特点:它是两次请求,属于浏览器行为,地址栏会改变,请求域中的数据会丢失
  4. - 请求转发的特点:一次请求,属于服务器行为,地址栏不会改变,请求域中的数据不丢失
  5. - 请求域的作用范围:当前请求(一次请求),和当前请求的转发之中。
  6. - 请求转发示例代码
  7. ```java
  8. public class RequestDemo6 extends HttpServlet {
  9. public void doGet(HttpServletRequest request, HttpServletResponse response)
  10. throws ServletException, IOException {
  11. //1.拿到请求调度对象
  12. RequestDispatcher rd = request.getRequestDispatcher("/RequestDemo7");//如果是给浏览器看的,/可写可不写。如果是给服务器看的,一般情况下,/都是必须的。
  13. //放入数据到请求域中
  14. request.setAttribute("CityCode", "bj-010");
  15. //2.实现真正的转发操作
  16. rd.forward(request, response);//实现真正的转发操作
  17. }
  18. public void doPost(HttpServletRequest request, HttpServletResponse response)
  19. throws ServletException, IOException {
  20. doGet(request, response);
  21. }
  22. }
  23. /**
  24. * 转发的目的地
  25. * @author 黑马程序员
  26. * @Company http://www.itheima.com
  27. */
  28. public class RequestDemo7 extends HttpServlet {
  29. public void doGet(HttpServletRequest request, HttpServletResponse response)
  30. throws ServletException, IOException {
  31. //获取请求域中的数据
  32. String value = (String)request.getAttribute("CityCode");
  33. response.getWriter().write("welcome to request demo 7 "+value);
  34. }
  35. public void doPost(HttpServletRequest request, HttpServletResponse response)
  36. throws ServletException, IOException {
  37. doGet(request, response);
  38. }
  39. }
  • 请求包含示例代码 ```java /**

    • 请求包含 *
    • 它是把两个Servlet的响应内容合并输出。
    • 注意:
    • 这种包含是动态包含。 *
    • 动态包含的特点:
    • 各编译各的,只是最后合并输出。
    • @author 黑马程序员
    • @Company http://www.itheima.com */ public class RequestDemo8 extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. response.getWriter().write("I am request demo8 ");
      3. //1.拿到请求调度对象
      4. RequestDispatcher rd = request.getRequestDispatcher("/RequestDemo9");
      5. //2.实现包含的操作
      6. rd.include(request, response);

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

      1. throws ServletException, IOException {
      2. doGet(request, response);

      } }

/**

  • 被包含者
  • @author 黑马程序员
  • @Company http://www.itheima.com */ public class RequestDemo9 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. response.getWriter().write("include request demo 9 ");

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

    1. throws ServletException, IOException {
    2. doGet(request, response);

    }

} ```

  • 请求转发和请求包含的细节问题
    • 请求转发的注意事项:负责转发的Servlet,转发前后的响应正文丢失,由转发目的地来响应浏览器。
    • 请求包含的注意事项:被包含者的响应消息头丢失。因为它被包含起来了。