0


Servlet【最复杂的hello world】

Tomcat 的基本使用是比较容易的:

1️⃣启动2️⃣把内容拷贝到 webapps3️⃣通过浏览器访问4️⃣使用 netstat 查看端口

我们要学习的重点是基于 Tomcat 进行编程!!

现在要写网站后端(HTTP 服务器),虽然可以重头写一个 HTTP 服务器,但是比较麻烦,Tomcat 已经完成这部分工作,并且 Tomcat 给我们提供了一系列 API,可以让我们在程序中直接调用;此时就可以省去一部分工作(HTTP 服务器肯定要根据 HTTP 协议解析请求报文,还要根据 HTTP 协议,构造响应报文,Tomcat 已经弄好了),更专注于业务逻辑了(写的程序要解决什么问题,是怎么解决的)

接下来我们将学习Tomcat 给提供了一系列 API 也叫 Servlet


一、Hello World

在 java 中使用 Servlet,先从一个 hello world 着手❗❗注意,接下来见到的是咱们整个学习生涯中,最复杂的 hello world;需要经历七个步骤(对初学者非常不友好),但是这些步骤都是一个固定套路

1.创建项目

此处需要创建一个 maven 项目:maven 是一个构建工具,能帮助我们去构建、测试、打包一个项目;

首次使用 maven 项目,IDEA 会从互联网上加载很多的依赖,需要花一定的时间,同时需要保证网络通畅

一个 maven 项目,首先会有一个 pom.xml 配置文件;这个文件描述了maven 项目的各个方面的内容;

<?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>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

一个 maven 创建好之后,IDEA 会帮助我们自动创建出一些目录:

2.引入依赖

Servlet 是 tomcat 提供的 api(不是标准库);标准库例如:String、Thread、List/Map、Scanner...只要装了 jdk,这些都是内置的;因此 servlet 是需要额外下载安装的(Tomcat 安装好了,是 Tomcat 运行时使用的,现在阶段是开发阶段,需要额外安装 Servlet 的 jar 包)

从中央仓库下载安装:Maven Repository: Search/Browse/Explore (mvnrepository.com)

在 pom.xlm 中添加 dependenceies 标签,把上边代码复制粘贴到这个标签中

<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>
</dependencies>

此时依赖就下载成功了;jar 包是被下载到本地的一个隐藏目录了,c 盘的用户里 .m2 文件中,找到 resposity打开,再找到 javax 寻找 servlet,里边有我们下载的 3.1.0 版本,只要下载好以后,后续使用就不必重新下载

3.创建目录

1️⃣创建 webapp 目录****:在 main 目录下, 和 java 目录并列, 创建一个 webapp 目录 (注意, 不是 webapps).

2️⃣创建 WEB-INF 目录:然后在 webapp 目录内部创建一个 WEB-INF 目录

3️⃣创建一个 web.xml 文件

这里的目录结构、目录位置、目录名字务必保证一字不差❗❗

web.xml 是给 tomcat 看的:tomcat 从 webapps 目录中加载 webapp,就是以 web.xml 为依据的

4️⃣**编写 web.xml **

往 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>

4.编写代码

在 java 项目下创建一个类 HelloServlet:

import javax.servlet.http.HttpServlet;
public class HelloServlet extends HttpServlet {
}

创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确,尝试刷新

4.1 继承 HttpServlet 父类,重写 doGet 方法

public class HelloServlet extends HttpServlet {

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

  • HttpServletRequest 表示 HTTP 请求:Tomcat 收到请求把这个请求按照 HTTP 协议的格式,解析成了对象(这个对象里的属性就是 HTTP 中的各个信息)也就是说 Tomcat 按照 HTTP 请求的格式把 字符串 格式的请求转成了一个 HttpServletRequest 对象. 后续想获取请求中的信息(方法, url, header, body 等) 都是通过这个对象来获取.
  • HttpServletResponse 表示 HTTP 响应:此处响应对象是一个空的对象,需要在 doGet 中设置响应的一些数据(例如响应的 body、header和状态码等...,只要把属性设置到这个 resp 对象中,Tomcat 就会自动根据响应对象,构造一个 HTTP 响应字符串,通过 socket 返回给客户端
  • 重写 doGet 方法:这个方法不是手动调用,而是 Tocmat 在合适的时机自动调用的

这种代码的编写方式,就算“框架”(framework)

1️⃣注释掉 super.doGet(req, resp);

点进父类的 doGet 可以看到这里直接返回一个错误页面,如果步干掉就返回了一个 405

4.2 在 doGet 中编写代码,打印 hello world

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);//调用父类的doGet,需要注释掉这个代码

