Servlet 运行和运行原理

运行一个servlet项目

1. 创建项目

使用 IDEA 创建一个 Maven 项目.

  1. 菜单 -> 文件 -> 新建项目 -> Maven
    在这里插入图片描述

创建好后的项目:
@S0HGGBUFER(GET~85LM@14.png

2. 引入依赖

Maven 项目创建完毕后, 会自动生成一个 pom.xml 文件.
我们需要在 pom.xml 中引入 Servlet API 依赖的 jar 包.

  1. 在中央仓库 https://mvnrepository.com/ 中搜索 “servlet”, 一般第一个结果就是.
    G`82%36MEVNS)9X$47MS5T1.png
  2. 选择版本(点击版本号). 我的tomcat版本是8.5.49,对应使用的servlet版本为 3.1.0 版本。可以在 http://tomcat.apache.org/whichversion.html 查询版本对应关系.
  3. 把中央仓库中提供的 xml 复制到项目的 pom.xml
    image.png
    修改后的 pom.xml
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>servlet-test</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>servlet-test Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <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>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>servlet-test</finalName>
  </build>
</project>

这个时候点击右上角的image.png,如果没有:
在这里插入图片描述

3. 创建目录

当项目创建好了之后, IDEA 会帮我们自动创建出一些目录. 形如
image.png

  • src 表示源代码所在的目录
  • main/resources 表示项目的一些资源文件所在的目录. 此处暂时不关注.
  • main/webapp webapp 目录就是未来部署到 Tomcat 中的一个重要的目录.

当前我们可以往 webapp 中放一些静态资源, 比如 html , css 等. 在这个目录中还有一个重要的文件 web.xml. Tomcat 找到这个文件才能正确处理 webapp 中的动态资源.
1) 创建 java 目录
main 目录下, 和resources目录并列, 创建一个 java 目录 .
在这里插入图片描述

结果如下:
image.png

4. 编写代码

在 java 目录中创建一个类 HelloServlet, 代码如下:

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")
    public class HelloServlet extends HttpServlet {
    
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
            //         获取前端参数
            String name = req.getParameter("name");
            //         编码设置方式1
            //         resp.setCharacterEncoding("UTF-8");
            resp.setContentType("text/html; charset=utf-8");

            //         将结果返回给前端
            resp.getWriter().println("Jackson,");

            //         System.out.println("hello");
            resp.getWriter().write("hello");

        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
            //将请求转发到 doget 方法中
            this.doGet(req,resp);
        }
    }

  • src/main下创建Java包,然后在Java包下创建一个类。
  • 在这个类上方加上 @WebServlet("/hello") 注解, 表示 Tomcat 收到的请求中,路径为/hello的请求才会调用 HelloServlet 这个类的代码.(这个路径未包含 Context Path)
  • 重写 doGet 方法. doGet 的参数有两个, 分别表示收到的 HTTP 请求 和要构造的 HTTP 响应.这个方法会在 Tomcat 收到 GET 请求时触发
  • HttpServletRequest 表示 HTTP 请求. Tomcat 按照 HTTP 请求的格式把 字符串 格式的请求转成了一个 HttpServletRequest 对象. 后续想获取请求中的信息(方法, url, header, body 等) 都是通过这个对象来获取.
  • HttpServletResponse 表示 HTTP 响应. 代码中把响应对象构造好(构造响应的状态码, header, body 等)
  • resp.getWriter() 会获取到一个流对象, 通过这个流对象就可以写入一些数据, 写入的数据会被构造成一个 HTTP 响应的 body 部分, Tomcat 会把整个响应转成字符串, 通过 socket 写回给浏览器.

注意:

    1. 我们的代码不是通过 main 方法作为入口了. main 方法已经被包含在 Tomcat 里, 我们写的代码会被 Tomcat 在合适的时机调用起来. 此时我们写的代码并不是一个完整的程序, 而是 Tomcat 这个程序的一小部分逻辑.
    1. 我们随便写个类都能被 Tomcat 调用嘛? 满足啥样条件才能被调用呢? 主要满足三个条件:

a) 创建的类需要继承自 HttpServlet
b) 这个类需要使用 @WebServlet 注解关联上一个 HTTP 的路径
c) 这个类需要实现 doXXX 方法.
当这三个条件都满足之后, Tomcat 就可以找到这个类, 并且在合适的时机进行调用.

5. 打包程序

使用 maven 进行打包. 打开 maven 窗口 (一般在 IDEA 右侧就可以看到 Maven 窗口, 如果看不到的话, 可以通过 菜单 -> View -> Tool Window -> Maven 打开)
然后展开 Lifecycle , 双击 package 即可进行打包.
PY9FR[12)]C7~I2W058F9W8.png
如果比较顺利的话, 能够看到 SUCCESS 这样的字样:
在这里插入图片描述

如果代码/配置/环境存在问题, 可能会提示 BUILD FAILED, 可以根据具体提示的错误信息具体解决.

6. 部署程序

把 war 包拷贝到 Tomcatwebapps 目录下.
(1)从项目的target目录下找到.war包,将这个包复制到Tomcatwebapps 目录下.

CA(N7TP({RHL7)5LE}WMVYX.png

7. 验证程序

(1)启动Tomcat(点击tomcatbin目录下的startup.batTomcat 就会自动把 war 包解压缩.
image.png
看到这个日志说明 Tomcat 已经正确识别了这个 webapp.
(2)此时通过浏览器访问 http://localhost:8080/servlet-test/hello,就可以看到结果了.
在这里插入图片描述

更方便的部署方式

手动拷贝 war 包到 Tomcat 的过程比较麻烦. 我们还有更方便的办法.
此处我们使用 IDEA 中的 Smart Tomcat 插件完成这个工作.
如果你的idea是社区版(专业版不用安装,idea已经内置了):

安装 Smart Tomcat 插件

  1. 菜单 -> 文件 -> Settings
  2. 选择 Plugins, 选择 Marketplace, 搜索 “tomcat”, 点击 “Install”. (注意: 安装过程必须要联网. )
    `GMAVYA~Y3A$5DCH5U5BDKJ.png

配置 Smart Tomcat 插件

  1. 点击右上角的 “Add Configuration
    image.png
  2. 选择左侧的 “Smart Tomcat
    image.png
  3. Name 这一栏填写一个名字(可以随便写)
    Tomcat Server 这一栏选择 Tomcat 所在的目录. 其他的选项不必做出修改.
    image.png
  4. 点击 OK 之后, 右上角变成了
    image.png
    点击绿色的三角号, IDEA 就会自动进行编译, 部署, 启动 Tomcat 的过程.
    此时 Tomcat 日志就会输出在 IDEA 的控制台中, 可以看到现在就不再乱码了.
    image.png
  5. 访问页面.
    在浏览器中使用http://localhost:8080/servlet-test/hello访问页面.
    image.png

访问出错怎么办?

出现 404

404 表示用户访问的资源不存在. 大概率是 URL 的路径写的不正确.
错误实例1: 少写了 Context Path
通过 /hello 访问服务器
image.png

错误实例2: 少写了 Servlet Path
通过/ServletHelloWorld访问服务器
image.png

错误实例3: Servlet Path 写的和 URL 不匹配
修改 @WebServlet 注解的路径
image.png
重启 Tomcat 服务器.
URL 中的路径写作 “/hello” , 而代码中写作的 Servlet Path 为 “/helloServlet”, 两者不匹配.
image.png

错误实例4: web.xml 写错了
清除 web.xml 中的内容,重启 Tomcat 服务器.
通过浏览器访问 URL, 可以看到:
image.png

出现 405

405 表示对应的 HTTP 请求方法没有实现.
**错误实例:**没有实现 doGet 方法.

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    
    
}

