Servlet工作原理解析(二)「建议收藏」

2022-11-09 16:56:16 浏览数 (1)

上一篇文章简单介绍了Servlet容器的启动、初始化的过程,(链接在这里Servlet工作原理解析(一)),接下来介绍Servlet的创建以及实例化。

一:创建Servlet对象

如果Servlet的load-on-startup配置项大于0,那么在Context容器启动时就会被实例化。

前面提到的在解析配置文件时会读取默认的globalWebXml,在conf下的web.xml文件中定义了一些默认的配置项,其中定义了两个Servlet

分别是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它们的load-on-startup分别是1和3,也就是

当tomcat启动时这两个servlet就会被启动。

创建Servlet实例的方式是从Wrapper.loadServlet开始的,loadServlet方法要完成的就是获取servletClass,然后把它交给Instance

Manager去创建一个基于servletClass.class的对象。如果这个Servlet配置了jsp-file,那么这个servletClass就是在conf/web.xml中定义的

org.apache.jasper.servlet.JspServlet。

二:初始化Servlet

初始化Servlet在StandardWrapper的initServlet方法中,这个方法很简单,就是调用Servlet的init()方法,同时把包装了Standard

Wrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet(这里为什么不直接把StandardWrapper传给servlet,其实是

门面设计模式,避免把不必要的数据过分暴露给目标,这个后面会介绍到)。

如果该Servlet关联的是一个JSP文件,那么前面初始化的就是JspServlet,接下来会模拟一次简单请求,请求调用这个JSP文件,以便编

译这个JSP文件为类,并初始化这个类。这样Servlet对象的初始化就完成了,事实上Servlet从背web.xml解析到初始化过程非常复杂,中间

有很多过程,包括各种容器发生的状态变化的引起的监听事件的触发、各种访问权限及异常的处理等。

三:Servlet体系结构

我们知道 Java Web 应用是基于 Servlet 规范运转的,那么 Servlet 本身又是如何运转的呢?为何要设计这样的体系结构。

从上图可以看出 Servlet 规范就是基于这几个类运转的,与 Servlet 主动关联的是三个类,分别是 ServletConfig、

ServletRequest 和 ServletResponse。这三个类都是通过容器传递给 Servlet 的,其中 ServletConfig 是在 Servlet 初始化

时就传给 Servlet 了,而后两个是在请求达到时调用 Servlet 时传递过来的。我们很清楚 ServletRequest 和 ServletResponse

在 Servlet 运行的意义,但是 ServletConfig 和 ServletContext 对 Servlet 有何价值?仔细查看 ServletConfig 接口中声明

的方法发现,这些方法都是为了获取这个 Servlet 的一些配置属性,而这些配置属性可能在 Servlet 运行时被用到。而 Servlet

Context 又是干什么的呢? Servlet 的运行模式是一个典型的“握手型的交互式”运行模式。所谓“握手型的交互式”就是两

个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这个交易过程直到这个交易完成为止。这个交易场景的

初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置类。所以对号入座,交易场景就由 Servlet

Context 来描述,而定制的参数集合就由 ServletConfig 来描述。而 ServletRequest 和 ServletResponse 就是要交互的具体

对象了,它们通常都是作为运输工具来传递交互结果。

ServletConfig 是在 Servlet init 时由容器传过来的,那么 ServletConfig 到底是个什么对象呢?下图是 ServletConfig

和 ServletContext 在 Tomcat 容器中的类关系图。

上图可以看出 StandardWrapper 和 StandardWrapperFacade 都实现了 ServletConfig 接口,而 StandardWrapper

Facade 是 StandardWrapper 门面类。所以传给 Servlet 的是 StandardWrapperFacade 对象,这个类能够保证从 Standard

Wrapper 中拿到 ServletConfig 所规定的数据,而又不把 ServletConfig 不关心的数据暴露给 Servlet。

同样 ServletContext 也与 ServletConfig 有类似的结构,Servlet 中能拿到的 ServletContext 的实际对象也是 Applicat

ionContextFacade 对象。ApplicationContextFacade 同样保证 ServletContex 只能从容器中拿到它该拿的数据,它们都起

到对数据的封装作用,它们使用的都是门面设计模式。

Servlet 中定义的两个 ServletRequest 和 ServletResponse 它们实际的对象又是什么呢?,我们在创建自己的 Servlet

类时通常使用的都是 HttpServletRequest 和 HttpServletResponse,它们继承了 ServletRequest 和 ServletResponse。

四:Servlet如何工作

我们已经清楚了 Servlet 是如何被加载的、Servlet 是如何被初始化的,以及 Servlet 的体系结构,现在的问题就是

它是如何被调用的。

当用户从浏览器向服务器发起一个请求,通常会包含如下信息:http://hostname: port /contextpath/servletpath,

hostname 和 port 是用来与服务器建立 TCP 连接,而后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。

那服务器是如何根据这个 URL 来达到正确的 Servlet 容器中的呢?Tomcat7.0 中这件事很容易解决,因为这种映射工作有专

门一个类来完成的,这个就是 org.apache.tomcat.util.http.mapper,这个类保存了 Tomcat 的 Container 容器中的所有子

容器的信息,当 org.apache.catalina.connector. Request 类在进入 Container 容器之前,mapper 将会根据这次请求的

hostnane 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。

所以当 Request进入Container 容器之前,它要访问那个子容器这时就已经确定了。可能你有疑问,mapper 中怎么会有容器的完整关系,这要回到Tomcat启动时序图 中 19 步 MapperListener 类的初始化过程,下面是 MapperListener 的 init 方法代码 :

代码语言:javascript复制
public void init() {   
        findDefaultHost();   
        Engine engine = (Engine) connector.getService().getContainer();   
        engine.addContainerListener(this);   
        Container[] conHosts = engine.findChildren();   
        for (Container conHost : conHosts) {   
            Host host = (Host) conHost;   
            if (!LifecycleState.NEW.equals(host.getState())) {   
                host.addLifecycleListener(this);   
                registerHost(host);   
            }   
        }   
 }  

这段代码的作用就是将 MapperListener 类作为一个监听者加到整个 Container 容器中的每个子容器中,这样只要任何 一个容器发生变化,MapperListener 都将会被通知,相应的保存容器关系的 MapperListener 的 mapper 属性也会修改。

for 循环中就是将 host 及下面的子容器注册到 mapper 中。 下图为Requset在容器中的路由图:

Request决定好访问哪个容器的哪个Serlvet之后,接下去就要执行 Servlet 的 service 方法了,通常情况下,我们自己定

义的 servlet 并不是直接去实现 javax.servlet.servlet 接口,而是去继承更简单的 HttpServlet 类或者 GenericServlet 类,我

们可以有选择的覆盖相应方法去实现我们要完成的工作。

Servlet 的确已经能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet 来实现,

而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet,然后去实现 serv

ice 方法,这个方法也就是 MVC 框架的入口(目前非常流行的SpringMVC,以后以后介绍到)。

当 Servlet 从 Servlet 容器中移除时,也就表明该 Servlet 的生命周期结束了,这时 Servlet 的 destroy 方法将被调用,

做一些扫尾工作。

本篇文章介绍了Servlet对象的创建以及初始化、servlet的体系结构以及Servlet是如何工作的,关于Servlet还有两个Listener、

Filter两个组件将在下篇文章中介绍。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/186071.html原文链接:https://javaforall.cn

0 人点赞