JavaWeb~Servlet~基本使用、smart Tomcat插件、Servlet工作原理深入了解、Tomcat初始化和处理请求的核心逻辑

Servlet的基本使用

步骤
1.IDEA创建一个maven项目
2.在中央仓库引入依赖~
3.创建目录结构 webapp/WEB-INF/web.xml
4.编写代码
1)创建一个类,继承自HttpServlet
2) 重写HttpServlet doGet/doPost方法
3)在方法里面根据请求计算响应,直接在响应对象中构造一个"hello world"
4)给类上面加一个注解@WebServlet,把这个类和一个具体的HTTP请求的路径关联起来~
5.打包,使用maven
使用packaging标签修改打包类型,build标签finalName标签指定打包后的名称
6.部署,把war包拷贝到Tomcat的webapps目录中
Tomcat会自动的对这个war包进行解压缩,得到一个同名目录
7.验证
http://服务器的ip:8080/ContextPath/[WebServlet注解中描述的路径]

smart Tomcat插件

插件 plugin.
计算机中有很多的程序,都是基于“插件体系”

最早的知名的插件体系的程序 编辑器中的Vim
和Emacs搭配一些额外的插件
包括知名的eclipse.
现在的eclipse 只是一个“平台”,搭配上不同的插件之后,就可以成为Java开发工具/C++开发工具等

为了更好地快速完成Servlet程序的部署,就有一个插件可以完成-----即Smart Tomcat
安装smart Tomcat
在这里插入图片描述
在弹出的窗口中点击Plugins(插件),然后搜索smart Tomcat,点击Install,进行下载安装。
在这里插入图片描述
当idea中出现如下日志时,就表明插件运行成功。
在这里插入图片描述

Smart Tomcat这个插件的运行原理,是临时创建了一个单独的目录,将目前的代码拷贝了一个临时副本过去,让Tomcat运行,因此在原始的webapps目录中,看不到war包。

常见问题

部署程序到Tomcat后,我们进行访问时,可能会出现以下情况:
1.出现404
可能原因:路径URl输入错误。
2.出现405
可能原因:请求方法和代码中重写方法不匹配。
3.出现500
可能原因:内部服务器代码运行出现异常。
4.出现空白页面
5.无法访问该网站。

Servlet工作原理深入了解

当Web服务器接收到一个HTTP请求时,它会先判断请求内容,如果是静态网页数据,Web服务器会自行处理,如果是动态数据内容,Web服务器会将请求转交给Tomcat(也就是其中的Servlet),然后Servlet会找到对应的实例来处理该请求,并将结果返回给Web服务器,再由Web服务器返回给客户端。

对于一个相同 的Sevlet,Tomcat会在第一次收到HTTP请求时建立一个Servlet实例,然后创建一个线程,当第二次收到该HTTP请求的时候,这个时候不会再建立相同的Servlets实例,而是启动第二个线程来服务客户端的 请求。

所以Servlet是运行在多线程的环境中的。多线程的方式不仅可以提高Web应用程序的执行效率,也可以降低Web服务器的系统负担。
在这里插入图片描述

客户端与服务器的详细交互过程:

在这里插入图片描述
处理请求过程:
当用户在浏览器中输入URl,浏览器就会构造一个HTTP请求的报文,然后该报文就会经过网络协议栈依次封装,经过传输层,套上TCP报头,经过网络层,套上ip报头,经过数据链路层,套上以太网数据帧的帧头和帧尾,经过物理层,转化为0101的光电信号,然后通过网络进行转发和传输,最终到达服务器的物理层,然后经过物理层,将光电信号转换为数字信号,然后到达数据链路层,解析以太网数据帧,取出里面的载核,然后经过网络层,经过ip协议进行解析,取出载核,然后在传输层经过TCP协议解析,然后在应用层得到一个HTTP请求的报文,然后Tomcat进行解析请求,生成一个HttpServletRequest对象,然后根据对象中的ContextPath信息,确定是交给哪个Web应用,然后根据ServletPath,确定交给哪一个类,然后根据报文中的方法确定执行哪个方法,最终执行到具体的代码。

伪代码描述Tomcat核心逻辑

伪代码:大概的表示某种逻辑,语法不一定严谨。

Tomcat初始化流程


//假设Tomcat就是一个类
class Tomcat {
    
    

 // 用来存储所有的 Servlet 对象
 //首先会用一个List来记录所有的Servlet实例,我们自己写类时都会重写doGet或者doPost等方法,这些方法的调用就是依靠这些实例,这里用数组来储存这些Servlet实例
 private List<Servlet> instanceList = new ArrayList<>();
 