重启 Tomcat 服务器.
在浏览器中访问, 可以看到:
image.png
在浏览器地址栏直接输入 URL , 会发送一个 HTTP GET 请求.
此时就会根据 /ServletHelloWorld/hello 这个路径找到 HelloServlet 这个类. 并且尝试调用
HelloServletdoGet 方法.
但是如果没有实现 doGet 方法, 就会出现上述现象.

出现 500

往往是 Servlet 代码中抛出异常导致的.
错误实例:
修改代码

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
    
    
        String s = null;
        resp.getWriter().write(s.length());
   }
}

重启 Tomcat 服务器.
重新访问页面, 可以看到:
image.png
在页面上已经有具体的异常调用栈.
异常信息里已经提示了出现异常的代码是 HelloServlet.java 的第 13 行.

resp.getWriter().write(s.length());

仔细检查这里的代码就可以看到空指针异常.

出现 “空白页面”

错误实例:
修改代码, 去掉 resp.getWritter().write() 操作.

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
    
    
        System.out.println("hello");
   }
}

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

出现 “无法访问此网站”

一般是 Tomcat 启动就失败了.
错误实例: Servlet Path 写错了.
image.png
应该写作 “/hello”, Tomcat 在启动的时候已经提示了相关的错误.
Tomcat 启动的日志里面报错信息可能比较多, 需要耐心观察, 找到关键的提示.
image.png
看到的现象:
image.png

