《JSP+Servlet+Tomcat》原书读书笔记(第 4 章)

4、Servlet

在 Web 应用中,Servlet 是一项重要的技术。Servlet 是利用 Java 类编写的服务器端程序,与平台架构、协议无关。JSP 的实质是 Servlet,因为 JSP 在执行第一次后,会被编译成 Servlet 的类文(即 .class),当再重复调用执行 JSP 时,就直接执行第一次所产生的 Servlet,而不再重新把 JSP 编译成 Servelt,所以 Servlet 至关重要。

本章主要涉及的知识点有:

(1)Servlet 的基本概念和技术特点

(2)一个 Servlet 的生命周期

(3)如何编写和部署一个 Servlet 程序

(4)Servlet 与 JSP 之间的关联与区别

4.1、Servlet 是什么

本节首先介绍 Servlet 的基本概念,Servlet 是利用 Java 类编写的服务器端应用程序,顾名思义,它通常是在服务器端运行的程序,打开浏览器即可调用一个。它可以被看作位于客户端和服务器端的一个中间层,负责接收和请求客户端用户的响应。

Servlet 使用了很多 Web 服务器都支持的 API,可以调用和扩展 Java 中提供的大量程序设计接口、类、方法等功能。

Servlet 可以提供以下功能。

1、对客户端发送的数据进行读取和拦截

客户端在发送一个请求时,一般而言都会携带一些数据(例如 URL 中的参数、页面中的表单、Ajax 提交的参数等),当一个 Servlet 接收到这些请求时,JavaServlet 中的类通过所提供的方法就能得到这些参数(例如,方法 request.getParameterName(name) 用于获得名为 name 的参数值),也正因为这个原因,Servlet 可以对发送请求起到拦截作用,它在某些请求发出前先做一个预处理分析,从而判断客户端是否可以做某些请求(例如检查访问权限、设定程序的字集、检查用户角色等),当 Servlet 具有如上功能时,一般可以被称为拦截器。

2、读取客户端请求的隐含数据

客户端请求的数据可以分为隐含数据和显式数据:

(1)隐含数据一般不直接跟随于 URL 中,它存在于请求的来源、缓存数据(Cookie)、客户端类型中;

(2)显式数据显然是用户可以直观看到的,例如表单数据和 URL 参数。Servlet 不但可以处理显式数据,而且可以处理隐含数据,是一个“多面手”。

3、运行结果或者生成结果

当一个 web 应用程序对客户端发出的请求做出响应时,一般需要很多中间过程才能得到结果。Servlet 起到这个中间角色的作用,协调各组件、各部分完成相应的功能,根据不同的请求做出相应的响应并显示结果。

4、发送响应的数据

Servlet 在对客户端做出响应并经过处理得出结果后,会对客户端发送响应的数据,以便让客户端获取请求的结果数据。在 Web 应用程序中,Servlet 的这个功能相当突出,无论现有的技术多么先进,都是基于这个功能出发的。综上所述,Servlet 的程序运行顺序大致如图 4.1 所示。

图 4.1 Servlet 的运行顺序

4.2、Servlet 的技术特点

本节介绍了 Servlet 的概念和功能,从而让读者村 Servlet 有一个整体的印象和认识,并了解其运行的顺序等,本节将介绍 Servlet 的一些技术特点,让读者了解 Servlet 的优点。

Servlet 在开发中带来的优点就是能及时响应和处理 Web 端的请求,使得一个不懂网页的 Java 开发人员也能编写出 Web 应用程序,只是在开发/修改一个 Web 程序时比较麻烦,因为代码的可读性比较差,也比较难以维护,但是它却具有以下特点。

1、高效率

Servlet 本身就是一个 Java 类,在运行的时候位于同一个 Java 虚拟机中,可以快速地响应客户端的请求并生成结果。在 Web 服务器中处理一个请求使用的都是线程而非进程,也就是说在性能开销方面就小很多,无须大量的启动进程时间,在高并发量访问时,一个进程可以有多个线程,并发时线程在 CPU 中的开销代价要远小于进程的开销。

2、简单方便

开发一个 Web 程序,从开发顺序上来说比较简单:

(1)首先定义一个 Servlet 类,然后在系统(web.xml)中配置程序,继而发布程序,这样一个 Web 程序就完成了。

(2)在开发的过程中,系统提供了大量的实用工具和方法,可以处理复杂的 HTML 表单数据、处理 cookie、跟踪网页会话等。

4.3、Servlet 的生命周期

