开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详 大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Spring系列框架、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流
作者简介:
- CSDN java领域新星创作者blog.csdn.net/bug..
- 掘金LV3用户 juejin.cn/user/bug..
- 阿里云社区专家博主,星级博主,developer.aliyun.com/bug..
- 华为云云享专家 bbs.huaweicloud.com/bug..
Servlet运行原理
我们的Servlet代码连一个main方法都没有是怎么运行呢?
这里我们需要了解一下tomcat帮我们做的工作和处理机制!
tomcat定位
我们知道tomcat就是一个http服务器,而http是用户层协议!
所以我们的Servlet代码是基于tomcat 运行的!
当用户在浏览器中发送请求后,
tomcat作为应用层服务器就可以接这个请求,而http只是一个应用层协议,需要通过其他层协议协助传输!这里的传输过程也是要经过5层协议进行封装分用,这里和之前的一样!
我们分析一下上述流程
- 接收请求
我们浏览器客户端发送一个请求,然后用户的请求参数随着查询字符串或者body构造了一个
http请求然后到达了用户层,用户层协议就是http,然后调用操作系统内核下的,socketapi发送到网络层,网络层加上TCP报头,到达传输层加上IP协议报头,然后就传输到了数据链路层,加上帧头帧尾,最后到达物理层调用网卡设备将这些信息转换成光信号或者高低电平,通过网络设备传输到达服务器主机,服务器主机通过网卡接收到这一组信号解析成以太网数据帧,进行分用!层层解析最后解析成一个http请求并交给tomcat进程进行处理!tomcat拿到http协议报(字符串)按照协议报格式进行解析,根据ContentPath路径确定webapp,在通过ServletPath确定具体的类,根据请求的方法,决定调用doGET/POST方法,此时我们的HttpServletResquest对象就包含了这个请求的详细信息!
- 根据请求处理响应
我们通过
HttpServletRequest中的请求信息,计算相应的响应信息,通过HttpServletResponse这个对象,存放响应信息!比如我们可以设置一些响应的状态码,body字段等!
- 返回响应
我们的
doGet/doPost执行结束后,就会自动把HttpServletResponse以及我们已经设置的一些属性转换成相应的http响应,通过socket发送!后面的过程就是网络传输层层分用分装的过程,最后将响应中的body信息展现在浏览器上给用户!
tomcat伪代码
通过下面tomcat伪代码,了解tomcat初始化/接收请求两部分核心内容!
tomcat初始化流程
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初始化
我们看到这里
tomcat其实是有main方法的,tomcat启动就从main方法开始! 启动后就会将@webServlet标记的类获取到,这些类已经是.class文件,需要通过反射机制创建好对应的实例,这些实例创建好就会调用init方法进行初始化,这个方法在HttpServlet类中,我们也可以重写这个方法! 这些请求处理业务完成后,就会将这些实例销毁调用其destroy方法,这里的方法也是在HttpServlet类中,我们也可以进行重写! 我们可以看到tomcat的内部也是调用操作系统中的socket进行网络通信的! 还有这里tomcat需要处理多个htttp请求,这里采取了多线程的方式,Servlet运行在多线程状态下的!
- 处理请求流程
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通过调用socketapi然后获取到http请求,然后将请求按照http协议报的格式解析成HttpServlet对象,然后通过url中的资源目录,获取到对应的ContentPath和Servlet路径获取到对应的文件,如果是静态资源就直接通过socket返回给客户端,如果是动态资源就会调用HttpServlet下的service方法,通过这个方法就可以调用对应的doGET/doPOST处理请求,然后再将计算对应的响应,最后返回!
Servlet的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);
}
......
}
}
这里就是根据对应的请求调用对应的请求处理方法,这里是通过多态的机制处理!