小结:

  • 4xx 的状态码表示路径不存在, 往往需要检查 URL 是否正确, 和代码中设定的 Context Path 以及 Servlet Path 是否一致.
  • 5xx 的状态码表示服务器出现错误, 往往需要观察页面提示的内容和 Tomcat 自身的日志, 观察是否存在报错.
  • 出现连接失败往往意味着 Tomcat 没有正确启动, 也需要观察 Tomcat 的自身日志是否有错误提示.
  • 空白页面这种情况则需要我们使用抓包工具来分析 HTTP 请求响应的具体交互过程.

在 Servlet 的代码中我们并没有写 main 方法, 那么对应的 doGet 代码是如何被调用的呢? 响应又是如何返回给浏览器的?

Tomcat 的定位

我们自己的实现是在 Tomcat 基础上运行的。
image.png
当浏览器给服务器发送请求的时候, Tomcat 作为 HTTP 服务器, 就可以接收到这个请求.
HTTP 协议作为一个应用层协议, 需要底层协议栈来支持工作. 如下图所示:
image.png
更详细的交互过程可以参考下图:
image.png

  1. 接收请求:
    用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求.
    这个 HTTP 请求会经过网络协议栈逐层进行 封装成二进制的 bit 流, 最终通过物理层的硬件设备转换成光信号/电信号传输出去.
    这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达目标主机(这个过程也需要网络层和数据链路层参与).
    服务器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成 HTTP 请求. 并交给 Tomcat 进程进行处理(根据端口号确定进程)
    Tomcat 通过 Socket 读取到这个请求(一个字符串), 并按照 HTTP 请求的格式来解析这个请求, 根据请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 . 再根据当前请求的方法 (GET/POST/…), 决定调用这个类的 doGet 或者 doPost 等方法. 此时我们的代码中的 doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息.
  2. 根据请求计算响应:
    在我们的 doGet / doPost 方法中, 就执行到了我们自己的代码. 我们自己的代码会根据请求中的一些信息, 来给 HttpServletResponse 对象设置一些属性. 例如状态码, header, body 等.
  3. 返回响应:
    我们的 doGet / doPost 执行完毕后, Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串, 通过 Socket 把这个响应发送出去.
    此时响应数据在服务器的主机上通过网络协议栈层层 封装, 最终又得到一个二进制的 bit 流, 通过物理层硬件设备转换成光信号/电信号传输出去.
    这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达浏览器所在的主机(这个过程也需要网络层和数据链路层参与).
    浏览器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成 HTTP 响应, 并交给浏览器处理.
    浏览器也通过 Socket 读到这个响应(一个字符串), 按照 HTTP 响应的格式来解析这个响应. 并且把 body 中的数据按照一定的格式显示在浏览器的界面上.

猜你喜欢

转载自blog.csdn.net/qq_53869058/article/details/131406144