每个生命都有特定的生命周期,例如人的生命周期为“婴儿一少年一青年→壮年一老人”开发项目的生命周期为“立项→开发一运维一消亡”。同样,Servlet 也不例外,它有 3 个阶段,分别是:初始化(包括装载和初始化)、运行、消亡。

1、初始化阶段

初始化阶段可以分为装载和初始化两个子阶段。装载就是由 Servlet 容器装载一个 Servlet 类,把它装载到 Java 内存中,Servlet 容器可创建一个 Servlet 对象并与 web.xml 中的配置对应起来;初始化子阶段是调用 Servlet 中的 init() 方法,在整个 Servlet 生命周期中 init() 方法只被调用一次。

2、运行阶段

在这个阶段中会实际响应客户端的请求,当有请求时 Servlet 会创建 HttpServletRequest 和HttpServletResponse 对象,然后调用 service(HttpServletRequest request, HttpServletResponse response)方法。serivce() 方法通过 request 对象获得请求对象的信息并加以处理,再由 response 对象对客户端做出响应。

3、消亡阶段

当 Servlet 应用被终止后,Servlet 容器会调用 destroy() 方法对 Servlet 对象进行销毁。在消亡的过程中,Servlet 容器将释放被它所占的资源,例如关闭流、关闭数据库连接等。同样在整个 Servlet 生命周期中 destroy() 方法也只被调用一次。

4、演示生命周期

(1)HelloServlet

下面通过编写一个 Servlet 类来说明它的生命周期,完整的代码如下:

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

import static java.lang.System.out;

public class HelloServlet extends HttpServlet {

    private static final long serialVersionUID = 1L; // 序列化

    // 1. 初始化阶段
    public void init(){
        out.println("控制器初始化,整个生命周期,只执行一次!");
    }

    // 2. 运行阶段

    /**
     * 当客户端访问了绑定该 Servlet 的路径时,会执行 service 方法
     * @param request 客户端请求对象
     * @param response 服务端响应对象
     * @throws IOException 异常
     */
    public void service(ServletRequest request, ServletResponse response) throws IOException {
        out.println("运行阶段:调用了:service 方法");
        response.setContentType("text/html;charset=gbk");
        PrintWriter writer = response.getWriter();
        writer.println("收到了 service 请求");
    }

    protected void service(HttpServletRequest request, HttpServletResponse response){
        out.println("调用了受保护的 service 方法");
    }

    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException {
        out.println("调用 doGet 方法");
        // 设置响应的页面类别和页面编码
        response.setContentType("text/html;charset=gbk");
        PrintWriter writer = response.getWriter();
        writer.println("收到 HelloServlet doGet 请求");
    }

    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
        out.println("调用 doPost 方法");
        // 设置响应的页面类别和页面编码
        response.setContentType("test/html;charset=gbk");
        PrintWriter writer = response.getWriter();
        out.println("收到 HelloServlet doPost 请求");
    }

    public void destroy(){
        out.println("调用 destroy 方法");
    }
}

可以看出,上述实例中的各个方法都只是执行打印功能,它们只是为了说明一个 Servlet 的生命周期执行过程。

(2)web.xml

完成上述 Servlet 编译后,还需要配置一下 web.xml,具体配置如下:

    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.shw.jspservlettomcat.ch4.HelloServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/HelloServlet</url-pattern>
    </servlet-mapping>

注意:配置 servlet-name 时应区分字母的大小写。

除了在 web.xml 中配置 Servlet 外,在 Serylet3.0 及以后的版本中还可以通过直接注入的方式进行配置,代码如下:

@WebServlet{
    urlPatterns = {"/HelloServlet"},
    name = "helloServlet"
}
public class HelloServlet extends HttpServlet {

    private static final long serialVersionUID = 1L; // 序列化

    // 1. 初始化阶段
    public void init(){
        out.println("控制器初始化,整个生命周期,只执行一次!");
    }

    // 2. 运行阶段

    /**
     * 当客户端访问了绑定该 Servlet 的路径时,会执行 service 方法
     * @param request 客户端请求对象
     * @param response 服务端响应对象
     * @throws IOException 异常
     */
    public void service(ServletRequest request, ServletResponse response) throws IOException {
        out.println("运行阶段:调用了:service 方法");
        response.setContentType("text/html;charset=gbk");
        PrintWriter writer = response.getWriter();
        writer.println("收到了 service 请求");
    }