        //这里在服务器的控制台中,打印了字符串(服务器看到了,客户端没看到)
        System.out.println("hello world");
        //这个是给 resp 的 body 写入 hello world 字符串,这个内容就会被 HTTP 响应返回给浏览器,显示到浏览器页面上
        resp.getWriter().write("hello wprld");
    }
}
  • resp.getWriter() :得到了 resp 内部持有的 Writer 对象(字符流),既然是字符流就可以使用 write 来写,此处写的数据就是写到 http 响应的body中 Tomcat 会把整个响应转成字符串, 通过 socket 写回给浏览器

4.3 给 HelloServlet 加上注解

@WebServlet("/hello")

4.4 完整代码

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;

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);//调用父类的doGet,需要注释掉这个代码

        //这里在服务器的控制台中,打印了字符串(服务器看到了,客户端没看到)
        System.out.println("hello world");
        //这个是给 resp 的 body 写入 hello world 字符串,这个内容就会被 HTTP 响应返回给浏览器,显示到浏览器页面上
        resp.getWriter().write("hello world");
    }
}

上述代码就已经写完了,不需要 main 方法;上述代码并非独立运行,而是把这个代码插入到 Tomcat 中,由 Tomcat去调用的

5.打包代码

我们的程序不能独立运行,而是必须放到 Tomcat 上运行(部署),部署的前提是打包

  • 对于一个规模比较大的项目,里边就会包含很多的 .java 文件,进一步就会产生很多的 .class,所以把这些 .class 答案宝成一个压缩包再进行拷贝,是比较科学的
  • 在 java 中使用的压缩包为 jar(普通的 java 程序)、war(部署给 tomcat 的程序)
  • war 和 jar 本质上没有区别,都是把一堆 .class 文件给打包进去,但是 war 包是属于 tomcat 的专属格式,里边会有一些特定的目录结构和文件,比如 web.xml,后续 tomcat 就要识别这些内容来加载 webapp

如何使用 maven 进行打包操作❓❓打开 maven 窗口 (般在 IDEA 右侧就可以看到 Maven 窗口, 如果看不到的话可以通过 菜单 -> View -> Tool Window -> Maven 打开),然后展开 Lifecycle , 双击 package 即可进行打包

** 打包的操作:**

  1. 检查代码中是否存在一些依赖,依赖是否下载好(这个事情都是 maven 负责的,之前引入了 serlvet 的依赖)
  2. 把代码进行编译,生成一堆 .class 文件
  3. 把这些 .class 文件,以及 web.xml 按照一定的格式进行打包

为了打出来的是 war 包,需要调整 pom.xml,描述打包生成的包格式:pom.xml 中,在 project 顶级标签下方,写一个 <packing> 标签,描述打包的类型是 war;此处也可以修改打包的文件名

此时重新打包,就看到了一个 war 包

打好的 war 包,就是一个普通的压缩包,收可以使用解压工具(WINRAR)打开,看到里边的内容,但是并不需要手动解压缩,直接把整个 war 交给 tomcat,tomcat能够自动的解压缩

6.部署

把打好的 war 包 ,拷贝到 tomcat 的 webapps 目录中;重新打开 tomcat 的 bin 文件 的 startup.bat ,此时就已经部署完成

这个乱码表示 hello_servlet.war 已经部署完成,只不过日志乱码;乱码是因为拜尼马方式不一样,tomcat 使用的编码是 utf8,而 windows 的 cmd 编码是 gdk

7.验证程序

doGet : 遇到 GET 请求,就可以执行 doGet,前提是 请求的 URL 的路径要匹配

浏览器 url 中输入(hello_servlet表示一级路径、hello表示二级路径):127.0.0.1/8080/hello_servlet/hello

此处的路径是分两级:

  1. **hello_servlet:称为 Context Path / Application Path,标识了一个 webapp(**也就是 webapp 的目录名 / war 包名)一个 Tomcat 上可以多个webapp
  2. hello:称为 Servlet Path,标识当前请求要调用哪个 Servlet 类的 doGet(一个 webapp 中可以有多个 Servlet,自然就有多个 doGet),此处的 hello 是根据注解来的

二、简化部署方式

上述 hello word 的程序也可以简化:把 5(打包代码) 和 6(部署) 简化成一键式完成,我们使用 IDEA 中的** Smart Tomcat**插件来完成这个过程

IDEA 功能非常多,非常强大,凡是及时如此,IDEA也无法做到“面面俱到”,为了支持这些特定的、小众的功能就引入了“插件体系”,插件可以视为对IDEA原有功能的扩充,程序猿可以按需使用;同理很多这样的程序都引入了插件体系,例如 VSCode

