都2023年了,还有必要学习Servlet吗?一文带你快速学会Servlet

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录

1. 前言

起初所有的 Web 网站都是静态的网页只是作为一个信息的发布平台客户机上请求服务器上的某个资源服务器将指定的资源返回给客户机。随着计算机网络的发展显然静态网站已经无法满足需求。Servlet 是 Java 进军 Web 开发领域的第一款技术完全基于Java实现。

Servlet 是一个运行于 Servlet 容器中的 Java 对象具有很多的优势例如 Servlet 初始化仅在第一次访问时进行并且只执行一次Servlet 能够维护每个请求的状态一旦加载了 Servlet他就存放在内存中具有移植性等。

image-20230204212523756

不得不说Servlet 确实是一门古老的技术了现在很少有公司直接使用 Servlet 来写项目了大家都在用 SpringMVC-Spring-MyBatis / SpringBoot 做开发了那么都2023年了Servlet 还需要学习吗真的有必要吗

答案是肯定的学习了 SpringMVC 的同学一定知道他的底层是离不开 Servlet 的其实不止这一点还有很多的地方用到了 Servlet 思想。所以一个中肯的建议是不要跳过 Servlet 去学习框架学习完 Servlet 你将有更好的基础去面对后面的框架的知识。

说完了学习 Servlet 的必要性下面我们将开始 Servlet 系列的学习你正在阅读的是 Servlet 快速入门篇让你快速了解 Servlet 技术。

2. Servlet 简介

Servlet 是 JavaEE 的规范之一通俗的来说就是 Java 接口将来我们可以定义 Java 类来实现这个接口并由 Web 服务器运行 Servlet 所以 TomCat 又被称作 Servlet 容器。

Servlet 提供了动态 Web 资源开发技术一种可以将网页数据提交到 Java 代码并且将 Java 程序的数据返回给网页的技术使用 Servlet 技术实现了不同用户登录之后在页面上动态的显示不同内容等的功能。

3. 快速入门

我们通过一个案例来快速了解什么是 Servlet怎么样使用 Servlet后面的案例大多不做具体实现只是举例的方式说明同时我们会在快速入门的过程中多了解源码种的实现逻辑。

需求编写一个 Servlet部署到 Tomcat 服务器中最终实现浏览器访问这个 Servlet 程序。

第一步

创建一个 Web 项目 servlet-project导入 Servlet 的依赖坐标

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

请把这个依赖的范围设置为 provided 即只在编译和测试的过程中有效最后生成的 war 包中不会加入这个依赖 jar 包因为 TomCat 文件中本身就带有这个 jar 包如果不使用这个范围则会出现冲突。添加依赖范围时只需要在依赖坐标下面添加 <scope> 标签。

第二步

定义一个类用来实现 Servlet 接口并重写接口中的所有方法。

public class ServletDemo implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Hello");
    }

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

    @Override
    public void destroy() {

    }
}

第三步

通过 @WebServlet 注解配置其访问路径如图

第四步

启动 TomCat 在浏览器中访问该 Servlet

image-20230204111801726

此时浏览器页面展示 Hello World因为我们项目中有一个默认的 html 文件文件中内容被展示到浏览器。此时往浏览器的 url 路径中添加刚才使用 Java 注解配置的访问路径idea 控制台就会打印刚才在 servlet() 方法中定义的输出内容。

我们并没有实例化这个 Servlet 类的对象那么为什么 servlet() 方法被成功执行了呢学习完后面的内容以后这个问题就不难理解了。

4. 执行流程

要想知道我们并没有手动创建 ServletDemo 这个类的实例化对象为什么它的方法 servlet() 被成功执行这个问题我们就要了解其执行流程。

image-20230204151930520

在上面的例子中我们已经写好一个 Servlet 的项目并且将其部署到了 Web 服务器 TomCat 中此时我们可以根据对应的 url 在浏览器中访问该服务器资源。

浏览器发出 http://localhost:8080/servlet-project/demo请求这个请求大致分为3部分分别是

  • 根据http://localhost:8080找到要访问的 Tomcat 服务器
  • 根据servlet-project找到部署在 TomCat 服务器中的 Web 项目
  • 根据demo找到访问的项目中的具体 Servlet因为我们已经通过注解给 Servlet 配置了具体的访问路径