    protected void service(HttpServletRequest request, HttpServletResponse response){
        out.println("调用了受保护的 service 方法");
    }

    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException {
        out.println("调用 doGet 方法");
        // 设置响应的页面类别和页面编码
        response.setContentType("text/html;charset=gbk");
        PrintWriter writer = response.getWriter();
        writer.println("收到 HelloServlet doGet 请求");
    }

    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
        out.println("调用 doPost 方法");
        // 设置响应的页面类别和页面编码
        response.setContentType("test/html;charset=gbk");
        PrintWriter writer = response.getWriter();
        out.println("收到 HelloServlet doPost 请求");
    }

    public void destroy(){
        out.println("调用 destroy 方法");
    }
}

在上述代码中,第 12~15 行就是利用注入声明的方式表示这是一个 Servlet 类,@WebServlet 中的参数如表 4.1 所示。从上述配置可以看出,这种方法比较简单,也是现在主流的开发形式,在随后的章节中还会继续介绍和使用这种方法。如果利用注入的方式进行了配置,那么 web.xml 就不用配置 Servlet。

表 4.1 @WebServlet 中的主要属性列表

name(String name)

描述指定 Servlet 的 name 属性,等价于<servlet-name>。如果没有指定,则该 Servlet 的取值为类的全名

urlPatterns(Stringll urls)

指定 Servlet 的 URL 匹配模式,等价于 <url-pattem>标签

配置完 web.xml 之后,可以通过以下步骤査看 Servlet 的生命周期执行过程。

(1)启动 Tomcat,将项目工程放在 webapps 文件夹下。

(2)在浏览器地址栏中输入与该 Servlet 配置相对应的 URL,在浏览器中可以看到“收到 service 请求”内容,在控制台中会输出:

控制器初始化,整个生命周期,只执行一次!
运行阶段:调用了:service 方法

访问地址:http://localhost:8080/jsp_servlet_tomcat_war/HelloServlet

(3)再在浏览器中输入 URL,在浏览器中看到“收到 service 请求”内容,在控制台中会输出:

运行阶段:调用了:service 方法

通过上述过程,验证了 Servlet 的生命周期过程。图 4.2 进一步说明了生命周期的不同阶段。

图 4.2 Servlet 的生命周期

从程序的运行结果还可以知道,当重写了 service() 方法之后,doPost() 方法和 doGet() 方法是不会被处理的,由 service() 来管理转向对应的方法。

5、工作原理

1、Servlet 生命周期管理
  • 加载阶段:Web 容器启动时读取 web.xml,发现 Servlet 配置
  • 初始化:容器创建 HelloServlet 实例,调用 init() 方法
  • 服务阶段:当匹配的 URL 请求到来时,调用 service() 方法
  • 销毁阶段:容器关闭时调用 destroy() 方法
2、URL 映射机制
客户端请求: http://localhost:8080/your-app/HelloServlet
↓
容器解析 URL 路径: /your-app/HelloServlet
↓
匹配 <url-pattern>/HelloServlet</url-pattern>
↓
找到对应的 <servlet-name>helloServlet</servlet-name>
↓
实例化/调用 com.shw.jspservlettomcat.ch4.HelloServlet
↓
执行相应的 doGet() 或 doPost() 方法
3、请求处理流程
public class HelloServlet extends HttpServlet {
    // 初始化方法 - 容器调用
    public void init() {
        // 初始化资源
    }

    // 处理 GET 请求
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 处理业务逻辑
        response.getWriter().write("Hello World!");
    }

    // 销毁方法 - 容器调用
    public void destroy() {
        // 清理资源
    }
}
4、核心作用
  1. 声明式配置:将 Servlet 类与 URL 路径解耦
  2. 集中管理:所有 Servlet 配置集中在 web.xml
  3. 动态加载:支持 Servlet 的懒加载和预加载配置
  4. 访问控制:通过 URL 模式控制哪些请求由哪个 Servlet 处理
5、@WebServlet 替代方案

虽然 web.xml 配置仍然有效,但现代开发更推荐使用注解:

@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
    // 类内容...
}

注解方式更简洁,但底层原理完全相同。

4.4、编写和部署 Servlet

上一节讲解了 Servlet 的生命周期过程,本节将介绍如何编写一个 Servlet 类和部署 Servlet 工程,编写和部署 Servlet 是开发一个 Web 工程的基础,也是读者必须掌握的内容。

4.4.1、编写 Servlet 类

本小节将讲述如何编写一个简单的 Servlet 类。本书编写 Servlet 的开发工具为 Intelli DEA,在 IntelliJ DEA的主界面中依次单击 File | New | Project 命令,然后在弹出的窗口左侧选择 Java,在右侧上方选择 JDK17.0.4,进入下一步,输入项目名称,选择项目路径,这样就完成了初始工程的创建。

