一、引言

在现代的网络应用中,后端服务扮演着至关重要的角色,而在 Java 世界中,Servlet 则是构建这些后端服务的基石。Servlet,不仅仅是一个处理请求和响应的简单组件,更是 Java Web 开发的核心技术之一。它的出现,为动态 Web 内容的生成提供了高效、灵活的解决方案。无论你是初学者还是经验丰富的开发者,理解和掌握 Servlet 的原理和应用,都是提升 Web 开发能力的关键。

本文将带你深入探讨 Servlet 的方方面面,从基础概念到高级应用,从开发到优化,通过详实的案例和图示,帮助你全面掌握这项技术。无论你是在准备编写复杂的 Web 应用,还是在优化现有的后端服务,本文都将为你提供有价值的参考和指导。


二、什么是 Servlet ?

首先,Servlet 是一种工具,是一种连接服务端和客户端的工具,就好比说,你去饭店吃饭,你就是服务端,然后你得点菜吧?这时候要叫服务员,服务员给你写需要的菜名,此时服务员就是 Servlet,然后服务员去把单子给后厨,后厨就是服务器,做好之后,还是靠 Servlet(服务员) 给端出来给你。这么解释的话是不是就瞬间明朗了呢?


三、Servlet API

那说了这么多,Servlet 该如何使用呢?我们该如何通过 Servlet 做出一个属于自己的小网页呢?我用留言墙来举例,首先看一张图:详解 Servlet_java,大家先按照我这个格式创建出一个项目,然后 messageWall.html 里的是前端的代码,不要求掌握,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言墙</title>
    <!-- 引入 jquery -->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <style>
        /* * 通配符选择器, 是选中页面所有元素 */
        * {
            /* 消除浏览器的默认样式. */
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        .container {
            width: 600px;
            margin: 20px auto;
        }

        h1 {
            text-align: center;
        }

        p {
            text-align: center;
            color: #666;
            margin: 20px 0;
        }

        .row {
            /* 开启弹性布局 */
            display: flex;
            height: 40px;
            /* 水平方向居中 */
            justify-content: center;
            /* 垂直方向居中 */
            align-items: center;
        }

        .row span {
            width: 80px;
        }

        .row input {
            width: 200px;
            height: 30px;
        }

        .row button {
            width: 280px;
            height: 30px;
            color: white;
            background-color: orange;
            /* 去掉边框 */
            border: none;
            border-radius: 5px;
        }

        /* 点击的时候有个反馈 */
        .row button:active {
            background-color: grey;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>留言墙</h1>
        <p>输入内容后点击提交, 信息会显示到下方表格中</p>
        <div class="row">
            <span>谁: </span>
            <input type="text">
        </div>
        <div class="row">
            <span>对谁: </span>
            <input type="text">
        </div>
        <div class="row">
            <span>说: </span>
            <input type="text">
        </div>
        <div class="row">
            <button id="submit">提交</button>
        </div>
        <!-- <div class="row">
            xxx 对 xx 说 xxxx
        </div> -->
    </div>

    <script>
        // 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示. 
        // 点击的时候, 获取到三个输入框中的文本内容
        // 创建一个新的 div.row 把内容构造到这个 div 中即可. 
        let containerDiv = document.querySelector('.container');
        let inputs = document.querySelectorAll('input');
        let button = document.querySelector('#submit');
        button.onclick = function() {
            // 1. 获取到三个输入框的内容
            let from = inputs[0].value;
            let to = inputs[1].value;
            let msg = inputs[2].value;
            if (from == '' || to == '' || msg == '') {
                return;
            }
            // 2. 构造新 div
            let rowDiv = document.createElement('div');
            rowDiv.className = 'row message';
            rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
            containerDiv.appendChild(rowDiv);
            // 3. 清空之前的输入框内容
            for (let input of inputs) {
                input.value = '';
            }
            // 4. 通过 ajax 构造 post 请求, 把这个新的消息提交给服务器. 
            let body = {
                "from": from,
                "to": to,
                "message": msg
            };
            $.ajax({
                type: 'post',
                url: 'messageWallServlet',
                contentType: "application/json;charset=utf8",
                // 把 js 对象转换成 JSON 字符串抛给服务器
                data: JSON.stringify(body),
                success: function(body) {
                    // 这是响应成功返回之后, 要调用的回调. 
                    console.log("消息发送给服务器成功!");
                }
            });
        }

        // 在页面加载的时候, 希望能够从服务器获取到所有的消息, 并显示在网页中. 
        $.ajax({
            type: 'get',
            url: 'messageWallServlet',  // url 都是使用相对路径的写法. 相对路径意味着工作路径就是当前文件所在的路径. 
                             // 当前文件所在路径是 /message_wall/ , 因此此时构造的请求就是 /message_wall/message
            success: function(body) {
                // body 是收到的响应的正文部分. 如我们之前的约定, body 应该是 json 数组
                // 由于响应的 Content-Type 是 application/json, 此时收到的 body 会被 jquery 自动的把它从 字符串 
                // 转成 js 对象数组. 此处就不需要手动的进行 JSON.parse 了. 
                // 此处的 body 已经是一个 JSON.parse 之后得到的 js 对象数组了. 
                // 就需要遍历这个 body 数组, 取出每个元素, 再依据这样的元素构造出 html 标签, 并添加到页面上. 
                let container = document.querySelector('.container');
                for (let message of body) {
                    let rowDiv = document.createElement('div');
                    rowDiv.className = "row";
                    rowDiv.innerHTML = message.from + " 对 " + message.to + " 说: " + message.message;
                    container.appendChild(rowDiv);
                }
            }
        });
    </script>
</body>
</html>
  • 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.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.

web.xml 文件的代码如下:

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

pom.xml 需要导入的依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>messageWall</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.14.2</version>
        </dependency>

    </dependencies>

</project>
  • 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.

这样,所有的准备工作就做好了,我们只需安心些后端代码就行了。

3.1、获取全部留言

关于请求和响应就不多说了,前一篇博客《 HTTP协议详解》都说的很清楚。

想要完成这个小网页,首先得前后端定义接口,如下:

请求:

get

响应格式:

JSON

url

messageWallServlet

响应的 JSON 格式形如:

[
	{
		from: "黑猫",
		to: "白猫",
		message: "喵"
	},
	{
    from: "黑狗",
    to: "白狗",
    message: "汪"
	},
	......
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

这就约定好前后端交互接口了,实现代码如下:

import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

class Message {
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

@WebServlet("/messageWallServlet")
public class MessageWall extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    // 先使用内存代替,正常情况下应该是存到数据库中的
    private List<Message> messageList = new ArrayList<>();

    // 从后端读取数据
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 把需要返回的数据从 Java 对象打包成 JSON 字符串
        String retJson = objectMapper.writeValueAsString(messageList);
        // 告诉前端,返回的是 JSON 格式的
        resp.setContentType("application/json; charset=utf-8");
        resp.getWriter().write(retJson);
    }

    // 存储数据
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}
  • 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.

这样我们的获取留言部分就写好了。

3.2、发布留言

发布留言也得先约定前后端交互接口,如下:

请求:

post

请求的body格式:

JSON

响应的body格式:

JSON

url:

messageWallServlet

既然约定好接口了,那么实现如下:

import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

class Message {
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

@WebServlet("/messageWallServlet")
public class MessageWall extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    // 先使用内存代替,正常情况下应该是存到数据库中的
    private List<Message> messageList = new ArrayList<>();

    // 从后端读取数据
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 把需要返回的数据从 Java 对象打包成 JSON 字符串
        String retJson = objectMapper.writeValueAsString(messageList);
        // 告诉前端,返回的是 JSON 格式的
        resp.setContentType("application/json; charset=utf-8");
        resp.getWriter().write(retJson);
    }

    // 存储数据
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 先拿到数据,从 JSON 字符串转换成 Java 对象
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        messageList.add(message);
        // 设置状态码,表示成功了
        resp.setStatus(200);
        resp.getWriter().write("保存成功!!!");
    }
}
  • 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.

