0%

Servlet

  • 关于Servlet的相关内容及开发一个带有Servlet的WebApp

1.Servlet

1.1 系统架构

  1. 系统架构包括什么形式?
    • C/S 架构
    • B/S 架构
  • C/S 架构(Client / Server,客户端 / 服务器):

    1. 特点:需要安装特定的客户端软件。
    2. 优点:
      • 速度快:软件中的数据大部分都是集成到客户端软件当中的,很少量的数据从服务器端传送过来,所以 C/S 结构的系统速度快。
      • 界面酷炫:专门的语言去实现界面,更加灵活。
      • 服务器压力小:因为大量的数据都是集成在客户端软件当中,所以服务器只需要传送很少的数据量,当然服务器压力小。
      • 安全:因为大量的数据是集成在客户端软件当中的,即使服务器发生故障了,数据在多个客户端上有缓存,有存储,所以不怕数据丢失问题。
    3. 缺点:
      • 升级维护麻烦,每一个客户端软件都需要升级,成本较高。
    • 娱乐性软件建议使用 C/S 架构。
  • B/S 架构(Browser / Server,浏览器 / 服务器):

    • 实际上 B/S 结构的系统还是一个 C/S,只不过这个 C 比较特殊,这个 Client 是一个固定不变的浏览器软件。
    1. 优点:
      • 升级维护方便,成本较低。
      • 不需要安装特定的客户端软件,用户操作方便,只需要打开浏览器,输入网址即可。
    2. 缺点:
      • 速度慢:不是因为带宽低的问题,是因为所有的数据都是在服务器上,用户发送的每一个请求都需要服务器的响应,所以 B/S 结构的系统在网络中传送的数据量比较大。
      • 不安全:所有的数据都在服务器上,只要服务器发生故障,软件服务就会瘫痪,数据也可能丢失。
    • 公司内部使用的业务软件建议使用 B/S 架构。

    开发 B/S 架构的系统,其实就是开发网站,就是开发一个 Web 系统

    • Java 做 Web 开发我们称为 JavaWeb 开发,JavaWeb 开发最核心的规范就是 ServletServer Applet,服务器端的 Java 小程序)。

1.2 B/S架构通信原理

  • 一个 Web 系统的通信原理(以百度为例):

    1. 用户输入网址 URLhttp://www.baidu.com
    2. 域名系统 DNSURL 进行域名解析:http://153.3.238.110:80/index.html
    3. 浏览器软件在网络中搜索 153.3.238.110 这一台主机,直到找到这台主机。
    4. 该主机根据端口号 80 定位其上的服务器软件。
    5. 服务器上 80 端口对应的服务器软件得知浏览器请求的资源名是 index.html
    6. 服务器软件找到 index.html 文件,并将该文件的内容直接输出到请求服务的浏览器软件上,作为响应
    7. 浏览器软件接收到来自服务器的代码,并进行渲染,向用户展示结果。
  • 什么是请求,什么是响应:

    • 请求和响应实际上说的是数据的流向不同。

      从 Browser 端发送数据到 Server 端,我们称为请求,request

      从 Server 端向浏览器 Browser 端发送数据,我们称为响应,response

    • B -> S(request)

      S -> B(response)

1.3 Web服务器

  • Web服务器有哪些:

    • Tomcat(Web服务器)
    • jetty(Web服务器)
    • JBOSS(应用服务器)
    • WebLogic(应用服务器)
    • WebSphere(应用服务器)
  • 应用服务器和 Web服务器的关系:

    • 应用服务器实现了JavaEE 的所有规范(JavaEE 有 13 个不同的规范)。

      Web服务器只实现了 JavaEE 中的 Servlet + JSP 两个核心的规范

    • 说明应用服务器是包含 Web服务器的。

1.4 Tomcat服务器

  • 关于 Tomcat 服务器的目录:

    • bin:Tomcat 服务器的命令文件存放目录,比如:启动Tomcat、关闭Tomcat等。
    • conf:Tomcat 服务器的配置文件存放目录(server.xml 文件中可以配置端口号,默认 Tomcat 端口是 8080)。
    • lib:Tomcat 服务器的核心程序目录,因为 Tomcat 服务器是 Java 语言编写的,这里的 jar 包里面都是 class 文件。
    • logs:Tomcat 服务器的日志目录,Tomcat 服务器启动等信息都会在这个目录下生成日志文件。
    • temp:Tomcat 服务器的临时目录,存储临时文件。
    • webapps:这个目录就是用来存放大量的 webapp(web application :web应用)。
    • work:这个目录用来存放 JSP 文件翻译之后的 Java 文件以及编译之后的 class 文件。
  • Tomcat 还有另外一个名字:catalina(catalina 是美国的一个岛屿,风景秀丽,据说作者是在这个风景秀丽的小岛上开发了一个轻量级的 Web 服务器,体积小,运行速度快,因此 Tomcat 又被称为 catalina)。

  • 启动 Tomcat 服务器需要:

    • 配置 Tomcat 服务器的环境变量,在 Path 中配置 Tomcat 的 bin 目录:C:\Program Files\Apache Software Foundation\Tomcat 8.5_Running_Noob\bin

      • 根据这个找到 Tomcat 启动的 startup.bat 文件
    • 配置 Tomcat 服务器的环境变量:CATALINA_HOME=C:\Program Files\Apache Software Foundation\Tomcat 8.5_Running_Noob

      • 因为 startup.bat 文件中需要用到 CATALINA_HOME 环境变量:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        rem Guess CATALINA_HOME if not defined
        set "CURRENT_DIR=%cd%"
        if not "%CATALINA_HOME%" == "" goto gotHome
        set "CATALINA_HOME=%CURRENT_DIR%"
        if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
        cd ..
        set "CATALINA_HOME=%cd%"
        cd "%CURRENT_DIR%"
        :gotHome
        if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
        echo The CATALINA_HOME environment variable is not defined correctly
        echo This environment variable is needed to run this program
        goto end
        :okHome
      • 所以上述 Path 中的配置可以改为 %CATALINA_HOME%\bin

    • 配置 JDK 的环境变量:JAVA_HOME=D:\Program Files\Java\jdk1.8.0_261

      • startup.bat 会调用 catalina.bat 文件,而在 catalina.bat 文件中,需要用到 JAVA_HOME 环境变量:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        echo Using CATALINA_BASE:   "%CATALINA_BASE%"
        echo Using CATALINA_HOME: "%CATALINA_HOME%"
        echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"
        if ""%1"" == ""debug"" goto use_jdk
        echo Using JRE_HOME: "%JRE_HOME%"
        goto java_dir_displayed
        :use_jdk
        echo Using JAVA_HOME: "%JAVA_HOME%"
        :java_dir_displayed
        echo Using CLASSPATH: "%CLASSPATH%"
        echo Using CATALINA_OPTS: "%CATALINA_OPTS%"

    启动 Tomcat 服务器:startup.bat

    关闭 Tomcat 服务器:shutdown.bat