由于 Java EE 已正式更名为 Jakarta EE,而目前的 DEA 只能支持 Java EE,因此创建完成后我们需要手动接入 Jakarta EE 的支持。首先在已创建的工程上右击鼠标,在弹出的快捷菜单中选择 Add FrameWork Suppont 命令,再在弹出的窗口中选择 Java EE | Web Application 选项,再单击 0K 按钮,如图 4.3 所示。然后打开项目工程目录,修改 web-app 的版本为 5.0,代码如下:

<?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_5_0.xsd"
         version="5.0">
</web-app>

整个工程的目录结构如图 4.4所示。

图 4.3 接入 Jakarta EE 的支持

图 4.4 Servlet 工程目录

为了使目录结构更加具有层次感,在包 com.shw.ch4,然后右击该文件,在弹出的快捷菜单中选择New | Java Class 命令,然后在 Name 文本框中输入自己定制的 Servlet 程序文件的名字。创建 Servlet 的对话框如图 4.5 所示。

图 4.5 创建一个 Servlet 类

单击 Finish 按钮后出现主编辑界面,在编写 Servlet 类时需要继承 HttpServlet 类,这样才可以开发具体的功能,假设想在页面中循环输出数字 0~10,那么程序代码如下:

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

@WebServlet(
        urlPatterns = "/FirstServlet",
        name = "firstServlet"
)
public class FirstServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public void init(){
        System.out.println("控制器开始初始化,执行方法:init()");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {

        System.out.println("控制器访问:doGet 方法");
        // 设置响应的页面类别和页面编码
        response.setContentType("text/html;charset=gbk");
        // 获取一个响应的输出流
        PrintWriter writer = response.getWriter();
        writer.println("<html>");
        writer.println("<head>");
        writer.println("<title>测试 0-10 的循环结果</title>");
        writer.println("<body>");
        writer.println("开始执行。。。。。。"+"<br>");
        int count = 0;
        for (int i=0;i<=10;i++){
            count += i;
        }
        writer.println("程序执行的结果是:" + count);
        writer.println("</body>");
        writer.println("</head>");
        writer.println("</html>");

        writer.flush();
        writer.close();
    }

    public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
        doGet(request,response);
        System.out.println("调用:doPost 方法");
    }

    public void destroy(){
        System.out.println("调用:destroy 方法");
    }
}

这里采用的是注解 @WebServlet 的方式注入 Servlet。现在分析一下创建 Servlet 的步骤:

(1)引入相应的包。

例如 jakarta.servlet 包或者 jakarta.servlet.http 包(这两个包的区别在于前者是与协议无关的,后者是与HTTP协议相关的)。

在平时开发的过程中,一般都是继承自 HttpServlet 类,因为它封装了很多基于 HTTP 的 Servlet 功能。当然要想自己开发一个协议还可以继承 GenericServlet 类。

FirstServlet 中有两个处理请求的方法:一个是 doGet() 方法响应 HTTP Get 请求;另一个是 doPost() 方法,响应 HTTP Post 请求。

这里之所以使用 jakarta 包。而不是原来的 javax 包,原因在于 Java EE 在 2017 年由甲骨文转让给 Eclipse 基金会,2018年3月 Eclipse 基金会正式宣布 JavaEE 更名为 Jakarta EE。

(2)创建一个扩展类,例如本例中的 FirstServlet 类。

(3)重构 doGet() 或者 doPost() 方法。例如,本例中重构了doGet() 方法,在该方法中完成处理请

求,并输出到 HTML 页面中。

(4)配置 web.xml 或添加 @WebServlet 注解。

4.4.2、署Servlet类

在 InteliJ IDEA 中部署一个 Web 工程有多种方法,本书只介绍两种方法:一种是原始的编译部署,另一种是直接利用在 ImtelJ IDEA 中配置的 Tomcat 服务器部署。

1、原始的编译部署

利用 Java 编译器将具体的 Java 类进行编译,例如本例中运行编译命令“javac FirstServlet.java”,在当前目录中生成 FirstServlet.class 文件,将 FirstServlet.class 文件复制到 WEB-INF 文件夹下的 classes 文件夹中,如图 4.6 所示。

再将 web.xml 存放在 WEB-INF 目录下,接着将整个 Servlet 工程文件夹存放在 webapps 目录下,如图 4.7 所示,再启动 Tomcat 服务器。

图 4.7 Servlet 放置目录

2、利用在 IntelliJ IDEA 中配置的 Tomcat 服务器部署

操作步骤如下:

(1)首先单击右上角的按钮打开项目结构窗口,在窗口中选择 Artifacts,单击加号,在弹出菜单中选择Web Application:Exploded,单击 OK 按钮完成配置,如图 4.8 所示。

图 4.8 在 IntelliJ IDEA 中部署 Servlet 工程

(2)在项目界面选择 Run | Edit Confgurations 打开配置界面,或者通过右上角的下拉框选择 Edit Configurations 打开,找到已配置的 Tomcat,如果不存在已配置的 Tomcat,请参照第1章有关 InteliJ IDEA 工具下载部分配置 Tomcat。单击对应的 Tomcat,选择 Server 选项卡配置 URL 和 HTTP port 端口号,单击 OK 按钮完成项目部署,如图 4.9 所示。配置完成后单击右上角的按钮启动项目即可。

(3)打开正浏览器,在地址栏中输入地址“http://localhost:8080/ch4_war_exploded/FirstServlet”然后按回车键,如果在页面中显示信息“开始执行……程序执行结果:55”,那么恭喜你,这个 Servlet 成功部署并运行。

4.5、Servlet与JSP 的比较

上一节介绍了如何编写和部署 Servlet 程序,从而使读者了解编写一个 Servlet 的基本步骤,并能编写出简单的 Servlet 类。本节将介绍 Servlet 与 JSP 之间的区别与联系,包括它们之间的内在联系是什么。其实从本质上讲,Servlet 与 JSP 是一样的,因为 JSP 页面最后在运行时会被转换成一个 servlet,但是从开发者的视角看,运用这两种技术还是有些区别的,这些区别也决定了在开发中应该如何选择使用它们。JSP 与 Servlet 的主要区别说明如下。

1、Servlet 是 Java 代码,JSP 是页面代码

编写 Servlet 就是编写 Java 代码,所以应用 java 中的规范去编写 Servlet 类就可以了,但是若想在客户端中响应结果,就必须在代码中加入大量的 HTML 代码。可想而知,当想要得到一个比较美观、复杂的界面时,HTML 代码量会相当多而且非常烦琐。JSP 以 HTML 代码为主,在页面中适当嵌入 Java 代码来处理业务上的逻辑。显然,JSP 会比 Servlet 较易编写并更直观。基于此的差异也是选择 Servlet 或者 JSP 技术的考量标准之一。如果业务中主要是以页面为主,就选扬 JSP 技术;反之,则选择 Servlet 技术(适合服务器端开发)。

2、Servlet 的运行速度快过 JSP

Servlet 本身就是一个 Java 类,编译的时候会直接被转换为类文件;JSP 需要先被编译为 Java 类, 而后再运行,所以 Servlet 的运行速度较快。

3、Servlet 需要手动编译,JSP 由服务器自动编译

Servlet 类被编译成为类文件后,需要手动复制到 Web 应用程序目录下。JSP 页面部署到 Web 应用则简单很多,只需要将 JSP 页面复制到指定的目录下即可,当它第一次被访问时,Web 服务器自动将 JSP 代码转换为Servlet(Java 代码)并自动编译。

4、编辑 HTML 工具不支持编辑 Servlet

当前有很多制作网页的工具,例如 Dreamweaver、Golive 等,利用这些工具可以快速地开发网页、编写出复杂的界面,而且具备可视化效果,极大地提高了网页开发的效率。

但对于大多数的网页工具而言,在 HTML 页面中加入 Java 代码是可以的,但是要在 Java 代码中加入 HTML 代码却不行,而且不能及时纠错。

因此,初学者利用不同的编辑器工具编写 JSP 和 Servlet 也是可以理解的。

了解了 Servlet 与 JSP 的差别之后,在编写 Web 应用程序时,就要根据当前的需要权衡是使用 JSP 还是 Servlet。开发者应尽量使 JSP 和 Servlet 都发挥出最大的作用,同时又能够便于日后的代码维护工作。

一般而言,Servlet 大多用于负责对客户端的请求进行处理和调用 Java Bean,由 Java Bean 负责提供可复用的数据以及访问数据等;而 JSP 页面主要负责页面的展示,将动态数据展现给客户,这就是开发者提出的简易 MVC 模式,这样分工大大减少了 JSP 页面中 Java 程序和 HTML 代码的耦合度,对维护工作具有重大的意义。

4.6 小结

本章主要介绍了 Servlet 的基础知识,包括它的基本概念、技术特点等。通过本章的学习,读者可掌握 Servlet 的基本编写方法和步骤、生命周期的意义,以及在生命周期各阶段调用的不同方法,最后需要了解并掌握Servlet 与 JSP 的相同点和不同点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shw2080

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值