此时 Web 服务器软件 TomCat 将会创建一个 ServletDemo 的对象这个对象称为 Servlet 对象并且该对象的 service() 方法也会被服务器自动调用。

当 service() 方法被调用执行后就会向客户端浏览器发送响应数据。上面的例子中我们没有向浏览器发送具体的数据要想实现这个功能我们就要继续学习 ServletRequest 类和 ServletResponse 类。

其中 ServletRequest 中封装了请求数据而 ServletResponse 中则是封装的响应数据通过这两个类的对象就可以实现前后端的数据交互了。

5. 生命周期

在 Servlet 执行流程中我们说到Servlet 对象是由 Web 服务器创建的那么具体创建时机是什么时候呢要想了解这个问题就要学习 Servlet 的生命周期。

对象的生命周期是指一个对象从创建到销毁经历的整个过程。Servlet 运行在 Servlet 容器Web 服务器中其生命周期由容器来管理大致分为四个阶段

  1. 加载和实例化
  2. 初始化
  3. 请求处理
  4. 服务终止

加载和实例化默认情况下当 Servlet 第一次被访问时由容器创建 Servlet 对象有时创建 Servlet 是比较耗时的那么第一次访问就比较耗费时间用户体验比较差。Servlet 提供了解决这个问题的方法通过具体的配置可以实现在服务器启动的时间来创建 Servlet 对象提高了访问速度。

只需要使用下面这个简单的配置

@Webservlet(urlPatterns = "/demo",loadOnStartup=1)

其中 loadOnStartup 参数如果是负整数则 Servlet 对象在第一次访问时创建如果参数的值为 0 或者正整数的话则会在服务器启动时创建并且数字越小优先级越高。

初始化在 Servlet 实例化以后容器就会调用 init() 方法初始化这个对象完成一些如加载配置文件创建连接等初始化工作该方法只会被调用一次。

请求处理每次请求 Servlet 时Servlet 容器都会调用 Servlet 的 service() 方法来对请求进行处理该方法会被多次调用。

服务终止当需要释放内存或者是容器关闭时容器都会调用 Servlet 的 destroy() 方法完成资源的释放在 destroy() 方法调用之后容器会释放实例化对象随后被 Java 垃圾回收机制处理该方法只会被调用一次。注意此时的服务器关闭指的是正常关闭非强制关闭。

6. 方法初识

Servlet 是一个接口其中一共有 5 个方法当我们的类实现了这个接口以后必须将这 5 个方法全部实现。这 5 个方法分别是

  • 初始化方法void init()
  • 提供服务方法void service()
  • 销毁方法void destroy()
  • 获取Servlet信息方法String getServletInfo()
  • 获取ServletConfig对象方法servletConfig getServletConfig()

其中前三个方法我们在之前的生命周期中已经接触过了在 Servlet 被创建时会执行 init() 方法进行初始化操作此方法只会执行一次每次 Servlet 被访问时都会执行 service() 方法而 Servlet 被销毁时则会执行 destroy() 方法释放对象。

我们看到在进行初始化操作时会往 init() 方法中传入 ServletConfig 类对象如下

@Override
public void init(ServletConfig servletConfig) throws ServletException {
//...
    }

所以在 getServletConfig() 方法中我们只需要将容器创建的 ServletConfig 对象返回即可而在 getServletInfo() 方法中先返回一个空字符串处理如下

private ServletConfig servletConfig;

public void init(ServletConfig config) throws ServletException {
    this.servletConfig = config;
    System.out.println("init...");
}
public ServletConfig getServletConfig() {
    return servletConfig;
}


public String getServletInfo() {
     return "";
}

其中最常用的是前面三个方法这里我们都是只做了解后面等对整个 Servlet 体系有了认知以后再深入学习。

小tips这也是我们学习编程一个很重要的方法论先广度后深度前期不用深入的了解其底层的含义否则容易导致我们钻牛角尖得不偿失。

7. 体系结构

在我们编写的实现 Servlet 接口的类中我们更加关注的是 service() 方法有没有一种方式让我们创建 Servlet 更加简便高效呢学习完 Servlet 的体系结构之后这个问题就变得简单了。

我们开发 B / S 架构的 Web 项目时其实都要对 HTTP 协议进行封装所以我们自定义的 Servlet 要继承 HttpServlet Servlet 的体系结构如下

