1.Tomcat如何调用Servlet
1.1Servlet原理
Servlet是属于上层建筑,它处在应用层,它的下层有传输层,网络层,数据链路层,硬件,属于“经济基础”,毕竟下层经济基础决定上层建筑。前面说过,Servlet是一组操作HTTP的API,Tomcat可作为HTTP服务器来处理请求,这个处理请求的关键就是调用Servlet来操作HTTP给客户端做出响应。
我们所写的Servlet代码没有main方法,那他是如何运行的呢?其实是Tomcat在调用Servlet,Tomcat其实就是一个应用程序,是运行在用户态上的一个普通的Java进程。
当浏览器发送请求给服务器的时候,Tomcat作为HTTP Server会调用Serlvet API,然后执行我们所写的Servlet程序来处理请求。
处理请求的过程中牵涉的不仅仅只有HTTP,还有其他层的协议,但是我们并没有感知到其他层协议的细节,只关注了应用层HTTP协议的细节,这就是协议分层好处,程序员在实现处理请求时,不必去关心应用层下面的细节。
1.2Tomcat的执行逻辑
为了方便描述Tomcat的执行逻辑,我们使用伪代码的形式来分析:
初始化与收尾工作,又细分为以下几部分:
1)从指定的目录中找到Servlet类,并加载。
2根据加载的结果,给这些类型创建实例。
3)创建好实例后,调用Servlet对象中的 init 方法。
4)创建TCP socket对象,监听8080端口,等待客户端来连接。
5)如果请求处理完毕,也就是处理请求的循环退出了,那Tomcat也结束了,调用 destroy 方法结束进程,但是这个环节不一定可靠,正常退出的情况下,需要在管理端口(8005)去调用 destroy ,将Tomcat关闭,但很多时候都是直接杀死进程来达到关闭的目的,此时根本来不及调用 dsetroy 方法。
class Tomcat { // 用来存储所有的 Servlet 对象 private List<Servlet> instanceList = new ArrayList<>(); public void start() { // 根据约定,读取 WEB-INF/web.xml 配置文件; // 并解析被 @WebServlet 注解修饰的类 // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 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 ServerSocket serverSocket = new ServerSocket(8080); // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况 ExecuteService pool = Executors.newFixedThreadPool(100); while (true) { Socket socket = ServerSocket.accept(); // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的 pool.execute(new Runnable() { doHttpRequest(socket);//处理请求 }); } // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次; for (Servlet ins : instanceList) { ins.destroy(); } } public static void main(String[] args) { new Tomcat().start(); }}
Tomcat处理请求工作:
1)读取socket中的数据,并按照HTTP协议的格式来进行解析,获取请求。
2)判断请求是需要静态内容还是动态内容,如果是静态内容,可以在根路径上找到目的文件,返回请求
3)如果是动态文件,则需要通过URL上的一级路径与二级路径来确定通过哪一个Servlet类来进行处理,没有的话就会返回404
4)找到对应Servlet对象,调用对象里面的 service 方法,根据请求的方法来调用对应的 do... 方法
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 页面,表示服务器内部错误 } }}
service方法执行逻辑:
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); } ...... }}
在整个流程中,有三个关键的方法:
init
2.Servlet中关键的几个API
2.1常用方法列举
HttpServlet关键方法:
方法名称 |
调用时机 |
init</ |