1.5 ★B/S架构系统的角色和协议

1.5.1 角色

  • 对于一个动态的 Web 应用来说,有哪些角色:

    • 浏览器软件(谷歌浏览器、火狐浏览器、Edge浏览器 …)
    • Web Server(Tomcat、jetty、WebLogic、JBOSS、WebSphere …)
    • DB Server(Oracle、Mysql …)
    • WebApp(Web应用,是我们作为 JavaWeb 程序员开发的)

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/BS架构系统的角色.png)

  • 注意:后端到底要执行哪个 Java 程序,取决于前端浏览器发送的请求路径。一个路径对应一个 Servlet Java 程序

    • 由谁来定义请求路径和 Java程序之间的对应关系呢?

      当然是 WebApp 的开发程序员,因为 Java程序是由 WebApp 的开发程序员开发的。

    • 对于我们 JavaWeb 开发者来说,我们可以指定一个配置文件,在配置文件中描述请求路径和 Java程序之间的对照关系

  • 由请求路径找到对应的 Servlet Java 程序 的过程:

    1. 将前端浏览器的请求路径发送给 Web服务器。
    2. Web服务器解析配置文件(该配置文件由 JavaWeb 开发者来配置),通过配置文件找到请求路径所对应的 Java程序的类名。
    3. 在得到 Java程序的类名后,通过反射机制创建对象。
    4. 然后 Web服务器开始执行该 Java程序对象实现的 Servlet 接口的 service 方法。
  • 对于 JavaWeb 程序员来说,只需要做两件事:

    1. 编写 Java类,该类要实现 Servlet 接口。-> 开发程序
    2. 将编写的类配置到配置文件中,在配置文件中指定请求路径和类名的关系。-> 设置配置文件

1.5.2 协议

  • 对于一个动态的 Web 应用来说,角色和角色之间需要遵守哪些协议:

    • 浏览器软件Web Server 之间有一套传输协议:**HTTP 协议**。
    • WebAppWeb Server 之间有一套规范:JavaEE 的规范之一 —— Servlet 规范
      • Servlet 规范的作用是什么 -> WebAppWeb Server 之间的解耦合。使得 WebApp 不管是在 Tomcat 上还是在 jetty 上等,都能正常运行。
    • WebAppDB Server 之间有一套规范:**JDBC 规范**。

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/角色之间的协议.png)

1.6 Servlet本质

  • 前面已经说过,Servlet 是 WebApp 和 Web Server 之间的规范,其中规定了:

    • 开发的 Java程序
      • 一个合格的 WebApp 应该是一个怎样的目录结构。
      • 一个合格的 WebApp 中的 Java程序要放在哪里。
    • 设置的配置文件
      • 一个合格的 WebApp 应该有一个怎样的配置文件。 -> 配置文件名和文件内容格式是固定的,不能乱起。
      • 一个合格的 WebApp 的配置文件路径是什么。 -> 配置文件是放在特定位置的,不能乱放。
  • Servlet 规范是一个什么规范?

    • 遵循 Servlet 规范的 webapp,是可以放在不同的 Web服务器中运行的(因为这个 webapp 是遵循 Servlet 规范的)。
    • Servlet 规范包括什么:
      • 规范了哪些接口
      • 规范了哪些类
      • 规范了一个web应用中应该有哪些配置文件
      • 规范了一个web应用中配置文件的名字
      • 规范了一个web应用中配置文件存放的路径
      • 规范了一个web应用中配置文件的内容
      • 规范了一个web应用的目录结构应该是怎样的