下面就可以看结果啦,启动我们的 Tomcat,结果如下:

详解 Servlet_JSON_02

通过这个小练习,主要是可以更好的理解前后端交互的过程。


四、总结

通过本文,我们深入探讨了 Java Web 开发中至关重要的技术——Servlet。从基础概念到实际应用,我们一步步搭建了一个简单的留言墙应用,展示了如何通过 Servlet 处理 HTTP 请求和响应,以及如何进行前后端数据交互。

首先,我们简要介绍了 Servlet 的定义和作用,借助通俗的比喻使其原理更加易懂。接着,我们通过具体的实例,详细阐述了如何使用 Servlet API 构建一个简单的 Web 应用。这个过程中,我们不仅讨论了前端页面的设计与实现,还重点讲解了后端 Servlet 的编写,包括处理 GET 和 POST 请求的方法。

在留言墙应用的开发过程中,我们还展示了如何使用 JSON 进行数据传输,以及如何使用 Jackson 库来处理 JSON 数据的序列化和反序列化。通过这些实战操作,相信读者对 Servlet 技术有了更深入的理解。


五、结语

Servlet 作为 Java Web 开发的核心技术之一,其重要性不言而喻。无论是初学者还是有经验的开发者,掌握并应用好 Servlet,都是提升 Web 开发能力的关键。通过本文的学习,相信读者已经掌握了 Servlet 的基本使用方法,并能在实际开发中加以应用。

然而,本文仅仅是对 Servlet 的一个初步介绍和应用示例。实际开发中,Servlet 技术还有许多高级特性和优化技巧等待我们去探索和掌握。希望本文能为你的 Java Web 开发之路提供有益的参考和启示,也希望你能不断深入学习,不断提升自己的技术水平,开发出更多优秀的 Web 应用。