1.安装 Smart Tomcat 插件

1️⃣打开 file,继续打开 Settings

2️⃣选择 Plugins, 选择 Marketplace, 搜索 "tomcat", 点击 "Install"

使用 Smart Tomcat 插件,可以简化打包部署工作(社区版使用的方法);IDEA 专业版来说,内置了 Tomcat Server(这个东西用起来更复杂,还是建议使用 smart tomcat)

2.配置 Smart Tomcat 插件

首次使用,需要配置插件:

1️⃣点击右上角的 "Add Configuration";选择左侧的 "Smart Tomcat"

2️⃣在这里 Name 可以改也可以不改,我改为了 hello servlet;并且把 Tomcat server 的路径改为 原本 Tomcat 安装的路径

3️⃣修改 Content path:访问程序的两级路径中的第一级 (我的第一级目录是 hello_servlet)

  • 特殊规则:
  • 如果我们的程序是拷贝 war 包到 webapps 中运行,此时 Context Path 是 war 包名字
  • **如果我们的程序是使用 Smart Tomcat 运行,Context Path 是在上述配置中,手动设置的,默认是项目名字 **

4️⃣运行代码

此时右上角的 "Add Configuration"旁边有个三角形,点击即可运行

此时 Tomcat 的日志就在 IDEA 中就显示了,不会再单独弹出 cmd,因此乱码问题就解决了

3.常见错误

初学者可能出现以下错误问题:

** 端口被占用**:Tomcat 启动需要绑定两个端口,8080(业务端口)、8005(管理端口);一个端口号只能呗一个进程绑定;此时我们直接把 bin 文件打开的 startup.bat 关闭即可

此时就运行成功了

以上地址是提示 Tomcat 如何访问(localhost == 127.0.0.1),不要点,点了就是 404 ;因为这个路径只有 Context Path,没有 Servlet Path

4.简化的本质区别

smart tomcat 的运行方式和之前拷贝到 webapps 中,是存在本质的

  • smart tomcat 使用了 Tomcat 另外一种运行方式;在运行 Tomcat 的时候,通过特定的参数来指定 Tomcat 加载某个特定目录中的 webapp

因此,上述过程既不会打包也不会拷贝;这是开发和调试阶段使用的方式,如果是部署到生产环境,还的是打 war 包拷贝

三、servlet 中常见的问题

1. 404

404 表示浏览器访问的资源在服务器上不存在

1️⃣请求的路径写错了

例如刚刚上述的地址( Tomcat 如何访问):路径只有 Context Path,没有 Servlet Path 或者路径只有 Servlet Path,没有 Context Path

再例如:Servlet Path 写的和 URL 不匹配,也会出现 404

2️⃣路径写对了,但是 war 包没有被正确加载

web.xml 写错了 或者 如果有两个 Servlet 的 Servlet Path 相同,会导致 war 包不能被正确加载(如果没正确加载,会在日志中有提示)

2.405

405 表示 对应 HTTP 请求方法没有实现

1️⃣发送请求的方法和代码不匹配:比如代码写的是 doPost,而发送的请求是 GET 请求

2️⃣ 方法和代码匹配,但是忘记消掉 super.doXXX

3.500

500 往往是 Servlet 代码中抛出的异常导致:需要观察异常调用栈

修改 HelloServlet 代码:

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);//调用父类的doGet,需要注释掉这个代码

        String s = null;
        System.out.println(s.length());
        //这里在服务器的控制台中,打印了字符串(服务器看到了,客户端没看到)
        System.out.println("hello world");
        //这个是给 resp 的 body 写入 hello world 字符串,这个内容就会被 HTTP 响应返回给浏览器,显示到浏览器页面上
        resp.getWriter().write("hello world");
    }
}

4.出现“空白页面”

修改代码:去掉 resp.getWritter().write() 操作

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);//调用父类的doGet,需要注释掉这个代码

        //这里在服务器的控制台中,打印了字符串(服务器看到了,客户端没看到)
        System.out.println("hello world");
        //这个是给 resp 的 body 写入 hello world 字符串,这个内容就会被 HTTP 响应返回给浏览器,显示到浏览器页面上
        //resp.getWriter().write("hello world");
    }
}

重启服务器,访问服务器可以看到一个空白页面

5.出现“无法访问此网站”

停止 Tomcat,然后访问服务器就可以看到“无法访问此网站”

四、Servlet API 详解

虽然 Servlet API 有很多,重点掌握三个类即可

  1. HttpServlet
  2. HttpServletRequest
  3. HttpServletResponse

1. HttpServlet

Servlet 程序都是要继承一个 HttpServlet 类