1.7 ★★★开发一个带有Servlet的WebApp

  • 开发步骤:

    1. 在 Tomcat 的 webapps 目录下新建一个目录,起名 crm(可以起别的,随意),那这个 crm 就是 webapp 的名字。

      • crm 就是这个 webapp 的根。
    2. 在这个 webapp 的根下新建一个目录:WEB-INF

      • 注意:这个目录的名字是 Servlet 规范中规定的,必须全部大写,必须一模一样。必须的必须。
    3. WEB-INF 目录下新建一个目录:classes

      • 注意:这个目录的名字必须是全部小写的 classes,这也是 Servlet 规范中规定的。另外这个目录下存放的一定是 Java程序编译之后的 .class文件
    4. WEB-INF 目录下新建一个目录:lib

      • 注意:这个目录不是必须的。但如果一个 webapp 需要第三方的 jar 包的话,这个 jar 包就要放到这个 lib 目录下,这个目录的名字也不能随意编写,必须是全部小写的 lib

        例如 Java 语言连接数据库需要数据库的驱动 jar 包,那么这个 jar 包就一定要放到 lib 目录下,这也是 Servlet 规范中规定的。

    5. WEB-INF 目录下新建一个文件:web.xml

      • 注意:这个文件是必须的,这个文件名必须叫做 web.xml,这个文件必须放在这里。

        一个合法的 webapp,web.xml 文件是必须的,这个 web.xml 文件就是一个配置文件,在这个配置文件中描述了请求路径和 Servlet 类之间的对照关系。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
      https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
      version="6.0"
      metadata-complete="true">


      </web-app>
    6. 编写 Java程序,这个程序也不能随意开发,必须实现 Servlet 接口

      • 注意:这个 Servlet 接口不在 JDK 当中(因为 Servlet 不属于 JavaSE,它属于 JavaEE,是另外一套类库)。

        Servlet 接口(Servlet.class文件)是 Oracle 提供的,是 JavaEE 规范中的一员。

      • 注意:从 JakartaEE 9 开始,Servlet 接口的全名改变了,从 javax.servlet.Servlet 改为了 jakarta.servlet.Servlet

      • 注意:编写 Java程序的时候,Java 源代码你愿意放在哪里就放在哪里,位置无所谓,你只需要将 Java 源代码编译之后的 .class文件放到 classes 目录下即可。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      package com.bjpowernode.servlet;

      // 在Tomcat9以及Tomcat9之前的版本中使用javax.servlet这个包
      import jakarta.servlet.Servlet;
      import jakarta.servlet.ServletException;
      import jakarta.servlet.ServletRequest;
      import jakarta.servlet.ServletResponse;
      import jakarta.servlet.ServletConfig;
      import java.io.IOException;
      import java.io.PrintWriter;


      public class HelloServlet implements Servlet{

      // 5个方法
      public void init(ServletConfig config) throws ServletException{

      }

      public void service(ServletRequest request,ServletResponse response)
      throws ServletException , IOException{

      // 向控制台打印输出
      System.out.println("My First Servlet, Hello Servlet");

      }

      public void destroy(){

      }

      public String getServletInfo(){
      return "";
      }

      public ServletConfig getServletConfig(){
      return null;
      }
      }
    7. 编译写好的 Java程序,将编译后的字节码文件放在 classes 目录下。

      • 注意:怎么能让 Java程序编译通过呢?因为 Java程序肯定需要 import jakarta.servlet.Servlet; ,而我们没有这个类。

        • 我们可以配置环境变量 CLASSPATH=.;%CATALINA_HOME%\lib\servlet-api.jar

          即,在 Tomcat 中的 lib 目录下,是有 jakarta.servlet.Servlet 类的,因为 Tomcat 的运行也需要用到 Servlet 类。所以我们可以用 Tomcat 中的这个 jar 包,作为 import 的来源。

    8. web.xml 文件中编写配置信息,让请求路径和 Servlet 类名能关联在一起。

      • 这一步用专业术语描述:web.xml 文件中注册 Servlet
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
      https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
      version="6.0"
      metadata-complete="true">

      <!--servlet描述信息-->
      <!--任何一个servlet都对应一个servlet-mapping -->
      <servlet>
      <servlet-name>helloservlet</servlet-name>
      <!--这个位置必须是带有包名的全限定类名-->
      <servlet-class>com.bjpowernode.servlet.HelloServlet</servlet-class>
      </servlet>

      <!--servlet映射信息-->
      <servlet-mapping>
      <!--这个也是随便的,不过这里写的内容要和上面的一样。-->
      <servlet-name>helloservlet</servlet-name>
      <!--这里需要一个路径-->
      <!--这个路径唯一的要求是必须以 / 开始-->
      <!--当前这个路径名可以随便写-->
      <!--这里就是浏览器网址中输入的请求路径-->
      <url-pattern>/helloservlet</url-pattern>
      </servlet-mapping>

      </web-app>
      • <url-pattern> 对应请求路径。

        <servlet-name><servlet-mapping><servlet> 对应。

        <servlet-class> 找到与请求路径相对应的 Java程序的全类名。

    9. 启动 Tomcat 服务器:startup.bat

    10. 打开浏览器,输入请求路径:127.0.0.1:8080/crm/helloservlet,得到响应结果:

      • 这里的 /helloservlet 就是 <url-pattern> 里的内容。

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/Tomcat请求的响应结果.png)

      • 注意:浏览器上的请求路径和 web.xml 文件中的 <url-pattern> 的唯一区别就是:浏览器上的请求路径带项目名:/crm
  • 总结:一个合法的 webapp 目录结构应该是怎样的?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    webapproot
    |------WEB-INF
    |------classes(存放字节码)
    |------lib(第三方jar包)
    |------web.xml(注册Servlet)
    |------html
    |------css
    |------javascript
    |------image
    ....
  • 补充:关于 JavaEE 的版本:

    • JavaEE 目前最高版本是 JavaEE8。

    • JavaEE 被 Oracle 捐献了,Oracle 将 JavaEE 规范捐献给了 Apache。

    • Apache 把 JavaEE 换名了,以后不叫 JavaEE了,以后叫做 Jakarta EE。

    • JavaEE8 版本升级之后的 “JavaEE9”,不再是 “JavaEE9” 这个名字了,而是叫做 JakartaEE9。

    • JavaEE8 的时候对应的 Servlet 类名是:javax.servlet.Servlet

      JakartaEE9 的时候对应的 Servlet 类名是:jakarta.servlet.Servlet

1.8 在IDEA中开发Servlet程序

  • 使用 IDEA 集成开发工具开发 Servlet:

    1. 新建一个项目(可以先创建一个空项目,然后在空项目下创建模块),这个项目起名为 javaweb,一般情况下新建的项目的名字最好和目录的名字一致。

    2. javaweb 项目下新建模块,起名为 servlet01

    3. 让新建的 servlet01 模块变为 JavaEE 的模块(让 Module 变为 webapp 模块,符合 webapp 规范。即让 Module 变为符合 Servlet 规范的 Module):

      • 在 Module 上点击右键,添加框架支持。

      • 在弹出的窗口中,选择 Web Application(Web应用程序)。

      • 在选择了这个 Web Application 支持之后,IDEA 会自动给你生成一个符合 Servlet 规范的 webapp 目录结构。

      • 注意:在 IDEA 工具中根据 Web Application 模板生成的目录中有一个 web 目录,这个目录就代表 webapp 的根。

    4. 编写 Servlet 程序(StudentServlet.java)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      package com.f.javaweb.servlet;

      import jakarta.servlet.*;

      import java.io.IOException;
      import java.io.PrintWriter;
      import java.sql.*;

      /**
      * @author fzy
      * @date 2023/10/24 17:55
      */
      public class StudentServlet implements Servlet {
      @Override
      public void init(ServletConfig servletConfig) throws ServletException {

      }

      @Override
      public ServletConfig getServletConfig() {
      return null;
      }

      @Override
      public void service(ServletRequest request, ServletResponse response)
      throws ServletException, IOException {
      //设置相应内容的类型
      response.setContentType("text/html;charset=UTF-8");
      PrintWriter out = response.getWriter();
      //连接数据库(JDBC)
      Connection conn = null;
      PreparedStatement ps = null;
      ResultSet result = null;
      try {
      //注册驱动
      Class.forName("com.mysql.jdbc.Driver");
      //获取连接
      String url = "jdbc:mysql://localhost:3306/javaweb?useSSL=false";
      String user = "root";
      String pwd = "******"; //根据实际情况填写
      conn = DriverManager.getConnection(url, user, pwd);
      //获取预编译的数据库操作对象
      String sql = "SELECT number, name FROM student";
      ps = conn.prepareStatement(sql);
      //执行SQL
      result = ps.executeQuery();
      //处理结果集
      while (result.next()) {
      String number = result.getString("number");
      String name = result.getString("name");
      out.print(number + " - " + name + "<br/>");
      }
      } catch (ClassNotFoundException | SQLException e) {
      throw new RuntimeException(e);
      } finally {
      //释放资源
      if (result != null) {
      try {
      result.close();
      } catch (SQLException e) {
      throw new RuntimeException(e);
      }
      }
      if (ps != null) {
      try {
      ps.close();
      } catch (SQLException e) {
      throw new RuntimeException(e);
      }
      }
      if (conn != null) {
      try {
      conn.close();
      } catch (SQLException e) {
      throw new RuntimeException(e);
      }
      }
      }
      }

      @Override
      public String getServletInfo() {
      return null;
      }

      @Override
      public void destroy() {

      }
      }

      注意:这里要将 jsp-api.jarservlet-api.jar 的 jar包添加到 classpath 中(这里的 classpath 指的是 IDEA 的 classpath)。

      • File --> Project Structure --> Modules --> 选中模块 --> 点击 “+” 添加 --> 添加 JAR包

      注意:在 WEB-INF 目录下新建子目录 lib ,将 mysql 连接的 jar 包添加到 lib 目录中。

    5. web.xml 文件中注册编写的 StudentServlet 程序(请求路径和 Servlet 程序之间对应起来)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
      version="4.0">
      <servlet>
      <servlet-name>studentServlet</servlet-name>
      <servlet-class>com.f.javaweb.servlet.StudentServlet</servlet-class>
      </servlet>
      <servlet-mapping>
      <servlet-name>studentServlet</servlet-name>
      <url-pattern>/servlet/studentlist</url-pattern>
      </servlet-mapping>
      </web-app>
      • 此时如果想要请求 StudentServlet 程序的服务,浏览器网址是什么呢?是 127.0.0.1:8080/web/servlet/studentlist 或者 127.0.0.1:8080/servlet01/servlet/studentlist 吗?
        • 都不是,即根目录的名字其实还未确定,需要等待后面的配置。
    6. 编写一个 html 页面,在 HTML 页面中编写一个超链接,用户点击这个超链接,发送请求,然后 Tomcat 执行后台的 StudentServletservice 方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>student page</title>
      </head>
      <body>
      <!--这里的项目名是 /xmm, 先写死-->
      <a href="/xmm/servlet/studentlist">学生列表</a>
      </body>
      </html>
      • 注意:这里的 /xmm 就是 webapp 的根目录名,是我们在后面配置的。
    7. 让 IDEA 工具去关联 Tomcat 服务器。关联的过程当中将 webapp 部署到 Tomcat 服务器当中

      • IDEA 工具右上角有 编辑配置... 选项。

        点击 “+” 添加 Tomcat服务器-本地。

        在弹出的界面中设置服务器 Server 的参数:

        ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/Tomcat服务器配置参数.png)

      • 在当前窗口中有一个 “部署” 按钮(点击这个用来部署 webapp),继续点击加号,部署即可。

        修改 “应用程序上下文”/xmm,就是在这里,我们指定了该 webapp 的根目录的名字。

        ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/Tomcat服务器部署.png)

    8. 启动 Tomcat 服务器,建议使用 debug 模式启动 Tomcat。

    9. 打开浏览器,在浏览器地址栏中输入 localhost:8080/xmm/student.html 访问 html 页面,并通过点击超链接 <a href="/xmm/servlet/studentlist">学生列表</a> 来请求 StudentServlet 程序的服务。

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/studentlist页面.png)