image-20230204172421386

我们想要实现的是在客户端发送 GET 请求给 Servlet 后执行一种逻辑当客户端发送 POST 请求给 Servlet 后执行另外一种逻辑…

在 HttpServlet 类中就实现了这样的功能HttpServlet 类会判断页面发送的请求方式根据不同的请求方式定义了不同的执行方法例如 doGetdoPOST 等

例如下面是部分HttpServlet类的源码

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

我们自己定义的实现类只需要继承自 HttpServlet 类并且重写 HttpServlet 类中的 doGet 这样的方法以此来实现不同的处理逻辑。例如

@WebServlet("/demo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO GET 请求方式处理逻辑
        System.out.println("get...");
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO Post 请求方式处理逻辑
        System.out.println("post...");
    }
}

8. urlPattern 配置

Servlet 编写好以后要想被浏览器访问就需要配置其访问路径例如前面案例中我们就通过 Java 注解的方式给 Servlet 配置了访问路径如图

image-20230204175956128

一个 Servlet 可以配置多个访问路径urlPattern 的配置有以下几个规则

  • 精确匹配
  • 目录匹配
  • 扩展名匹配
  • 任意匹配

精确匹配

当我们配置如下的访问路径

@WebServlet("/user/demo")

此时我们在浏览器中访问时的 url 为

image-20230204181440797

目录匹配

当我们配置如下的访问路径

@WebServlet("/user/*")

此时我们在浏览器中的访问 url 为

image-20230204181508604

image-20230204181534164

扩展名匹配

当我们配置如下的访问路径

@WebServlet("*.do")

此时我们在浏览器中的访问 url 为

image-20230204181738354

image-20230204181757215

任意匹配:

当我们配置如下的访问路径

@WebServlet("/")
@WebServlet("/*")

此时我们在浏览器中的访问 url 为

image-20230204181943709

image-20230204182033780

/*/ 的区别当项目中的 Servlet 配置了 / 会覆盖 tomcat 中的 DefaultServlet 默认访问路径。DefaultSerlet 是用来处理静态资源的当其他的 urlPattern 都匹配不上时就会访问这个 Servlet如果我们自己定义了 / 访问路径则请求静态资源时不会生效而会匹配自己定义的路径最终导致静态资源无法访问所以这种方式不建议使用。

当我们的项目中配置了 /*意味着匹配任意访问路径。

urlPattern 共有四种匹配方式分别是精确匹配目录匹配扩展名匹配和任意匹配。其具有不同的优先级具体为精确匹配 > 目录匹配 > 扩展名匹配 > 任意匹配任意匹配中 /* 大于 /

9. XML配置

前面对应的 Servlet 访问路径我们都是使用 Java 注解的方式配置其实在 Servlet 3.0 之前是只支持 XML 配置文件的方式配置 Servlet 的访问路径这里只对其方法做了解。

使用 XML 配置方法大致分为两步编写 Servlet 类和配置下面通过案例的方式讲解。

第一步编写 Servlet 类。

public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO GET 请求方式处理逻辑
        System.out.println("get...");
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO Post 请求方式处理逻辑
        System.out.println("post...");
    }
}

第二步在 web.xml 中配置该 Servlet

<?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>
        <!-- servlet的名称名字任意-->
        <servlet-name>demo</servlet-name>
        <!--servlet的类全名-->
        <servlet-class>org.chengzi.web.ServletDemo</servlet-class>
    </servlet>

    <!-- 
        Servlet 访问路径
    -->
    <servlet-mapping>
        <!-- servlet的名称要和上面的名称一致-->
        <servlet-name>demo</servlet-name>
        <!-- servlet的访问路径-->
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>
</web-app>

10. 总结

不得不说Servlet 确实是一门古老的技术现在很少有公司直接使用 Servlet 来写项目了大家都在用 SpringMVC-Spring-MyBatis / SpringBoot 做开发了那么 Servlet 还值得学习吗一个中肯的建议是不要跳过 Servlet 去学习框架学习完 Servlet 你将有更好的基础去面对后面的框架的知识而绝非浪费时间和精力。

本文是 Servlet 的快速入门篇主要从执行流程生命周期方法的介绍体系结构和访问路径的配置等这几个方面探讨了 Servlet 。后面我们将通过源码更加深入的学习。

下期见。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6