因此我们就需要知道哪些方法是能够被重写的,也就是 HttpServlet 中有什么方法,都是做什么的

1.1 HttpServlet 方法

方法名称调用时机doGet收到 GET 请求的时候调用(由 service 方法调用)doPost收到 POST 请求的时候调用(由 service 方法调用)
**doPut/doDelete/doOptions/... **

收到其他请求的时候调用(由 service 方法调用)
init
在 HttpServlet 实例化之后被调用一次

**destroy **

在 HttpServlet 实例不再使用的时候调用一次

**service **

收到 HTTP 请求的时候调用

1️⃣init 方法

HttpServlet 被实例化之后会调用一次(只调用一次)(首次匹配请求的时候,会被调用),使用这个方法来做一些初始化相关的工作

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        System.out.println("打印 init");
    }

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);//调用父类的doGet,需要注释掉这个代码

        //这里在服务器的控制台中,打印了字符串(服务器看到了,客户端没看到)
        System.out.println("hello world");
        //这个是给 resp 的 body 写入 hello world 字符串,这个内容就会被 HTTP 响应返回给浏览器,显示到浏览器页面上
         resp.getWriter().write("hello world");
    }
}

启动服务器,此时并没有实例化对象,并没有执行到“打印 init”:

而是服务器收到一个匹配的请求的时候会被调用(能够调用 doGet 的请求):

此时就执行了 “打印 init”;上述 127.0.0.1:8080/hello_servlet/hello 这个请求会触发 HelloServlet 类的 doGet 的执行,就会调用 doGet 之前,先调用 init;❗❗注意,只会调用一次

因此这个方法来做一些初始化相关的工作

2️⃣destroy 方法

这个方法是 webapp 被卸载(被销毁之前)执行一次;用来做一些收尾工作

destroy 是否能被执行,是不靠谱的❓❓❓


  1. 如果是通过 8005 管理端口来停止服务器,此时 destroy 能执行
  2. 如果是直接杀死进程的方式停止服务器,此时 destroy 执行不了

所以不建议使用 destroy

❓❓8005 管理端口是什么

Tomcat 启动会使用两个端口:8080业务端口(工作)、8005管理端口(生活)

3️⃣service 方法

每次收到路径匹配的请求都会执行;doGet 和 doPost 其实是在 service 中被调用的,一般不会重写 service,只是重写 doXXX 就行了

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        //HttpServlet 被实例化之后会调用一次(只调用一次)(首次匹配请求的时候,会被调用),使用这个方法来做一些初始化相关的工作
        System.out.println("打印 init");
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行 service");
    }

    //继承 HttpServlet 父类,重写 doGet 方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);//调用父类的doGet,需要注释掉这个代码

        //这里在服务器的控制台中,打印了字符串(服务器看到了,客户端没看到)
        System.out.println("hello world");
        //这个是给 resp 的 body 写入 hello world 字符串,这个内容就会被 HTTP 响应返回给浏览器,显示到浏览器页面上
         resp.getWriter().write("hello world");
    }
}

4️⃣doGet、doPost、doPut...方法:

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPost");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPut");
    }
}

启动服务器,使用 Postman 构造请求:

❗❗注意:

@WebServlet("/hello")
//创建一个类 HelloServlet , 继承自 HttpServlet(来自于从 maven 中央仓库下载的 jar 包);如果提示不出来说明 jar 包没有加载正确

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("这是一个 doGet 请求");
    }
}

** ???说明是乱码了**


  • 数据返回的时候,自身是一种编码方式(IDEA 里边写一个字符串,默认都是 UTF8)
  • 浏览器在展示的时候,也有一种编码方式(根据系统的默认编码——windows 10 简体中文版,默认编码是 gdk)

如果上述两个方式对不上,就会乱码

解决方法:加入 **resp.setContentType("text/html; charset=utf8"); **代码代表:告诉浏览器返回的数据是 utf8

1.2 Servlet 的生命周期(面试题)

生命周期:什么阶段在做什么事

  1. init 是初始情况下调用一次
  2. destroy 是结束之前调用一次
  3. service 是每次收到路径匹配的请求都调用一次

🌈这节课我们讲解了 Servlet 的使用,写了有史以来最复杂的 Hello World,并且解决了写代码出现的问题,而且介绍了三种重要的 Servlet API 中的 HttpServlet,下节课我们将介绍 HttpServletRequest 、 HttpServletResponse以及代码示例

标签: servlet tomcat java

本文转载自: https://blog.csdn.net/m0_72161237/article/details/130794410
版权归原作者 奋斗小温 所有, 如有侵权,请联系我们删除。

“Servlet【最复杂的hello world】”的评论:

还没有评论