1.9 ★★★Servlet对象的生命周期

  • Servlet 对象从创建到销毁的整个过程的周期。

  • Servlet 对象是由谁来维护的?

    • Tomcat 服务器(WEB Server)全权负责

      Servlet 对象的创建,对象方法的调用,对象的销毁,Javaweb 程序员是无权干预的。

    • 思考:我们自己 new 的 Servlet 对象受 WEB容器(即 WEB服务器)的管理吗?

      • 我们自己 new 的 Servlet 对象是不受 WEB容器管理的。

        WEB容器创建的 Servlet 对象,会被放到一个集合(HashMap)当中,只有放到这个 HashMap 集合中的 Servlet 对象才能够被 WEB容器管理,自己 new 的 Servlet 对象不会被 WEB容器管理(因为不在容器中)。

        ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/JavaWeb-notebook/Servlet/WEB容器存储Servlet对象.png)

  • 在启动服务器的时候,Servlet对象有没有被创建出来?

    • 可以在 Servlet 中提供一个无参数的构造方法,启动服务器的时候看看构造方法是否执行。

      经过测试发现,默认情况下,服务器在启动的时候 Servlet对象并不会被实例化。

  • 怎么让服务器启动的时候创建Servlet对象呢?

    • 可以在配置文件 web.xml 的 servlet标签中添加 <load-on-startup> 子标签,在该子标签中填写整数,越小的整数优先级越高:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <servlet>
      <servlet-name>aservlet</servlet-name>
      <servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class>
      <!--
      在服务器启动时创建Servlet对象,在该标签中填写整数
      数字表示创建对象的先后顺序
      数字越小优先级越高
      -->
      <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
      <servlet-name>aservlet</servlet-name>
      <url-pattern>/a</url-pattern>
      </servlet-mapping>

      一般情况下,不会在服务器启动时就创建 Servlet对象。

  • Servlet对象生命周期:

    • 默认情况下服务器启动的时候 Servlet对象并没有被实例化。

    • 用户发送第一次请求的时候,控制台输出了以下内容:

      1
      2
      3
      AServlet无参数构造方法执行了
      AServlet's init method execute!
      AServlet's service method execute!

      根据以上输出内容得出结论:

      • 用户在发送第一次请求的时候 Servlet对象被实例化(AServlet的构造方法被执行了,并且执行的是无参数构造方法)。
      • AServlet对象被创建出来之后,Tomcat服务器马上调用了 AServlet对象的 init 方法(init方法在执行的时候,AServlet对象已经存在了。已经被创建出来了)。
      • 用户发送第一次请求的时候,init方法执行之后,Tomcat服务器马上调用 AServlet对象的 service 方法。
    • 用户继续发送第二次请求,控制台输出了以下内容:

      1
      AServlet's service method execute!

      根据以上输出结果得知,用户在发送第二次,或者第三次,或者第四次请求的时候,Servlet对象并没有新建,还是使用之前创建好的 Servlet对象,直接调用该 Servlet对象的 service 方法,这说明:

      • 第一:Servlet对象是单例的(单实例的。但是要注意:Servlet对象是单实例的,但是 Servlet类并不符合单例模式。我们称之为假单例。之所以单例是因为 Servlet对象的创建我们 Javaweb程序员管不着,这个对象的创建只能是 Tomcat 来说了算,Tomcat只创建了一个,所以导致了单例,但是属于假单例。对于真单例模式,构造方法是私有化的。)
      • 第二:无参数构造方法、init 方法只在用户第一次发送请求的时候执行。也就是说无参数构造方法只执行一次。init 方法也只被 Tomcat 服务器调用一次。
      • 第三:只要用户发送一次请求:service 方法必然会被 Tomcat 服务器调用一次。发送 100 次请求,service 方法会被调用 100 次。
    • 关闭服务器的时候,控制台输出了以下内容:

      1
      AServlet's destroy method execute!

      通过以上输出内容,可以得出以下结论:

      • Servlet 的 destroy 方法只被 Tomcat 服务器调用一次。

      • destroy 方法是在什么时候被调用的?

        • 在服务器关闭的时候。
        • 因为服务器关闭的时候要销毁 AServlet对象的内存。
        • 服务器在销毁AServlet对象内存之前,Tomcat服务器会自动调用 AServlet对象的 destroy 方法。

        destroy 方法执行的时候 AServlet对象还在,没有被销毁,destroy 方法执行结束之后,AServlet对象的内存才会被 Tomcat 释放。