  //Tomcat启动时就会调用这个start方法
 public void start() {
    
    
       // 根据约定,读取 WEB-INF/web.xml 配置文件;
        // 并解析被 @WebServlet 注解修饰的类
       
        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 
        //数组来储存所有的Servlet类对象,这些类对象就是webapps目录下的配置文件产生,也有根据我们自己写的类所生成的对象。
        Class<Servlet>[] allServletClasses = ...;
        //以上就是类的加载 的过程

  
     //加载完成后,就要对所有的类进行实例化,这里是通过反射来完成的。
        
        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
    
    
            // 这里是利用 java 中的反射特性做的
           // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
            
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
       }
        

        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
    
    
            ins.init();
       }
        
        // 利用我们之前学过的知识,启动一个 HTTP 服务器
        // 并用线程池的方式分别处理每一个 Request
    //创建Socket绑定端口号,开始启动服务器
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);
     
        
        //循环调用accept方法,每次accept返回的结果都会放到线程池里面,用doHttpRequest处理请求。
        while (true) {
    
    
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
    
    
               doHttpRequest(socket); 
           });
       }

        
        //Servlet开始销毁,调用destroy方法
        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
    
    
            ins.destroy();
       }
   }
    
    public static void main(String[] args) {
    
    
        new Tomcat().start();
   }
}

这里只是伪代码,一些大体流程,真实的Tomcat执行流程会比这个逻辑更复杂一点。

总结:

  • Tomcat中内置了main方法,我们启动 Tomcat的时候,是从Tomcat的main方法开始执行
  • 首先会进行类的加载,在加载的过程中,会获取到被@WebServlet注解修饰的类,也就是我们自己写的类,这些类会被集中管理
  • 然后进行类的实例化,通过反射的方式来创建。
  • 创建实例后,会调用其中的init方法进行初始化,这个方法是HttpServlet自带的方法。
  • 然后会循环用accpet将其放入线程中,doHttpReuqest处理请求。
  • 最后实例销毁,在销毁之前,会调用dextory方法进一步收尾。

Servlet本身是运行在多线程的环境下的。

Tomcat处理请求

class Tomcat {
    
    
    void doHttpRequest(Socket socket) {
    
    
        // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket);
        HttpServletRequest resp = HttpServletRequest.build(socket);
        
        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态
内容
        // 直接使用我们学习过的 IO 进行内容输出
        if (file.exists()) {
    
    
            // 返回静态内容
            return;
       }
        
        // 走到这里的逻辑都是动态内容了
        
        // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
        // 最终找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL());
        
        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
    
    
       ins.service(req, resp); 
       } catch (Exception e) {
    
    
            // 返回 500 页面,表示服务器内部错误
       }
   }
}

总结:

  • Tomcat会从Socket中读取Http请求,此时读取到的请求是一个字符串,然后会按照HTTP协议的格式解析成一个HttpServletRequest对象。
  • Tomcat会根据URL中的路径path判断当前的请求是动态还是静态的。如果是静态,会直接在本地找到对应的文件内容通过Socket返回,如果是动态的,会执行Servlet相关的逻辑。
  • 然后根据URL中的ContextPathServletPath确定要调用哪个Servlet实例的service方法。
  • 调用service方法,开始处理请求。

service方法就是我们自己定义的类和其中重写的doGet或者doPost等方法。
逻辑如下:

class Servlet {
    
    
    public void service(HttpServletRequest req, HttpServletResponse resp) {
    
    
        String method = req.getMethod();
        if (method.equals("GET")) {
    
    
            doGet(req, resp);
       } else if (method.equals("POST")) {
    
    
            doPost(req, resp);
       } else if (method.equals("PUT")) {
    
    
            doPut(req, resp);
       } else if (method.equals("DELETE")) {
    
    
            doDelete(req, resp);
       } 
       ......
   }
}

这里调用doXXX方法的时候,
触发了 多态机制.我们自己写的类,都是继承了HttpServlet类,而HttpServlet类又是继承了Servlet,所以我们自己写的类就是Servlet的子类。
这就满足了多态的继承关系。
在Tomcat启动时,Tomcat会根据注解的描述,创建我们所写的类的实例,然后将其放入Servlet数组中集中管理。我们通过URL从数组中拿出该实例时,是通过servlet ins这样的父类引用获取到的。然后通过ins.doGet的方式调用方法的时候。
这就满足了 多态的 父类引用指向子类对象这一条件。

Guess you like

Origin blog.csdn.net/Merciful_Lion/article/details/123561377