思考

  • 关于 Servlet 类中方法的调用次数?

    • 构造方法只执行一次。
    • init 方法只执行一次。
    • service 方法:用户发送一次请求则执行一次,发送 N 次请求则执行 N 次。
    • destroy 方法只执行一次。
  • 当我们在 Servlet 类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现什么问题?

    • 报错了:500 错误。

      注意:500 是一个 HTTP 协议的错误状态码。500 一般情况下是因为服务器端的 Java 程序出现了异常(服务器端的错误都是 500 错误:服务器内部错误)。

    • 如果没有无参数的构造方法,会导致出现 500 错误,无法实例化 Servlet 对象。

      所以,一定要注意:在 Servlet 开发当中,不建议程序员手动定义构造方法,因为定义不当,一不小心就会导致无法实例化 Servlet 对象。

  • Servlet 的无参数构造方法是在对象第一次创建的时候执行,并且只执行一次;init 方法也是在对象第一次创建的时候执行,并且只执行一次。那么这个无参数构造方法可以代替掉 init 方法吗?

    • 不能。
    • Servlet 规范中有要求,作为 Javaweb 程序员,编写 Servlet 类的时候,不建议手动编写构造方法,因为编写构造方法,很容易让无参数构造方法消失,这个操作可能会导致 Servlet 对象无法实例化。所以 init 方法是有存在的必要的。
  • initservicedestroy 方法中使用最多的是哪个方法?

    • 使用最多就是 service 方法service 方法是一定要实现的,因为 service 方法是处理用户请求的核心方法。

    • 什么时候使用 init 方法呢?

      • init 方法很少用。

        通常在 init 方法当中做初始化操作,并且这个初始化操作只需要执行一次。例如:初始化数据库连接池,初始化线程池 ……

    • 什么时候使用 destroy 方法呢?

      • destroy 方法也很少用。

        通常在 destroy 方法当中,进行资源的关闭。对象马上要被销毁了,还有什么没有关闭的,抓紧时间关闭资源,还有什么资源没保存的,抓紧时间保存一下。

1.10 适配器模式Adapter改造Servlet

  • 编写一个 Servlet 类直接实现 Servlet 接口有什么缺点?

    • 我们只需要 service 方法,其他方法大部分情况下是不需要使用的。代码很丑陋。

    为了解决这个问题,可以使用适配器设计模式 Adapter

    • 编写一个 GenericServlet 类,这个类是一个抽象类,其中有一个抽象方法 service

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      package com.f.javaweb.adapter;

      import jakarta.servlet.*;

      import java.io.IOException;

      /**
      * @author fzy
      * @date 2023/11/11 14:36
      */
      /**
      * 编写一个标准通用的Servlet,起名:GenericServlet
      * 以后所有的Servlet类都不要直接实现Servlet接口了。
      * 以后所有的Servlet类都要继承GenericServlet类。
      * GenericServlet 就是一个适配器。
      */
      public abstract class GenericServlet implements Servlet {
      @Override
      public void init(ServletConfig servletConfig) throws ServletException {

      }

      @Override
      public ServletConfig getServletConfig() {
      return null;
      }

      /**
      * 抽象方法,这个方法最常用。所以要求子类必须实现service方法。
      * @param servletRequest
      * @param servletResponse
      * @throws ServletException
      * @throws IOException
      */
      @Override
      public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
      throws ServletException, IOException;

      @Override
      public String getServletInfo() {
      return null;
      }

      @Override
      public void destroy() {

      }
      }
      • GenericServlet 实现 Servlet 接口。

      • GenericServlet 是一个适配器。

      • 以后编写的所有 Servlet 类继承 GenericServlet,重写 service 方法即可。不需要每次都要实现 Servlet 接口的所有抽象方法。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        package com.f.javaweb.adapter;

        import jakarta.servlet.ServletException;
        import jakarta.servlet.ServletRequest;
        import jakarta.servlet.ServletResponse;

        import java.io.IOException;

        /**
        * @author fzy
        * @date 2023/11/11 14:44
        */
        public class StudentService extends GenericServlet {
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse)
        throws ServletException, IOException {
        System.out.println("Student service method execute!");
        }
        }

1.10.1 改造GenericServlet

  • 思考:GenericServlet 类是否需要改造一下?怎么改造?更利于子类程序的编写?

    • 第一个问题:创建了一个继承 GenericServlet 的类后,在使用该类时,init 方法还会执行吗?
      • 还会执行,会执行 GenericServlet 类中的 init 方法。子类如果没有重写父类的方法,就会默认执行父类的方法。
    • 第二个问题:init 方法是谁调用的?
      • Tomcat服务器调用的。
    • 第三个问题:init 方法中的 ServletConfig 对象是谁创建的?是谁传过来的?
      • 都是 Tomcat 干的。Tomcat 服务器先创建了 ServletConfig 对象,然后调用 init 方法,将 ServletConfig 对象传给了 init 方法。
  • 目前,init 方法中的 ServletConfig 对象是 Tomcat 创建好的,这个 ServletConfig 对象在 init 方法的参数上,属于局部变量public void init(ServletConfig servletConfig) throws ServletException

    但是,ServletConfig 对象以后肯定要在 service 方法中使用,怎么才能保证 ServletConfig 对象在 service 方法中使用呢?

    • 可以将 GenericServlet 按如下几条进行改造:
      1. GenericServlet 中创建一个私有的 ServletConfig 成员变量, 在 init 方法中给其赋值,当继承 GenericServlet 的子类对象需要使用 ServletConfig 时,通过调用 getServletConfig 方法即可。
      2. 为了防止子类重写已经写好的 init 方法(“在 init 方法中给其赋值”),所以将该 init 方法声明为 final,并提供一个单独的 init 方法供子类重写,在该方法中调用允许子类重写的 init 方法。

    改造后的 GenericServlet 的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    package com.f.javaweb.adapter;

    import jakarta.servlet.*;

    import java.io.IOException;

    /**
    * @author fzy
    * @date 2023/11/11 14:36
    */

    /**
    * 编写一个标准通用的Servlet,起名:GenericServlet
    * 以后所有的Servlet类都不要直接实现Servlet接口了。
    * 以后所有的Servlet类都要继承GenericServlet类。
    * GenericServlet 就是一个适配器。
    */
    public abstract class GenericServlet implements Servlet {
    //创建一个私有的 `ServletConfig` 成员变量, 在 `init` 方法中给其赋值
    private ServletConfig servletConfig;

    //防止子类重写已经写好的 `init` 方法
    @Override
    public final void init(ServletConfig servletConfig) throws ServletException {
    this.servletConfig = servletConfig;
    this.init(); //在该方法中调用允许子类重写的 `init` 方法
    }

    //提供一个单独的 `init` 方法供子类重写
    public void init(){

    }

    @Override
    public ServletConfig getServletConfig() {
    return servletConfig;
    }

    /**
    * 抽象方法,这个方法最常用。所以要求子类必须实现service方法。
    *
    * @param servletRequest
    * @param servletResponse
    * @throws ServletException
    * @throws IOException
    */
    @Override
    public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
    throws ServletException, IOException;

    @Override
    public String getServletInfo() {
    return null;
    }

    @Override
    public void destroy() {

    }
    }

    实现 GenericServlet 的子类的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.f.javaweb.adapter;

    import jakarta.servlet.ServletConfig;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;

    import java.io.IOException;

    /**
    * @author fzy
    * @date 2023/11/11 14:44
    */
    public class StudentService extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
    throws ServletException, IOException {
    //调用父类的 getServletConfig 方法得到 ServletConfig 对象
    ServletConfig servletConfig = this.getServletConfig();
    System.out.println("ServletConfig:" + servletConfig);
    System.out.println("Student service method execute!");
    }

    @Override
    public void init() {
    System.out.println("Student service init method execute!");
    }
    }

1.10.2 jakarta.servlet.GenericServlet

  • 1.10.1 节所述是为了说明 GenericServlet 的源码,其实在 jakarta.servlet.GenericServlet 中已经实现了上述功能,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //

    package jakarta.servlet;

    import java.io.IOException;
    import java.io.Serializable;
    import java.util.Enumeration;

    public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
    return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
    return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
    return this.config;
    }

    public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
    return "";
    }

    public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String message) {
    ServletContext var10000 = this.getServletContext();
    String var10001 = this.getServletName();
    var10000.log(var10001 + ": " + message);
    }

    public void log(String message, Throwable t) {
    this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
    return this.config.getServletName();
    }
    }
    • transient 表示修饰的属性不可序列化。

1.11 ServletConfig接口详解

  • ServletConfig

    1. ServletConfig 是什么?

      • jakarta.servlet.ServletConfig,是 Servlet 规范中的一员。

      • ServletConfig 是一个接口( jakarta.servlet.ServletConfig 是一个接口)。

    2. 谁去实现了这个接口呢? -> WEB服务器实现了

      • 打印输出 ServletConfig 对象时,输出结果为 org.apache.catalina.core.StandardWrapperFacade implements ServletConfig

      • 结论:Tomcat服务器实现了 ServletConfig 接口。

      • 思考:如果把 Tomcat服务器换成 jetty服务器,输出 ServletConfig 对象的时候,还是这个结果吗?

        不一定一样,包名类名可能和 Tomcat 不一样,但是他们都实现了 ServletConfig 这个规范。

    3. 一个 Servlet 对象中有一个 ServletConfig 对象(ServletServletConfig 对象是一对一的)

      100 个 Servlet 对象,就应该有 100 个 ServletConfig 对象。

    4. ServletConfig 对象是谁创建的?在什么时候创建的?

      • Tomcat 服务器(WEB服务器)创建了 ServletConfig 对象。
      • 在创建 Servlet 对象的时候,同时创建了 ServletConfig 对象。
    5. ServletConfig 接口到底是干啥的?有什么用呢?

      • ServletConfig 对象被翻译为:Servlet 对象的 “配置信息对象”。
      • 一个 Servlet 对象就有一个配置信息对象。两个 Servlet 对象就有两个配置信息对象。
    6. ServletConfig 对象中到底包装了什么信息呢?

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <servlet>
      <servlet-name>configTest</servlet-name>
      <servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
      <!--这里是可以配置一个Servlet对象的初始化信息的。-->
      <init-param>
      <param-name>driver</param-name>
      <param-value>com.mysql.cj.jdbc.Driver</param-value>
      </init-param>
      <init-param>
      <param-name>url</param-name>
      <param-value>jdbc:mysql://localhost:3306/bjpowernode</param-value>
      </init-param>
      <init-param>
      <param-name>user</param-name>
      <param-value>root</param-value>
      </init-param>
      <init-param>
      <param-name>password</param-name>
      <param-value>root1234</param-value>
      </init-param>
      </servlet>
      • ServletConfig 对象中包装的信息是:web.xml 文件中 <servlet></servlet> 标签的配置信息。

        其中 <init-param></init-param> 是初始化参数。

      • Tomcat 解析 web.xml 文件,将 web.xml 文件中 <servlet></servlet> 标签中的配置信息自动包装到 ServletConfig 对象中。

    7. ServletConfig 接口中有哪些方法?-> 共有 4 个方法:

      • public String getInitParameter(String name);

      • public Enumeration<String> getInitParameterNames();

      通过 ServletConfig 对象的上面两个方法,可以获取到 web.xml 文件中 Servlet 对象的初始化参数配置 <init-param></init-param> 的信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      package com.f.javaweb.servlet;

      import jakarta.servlet.*;

      import java.io.IOException;
      import java.io.PrintWriter;
      import java.util.Enumeration;

      /**
      * @author fzy
      * @date 2023/11/22 14:43
      */
      public class TestServletConfig extends GenericServlet {
      @Override
      public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();

      // 获取ServletConfig对象
      ServletConfig config = this.getServletConfig();
      // 输出该对象
      //ServletConfig: org.apache.catalina.core.StandardWrapperFacade@607693fa
      out.print("ServletConfig: " + config);
      out.print("<br/>");

      // 获取<servlet-name></servlet-name>
      String servletName = config.getServletName();
      out.print("<servlet-name>" + servletName + "</servlet-name>");
      out.print("<br/>");

      // java.util.Enumeration<java.lang.String> getInitParameterNames() 获取所有的初始化参数的name
      Enumeration<String> initParameterNames = config.getInitParameterNames();
      // 遍历集合
      while (initParameterNames.hasMoreElements()) { // 是否有更多元素
      String parameterName = initParameterNames.nextElement(); // 取元素
      String parameterVal = config.getInitParameter(parameterName); // 通过name获取value
      out.print(parameterName + "=" + parameterVal);
      out.print("<br/>");
      }
      }
      }
      • public ServletContext getServletContext();

      • public String getServletName();

  • 注意:其实 ServletConfig 接口的四个方法不需要专门通过 ServletConfig 对象去调用,在自己的 Servlet 对象中可以通过 this 去调用(因为自己的 Servlet 继承了 GenericServlet,而 GenericServlet 实现了 ServletConfig 接口。可以看 1.10.2GenericServlet 的源码,里面已经实现了这四个方法)。所以上面的代码可以改为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.f.javaweb.servlet;

    import jakarta.servlet.*;

    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Enumeration;

    /**
    * @author fzy
    * @date 2023/11/22 14:43
    */
    public class TestServletConfig extends GenericServlet {
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();

    // 获取<servlet-name></servlet-name>
    String servletName = this.getServletName();
    out.print("<servlet-name>" + servletName + "</servlet-name>");
    out.print("<br/>");

    // java.util.Enumeration<java.lang.String> getInitParameterNames() 获取所有的初始化参数的name
    Enumeration<String> initParameterNames = this.getInitParameterNames();
    // 遍历集合
    while (initParameterNames.hasMoreElements()) { // 是否有更多元素
    String parameterName = initParameterNames.nextElement(); // 取元素
    String parameterVal = this.getInitParameter(parameterName); // 通过name获取value
    out.print(parameterName + "=" + parameterVal);
    out.print("<br/>");
    }
    }
    }

    即,以上四个方法在 Servlet 类当中,都可以使用 this 去调用。因为 GenericServlet 实现了 ServletConfig 接口。

1.12 ★ServletContext接口详解

  • ServletContext

    1. ServletContext 是什么?

      • jakarta.servlet.ServletContext,是 Servlet 规范中的一员。

      • ServletContext 是一个接口( jakarta.servlet.ServletContext 是一个接口)。

    2. 谁去实现了这个接口呢? -> WEB服务器实现了

      • public class org.apache.catalina.core.ApplicationContextFacade implements ServletContext {}
    3. 怎么获取 ServletContext 对象呢?

      • 通过 ServletConfig 对象获取 ServletContext 对象。

      • 通过 this 也可以获取 ServletContext 对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      package com.f.javaweb.servlet;

      import jakarta.servlet.*;

      import java.io.IOException;
      import java.io.PrintWriter;

      /**
      * @author fzy
      * @date 2023/11/22 16:50
      */
      public class TestServletContext extends GenericServlet {
      @Override
      public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();

      // 1.通过 `ServletConfig` 对象获取 `ServletContext` 对象。
      ServletConfig config = this.getServletConfig();
      ServletContext application1 = config.getServletContext();
      out.print("ServletContext: " + application1);
      out.print("<br/>");

      // 2.通过 `this` 也可以获取 `ServletContext` 对象。
      ServletContext application2 = this.getServletContext();
      out.print("ServletContext: " + application2);
      out.print("<br/>");
      }
      }
    4. ServletContext 对象是谁创建的?在什么时候创建的?

      • Tomcat 服务器(WEB服务器)创建了 ServletContext 对象。

      • 在 WEB服务器启动的时候创建了 ServletContext 对象。在 WEB服务器关闭的时候销毁 ServletContext 对象。

    5. 对于一个 webapp 来说,ServletContext 对象只有一个

      一个 <web-app></web-app> 标签对应一个 ServletContext 对象。

    6. ServletContext 接口到底是干啥的?有什么用呢?

      • ServletContext 对象被翻译为:Servlet 对象的 “环境对象”(Servlet 对象的“上下文对象”)。
      • ServletContext 对象对应的其实就是整个 web.xml 文件

      举个例子:50个学生,每个学生都是一个 Servlet 对象,这 50 个学生都在同一个教室当中,那么这个教室就相当于 ServletContext 对象。

      • 放在 ServletContext 对象中的数据,一定是所有 Servlet 对象共享的。只要在同一个webapp当中,只要在同一个应用当中,所有的 Servlet 对象都是共享同一个 ServletContext 对象的。比如:一个教室中的空调是所有学生共享的,一个教室中的语文老师是所有学生共享的。
      • Tomcat 是一个容器,一个容器当中可以放多个 webapp,一个 webapp 对应一个 ServletContext 对象
    7. ServletContext 接口中有哪些常用的方法?

      • public String getInitParameter(String name);

      • public Enumeration<String> getInitParameterNames();

      通过 ServletContext 对象的上面两个方法,可以获取到 web.xml 文件中的初始化参数配置 <context-param></context-param> 的信息(注意和 ServletConfig 中的方法的区别)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
      version="6.0">

      <!--上下文的初始化参数,以下的这些配置信息,可以通过ServletContext对象来获取-->
      <context-param>
      <param-name>pageSize</param-name>
      <param-value>30</param-value>
      </context-param>
      <context-param>
      <param-name>startIndex</param-name>
      <param-value>0</param-value>
      </context-param>

      <servlet>
      <servlet-name>testServletContext</servlet-name>
      <servlet-class>com.f.javaweb.servlet.TestServletContext</servlet-class>
      </servlet>
      <servlet-mapping>
      <servlet-name>testServletContext</servlet-name>
      <url-pattern>/test</url-pattern>
      </servlet-mapping>

      </web-app>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      package com.f.javaweb.servlet;

      import jakarta.servlet.*;

      import java.io.IOException;
      import java.io.PrintWriter;
      import java.util.Enumeration;

      /**
      * @author fzy
      * @date 2023/11/22 21:04
      */
      public class TestServletContext2 extends GenericServlet {
      @Override
      public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();

      ServletContext application = this.getServletContext();
      // 获取上下文的初始化参数
      Enumeration<String> initParameterNames = application.getInitParameterNames();
      /**
      * 浏览器显示:
      * startIndex = 0
      * pageSize = 30
      */
      while (initParameterNames.hasMoreElements()) {
      String paramName = initParameterNames.nextElement();
      String paramVal = application.getInitParameter(paramName);
      out.print(paramName + " = " + paramVal + "<br/>");
      }
      }
      }
      • public String getContextPath(); 获取应用的根路径。
      • public String getRealPath(String path); 获取文件的绝对路径(真实路径)。
      • public void log(String message); 记录日志信息。
      • public void log(String message, Throwable t); 除了记录日志信息外,还记录异常信息,控制台不会发送异常,只是记录异常信息。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      package com.f.javaweb.servlet;

      import jakarta.servlet.*;

      import java.io.IOException;
      import java.io.PrintWriter;

      /**
      * @author fzy
      * @date 2023/11/22 21:25
      */
      public class TestServletContext3 extends GenericServlet {
      @Override
      public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      ServletContext application = this.getServletContext();
      /**
      * 1.`public String getContextPath();` 获取应用的根路径。
      * 因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径。
      * 在java源码当中,不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候,会起一个什么名字。
      */
      String contextPath = application.getContextPath();
      out.print(contextPath + "<br/>"); // "/context"
      /**
      * 2.`public String getRealPath(String path);` 获取文件的绝对路径(真实路径).
      * 传入的参数中,加了一个“/”,这个“/”代表的是web的根
      * String realPath = application.getRealPath("/index.html"); // 可以
      * 你不加“/”,默认也是从根下开始找。
      * String realPath = application.getRealPath("index.html"); // 不加“/”也可以
      * out.print(realPath + "<br/>");
      */
      String realPath = application.getRealPath("/index.html");
      out.print(realPath + "<br/>"); // C:\Users\Running Noob\Code Project\javaweb\out\artifacts\servlet04_war_exploded\index.html
      /**
      * 3.`public void log(String message);` 记录日志信息。
      * 这些日志信息记录到哪里了? -> %CATALINA_HOME%logs 目录下
      * // 注意:IDEA工具的Tomcat服务器是根据下载的Tomcat服务器生成的副本
      * // Tomcat服务器的logs目录下都有哪些日志文件?
      * //catalina.2021-11-05.log 服务器端的java程序运行的控制台信息。
      * //localhost.2021-11-05.log ServletContext对象的log方法记录的日志信息存储到这个文件中。
      * //localhost_access_log.2021-11-05.txt 访问日志。
      */
      application.log("hello,world!");
      /**
      * 4.`public void log(String message, Throwable t);`
      * 除了记录日志信息外,还记录异常信息,控制台不会发送异常,只是记录异常信息。
      */
      int age = 1;
      if (age < 18) {
      application.log("年龄未满18岁", new RuntimeException("age < 18"));
      }
      }
      }
      • public void setAttribute(String name, Object value); 向 ServletContext 应用域中存数据。
      • public Object getAttribute(String name); 从 ServletContext 应用域中取数据。
      • public void removeAttribute(String name); 删除 ServletContext 应用域中的数据。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      package com.f.javaweb.servlet;

      import jakarta.servlet.*;

      import java.io.IOException;
      import java.io.PrintWriter;

      /**
      * @author fzy
      * @date 2023/11/23 10:50
      */
      public class TestServletContext4 extends GenericServlet {
      @Override
      public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      ServletContext application = this.getServletContext();
      /**
      * 1.`public void setAttribute(String name, Object value);` 向 ServletContext 应用域中存数据。
      */
      application.setAttribute("name", "jack");
      /**
      * 2.`public Object getAttribute(String name);` 从 ServletContext 应用域中取数据。
      */
      Object name = application.getAttribute("name");
      out.print(name);
      /**
      * 3.`public void removeAttribute(String name);` 删除 ServletContext 应用域中的数据。
      */
      application.removeAttribute("name");
      }
      }

      注意:上面存取的数据,是所有 Servlet 对象共享的,即,可以在 AServlet 对象中 setAttribute,然后在 BServlet 对象中 getAttribute

    8. ServletContext 对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)。

      • 如果所有用户共享一份数据,并且这个数据很少被修改,并且这个数据量很少,那么就可以将这些数据放到 ServletContext 这个应用域中。

        • 为什么是所有用户共享的数据?

          -> 不是共享的没有意义,因为 ServletContext 这个对象只有一个,只有共享的数据放进去才有意义。

        • 为什么这些共享数据很少被修改,或者说几乎不修改?

          -> 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在 ServletContext 对象中的数据一般都是只读的。

        • 为什么数据量要小?

          -> 因为数据量比较大的话,太占用堆内存,并且 ServletContext 对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁,大数据量会影响服务器的性能,因此占用内存较小的数据量才可以考虑放进去。

        数据量小、所有用户共享、又不修改,这样的数据放到 ServletContext 这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次再用的时候,不需要从数据库中再次获取,大大提升了执行效率。

思考

  • 什么时候使用 <context-param></context-param> 来配置信息,什么时候使用 <init-param></init-param> 来配置信息?

    • <context-param></context-param> 中的信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到该标签当中

      • 通过 ServletContext 对象的下面两个方法来获取 <context-param></context-param> 中的信息:

        • public String getInitParameter(String name);

        • public Enumeration<String> getInitParameterNames();

    • <init-param></init-param> 中的信息仅适用于当前所在的 Servlet 对象,如果你的配置信息只是想给某一个 Servlet 对象使用,那么配置到 servlet 标签当中即可。

    • 通过 this 或者 ServletConfig 对象的下面两个方法来获取 <init-param></init-param> 中的信息:

      • public String getInitParameter(String name);

      • public Enumeration<String> getInitParameterNames();

注意

  • 以后我们编写 Servlet 类的时候,实际上是不会去直接继承 GenericServlet 类的,因为我们是 B/S 结构的系统,这种系统是基于 HTTP 超文本传输协议的。

    在 Servlet 规范当中,提供了一个类叫做 HttpServlet,它是专门为 HTTP 协议准备的一个 Servlet 类,我们编写的 Servlet 类要继承 HttpServletHttpServlet 是 HTTP 协议专用的),使用 HttpServlet 处理 HTTP 协议更便捷,但是你需要知道它的继承结构:

    1
    2
    3
    4
    5
    jakarta.servlet.Servlet(接口)【爷爷】
    jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
    jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】

    我们以后编写的Servlet要继承HttpServlet类。

另:缓存机制

  • 大家到目前为止都接触过哪些缓存机制了?
    • 堆内存当中的字符串常量池
      • 如果要使用字符串 “abc”,会先在字符串常量池中查找,如果有,直接拿来用。如果没有则新建,然后再放入字符串常量池。
    • 堆内存当中的整数型常量池
      • [-128 ~ 127] 一共 256 个 Integer 类型的引用,放在整数型常量池中。没有超出这个范围的话,可以直接从常量池中取。
    • 连接池(Connection Cache)
      • 这里所说的连接池中的连接是 java 语言连接数据库的连接对象:java.sql.Connection 对象。
      • JVM 是一个进程,MySQL 数据库是一个进程,进程和进程之间建立连接,打开通道是很费劲的,是很耗费资源的。怎么办?可以提前先创建好 N 个 Connection 连接对象,将连接对象放到一个集合当中,我们把这个放有 Connection 对象的集合称为连接池。每一次用户连接的时候不需要再新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率。
      • 连接池
        • 最小连接数
        • 最大连接数
        • 连接池可以提高用户的访问效率。当然也可以保证数据库的安全性。
    • 线程池
      • Tomcat 服务器本身就是支持多线程的。
      • Tomcat 服务器是在用户发送一次请求,就新建一个Thread线程对象吗?
        • 当然不是,实际上是在 Tomcat 服务器启动的时候,会先创建好 N 个线程 Thread 对象,然后将线程对象放到集合当中,称为线程池。用户发送请求过来之后,需要有一个对应的线程来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率比较高。
        • 所有的 WEB 服务器,或者应用服务器,都是支持多线程的,都有线程池机制。
    • redis
      • NoSQL数据库。非关系型数据库。缓存数据库。
    • ServletContext 应用域中存储数据,也等于是将数据存放到缓存 cache 当中了。
---------------The End---------------