servlet的基本原理_简述arp的工作原理

servlet的基本原理_简述arp的工作原理Web技术成为当今主流的互联网Web应用技术之一,而Servlet是JavaWeb技术的核心基础。因而掌握Servlet的工作原理是成为一名合格的JavaWeb技术开发人员的基本要求。本文将带你认识JavaWeb技术是如何基于Servlet工作,以Tomcat为例了解Servlet容器是如何工作的?一个Web工程在Servlet容器中是如何启动的?

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

Web技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础。因而掌握 Servlet的工作原理是成为一名合格的 Java Web 技术开发人员的基本要求。本文将带你认识 Java Web 技术是如何基于 Servlet工作,以 Tomcat 为例了解 Servlet 容器是如何工作的?一个 Web 工程在 Servlet 容器中是如何启动的?Servlet 容器如何解析你在 web.xml 中定义的 Servlet ?用户的请求是如何被分配给指定的 Servlet 的?Servlet 容器如何管理 Servlet 生命周期?Servlet 的 API 的类层次结构,以及 Servlet中一些难点问题的分析。

从 Servlet 容器说起

要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。从技术角度来说是为了解耦,通过标准化接口来相互协作。既然接口是连接Servlet 与 Servlet 容器的关键,那我们就从它们的接口说起。

前面说了 Servlet容器作为一个独立发展的标准化产品,目前它的种类很多,但是它们都有自己的市场定位,很难说谁优谁劣,各有特点。例如现在比较流行的Jetty,在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理Servlet。Tomcat 本身也很复杂,我们只从 Servlet 与 Servlet 容器的接口部分开始介绍,关于 Tomcat的详细介绍可以参考我的另外一篇文章《 Tomcat 系统架构与模式设计分析》。

Tomcat 的容器等级中,Context 容器是直接管理 Servlet 在容器中的包装类 Wrapper,所以 Context容器如何运行将直接影响 Servlet 的工作方式。

图 1 . Tomcat 容器模型

清单 1Context 配置参数

 Context path=”/projectOne “docBase=”D:\projects\projectOne”
reloadable=”true”

下面详细介绍一下Tomcat 解析 Context 容器的过程,包括如何构建 Servlet 的过程。

Servlet容器的启动过程

Tomcat7也开始支持嵌入式功能,增加了一个启动类 org.apache.catalina.startup.Tomcat。创建一个实例对象并调用start 方法就可以很容易启动 Tomcat,我们还可以通过这个对象来增加和修改 Tomcat 的配置参数,如可以动态增加Context、Servlet 等。下面我们就利用这个 Tomcat 类来管理新增的一个 Context 容器,我们就选择Tomcat7 自带的 examples Web 工程,并看看它是如何加到这个 Context容器中的。

清单 2 . 给Tomcat 增加一个 Web 工程

  Tomcattomcat = getTomcatInstance();
 File appDir = newFile(getBuildDirectory(), “webapps/examples”);
 tomcat.addWebapp(null, “/examples”,appDir.getAbsolutePath());
 tomcat.start();
 ByteChunk res = getUrl(“
http://localhost:” + getPort()+          “/examples/servlets/servlet/HelloWorldExample”);
 assertTrue(res.toString().indexOf(“HelloWorld!“) > 0);



清单2的代码是创建一个 Tomcat 实例并新增一个 Web 应用,然后启动 Tomcat 并调用其中的一个HelloWorldExample Servlet,看有没有正确返回预期的数据。

Tomcat 的addWebapp 方法的代码如下:

清单 3.Tomcat.addWebapp

public Context addWebapp(Host host, String url,String path) {

       silence(url);

       Context ctx = new StandardContext();

       ctx.setPath( url );

       ctx.setDocBase(path);

       if (defaultRealm == null) {

           initSimpleAuth();

       }

       ctx.setRealm(defaultRealm);

       ctx.addLifecycleListener(new DefaultWebXmlListener());

       ContextConfig ctxCfg = new ContextConfig();

       ctx.addLifecycleListener(ctxCfg);

       ctxCfg.setDefaultWebXml(“org/apache/catalin/startup/NO_DEFAULT_XML”);

       if (host == null) {

           getHost().addChild(ctx);

       } else {

           host.addChild(ctx);

       }

       return ctx;

 }

前面已经介绍了一个Web 应用对应一个 Context 容器,也就是 Servlet 运行时的 Servlet 容器,添加一个 Web应用时将会创建一个 StandardContext 容器,并且给这个 Context 容器设置必要的参数,url 和 path分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径,这个两个参数与清单 1中的两个参数是一致的。其中最重要的一个配置是 ContextConfig,这个类将会负责整个 Web应用配置的解析工作,后面将会详细介绍。最后将这个 Context 容器加到父容器 Host 中。

接下去将会调用Tomcat 的 start 方法启动 Tomcat,如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat的启动逻辑,Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《Tomcat 的系统架构与设计模式,第二部分:设计模式》。Tomcat 启动的时序图可以用图 2表示。

图 2.Tomcat 主要类的启动时序图

当Context 容器初始化状态设为 init 时,添加在 Contex 容器的 Listener将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用清单 3 时被加入到StandardContext 容器中。ContextConfig 类会负责整个 Web应用的配置文件的解析工作。

ContextConfig的 init 方法将会主要完成以下工作:

创建用于解析xml 配置文件的 contextDigester 对象

读取默认context.xml 配置文件,如果存在解析它

读取默认Host 配置文件,如果存在解析它

读取默认Context 自身的配置文件,如果存在解析它

设置Context 的 DocBase

ContextConfig的 init 方法完成后,Context 容器的会执行 startInternal方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:

创建读取资源文件的对象

创建ClassLoader 对象

设置应用的工作目录

启动相关的辅助类如:logger、realm、resources等

修改启动状态,通知感兴趣的观察者(Web应用的配置)

子容器的初始化

获取ServletContext 并设置必要的参数

初始化“loadon startup”的 Servlet

Web应用的初始化工作

Web应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始化主要是要解析web.xml 文件,这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口。

Tomcat首先会找 globalWebXml 这个文件的搜索路径是在 engine 的工作目录下寻找以下两个文件中的任一个org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。接着会找hostWebXml 这个文件可能会在System.getProperty(“catalina.base”)/conf/${EngineName}/${HostName}/web.xml.default,接着寻找应用的配置文件examples/WEB-INF/web.xml。web.xml 文件中的各个配置项将会被解析成相应的属性保存在 WebXml对象中。如果当前应用支持 Servlet3.0,解析还将完成额外 9 项工作,这个额外的 9 项工作主要是为 Servlet3.0新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及对 annotations的支持。

接下去将会将WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener等等。这段代码在 WebXml 的 configureContext 方法中。下面是解析 Servlet的代码片段:

清单 4. 创建Wrapper 实例

for (ServletDef servlet : servlets.values()) { 
            Wrapper wrapper = context.createWrapper(); 
            String jspFile = servlet.getJspFile(); 
            if (jspFile != null) { 
                wrapper.setJspFile(jspFile); 
            } 
            if (servlet.getLoadOnStartup() != null) { 
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); 
            } 
            if (servlet.getEnabled() != null) { 
                wrapper.setEnabled(servlet.getEnabled().booleanValue()); 
            } 
            wrapper.setName(servlet.getServletName()); 
            Map params = servlet.getParameterMap(); 
            for (Entry entry : params.entrySet()) { 
                wrapper.addInitParameter(entry.getKey(), entry.getValue()); 
            } 
            wrapper.setRunAs(servlet.getRunAs()); 
            Set roleRefs = servlet.getSecurityRoleRefs(); 
            for (SecurityRoleRef roleRef : roleRefs) { 
                wrapper.addSecurityReference( 
                        roleRef.getName(), roleRef.getLink()); 
            } 
            wrapper.setServletClass(servlet.getServletClass()); 
            MultipartDef multipartdef = servlet.getMultipartDef(); 
            if (multipartdef != null) { 
                if (multipartdef.getMaxFileSize() != null && 
                        multipartdef.getMaxRequestSize()!= null && 
                        multipartdef.getFileSizeThreshold() != null) { 
                    wrapper.setMultipartConfigElement(new 
 MultipartConfigElement( 
                            multipartdef.getLocation(), 
                            Long.parseLong(multipartdef.getMaxFileSize()), 
                            Long.parseLong(multipartdef.getMaxRequestSize()), 
                            Integer.parseInt( 
                                    multipartdef.getFileSizeThreshold()))); 
                } else { 
                    wrapper.setMultipartConfigElement(new 
 MultipartConfigElement( 
                            multipartdef.getLocation())); 
                } 
            } 
            if (servlet.getAsyncSupported() != null) { 
                wrapper.setAsyncSupported( 
                        servlet.getAsyncSupported().booleanValue()); 
            } 
            context.addChild(wrapper); 
 }

这段代码清楚的描述了如何将Servlet 包装成 Context 容器中的 StandardWrapper,这里有个疑问,为什么要将 Servlet 包装成StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat中。

除了将Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中,其它的所有 web.xml属性都被解析到 Context 中,所以说 Context 容器才是真正运行 Servlet 的 Servlet 容器。一个 Web应用对应一个 Context 容器,容器的配置属性由应用的 web.xml 指定,这样我们就能理解 web.xml到底起到什么作用了。

回页首

创建Servlet 实例

前面已经完成了Servlet 的解析工作,并且被包装成 StandardWrapper 添加在 Context容器中,但是它仍然不能为我们工作,它还没有被实例化。下面我们将介绍 Servlet对象是如何创建的,以及如何被初始化的。

创建Servlet 对象

如果Servlet 的 load-on-startup 配置项大于 0,那么在 Context容器启动的时候就会被实例化,前面提到在解析配置文件时会读取默认的 globalWebXml,在 conf 下的 web.xml文件中定义了一些默认的配置项,其定义了两个Servlet,分别是:org.apache.catalina.servlets.DefaultServlet 和org.apache.jasper.servlet.JspServlet 它们的 load-on-startup 分别是 1 和3,也就是当 Tomcat 启动时这两个 Servlet 就会被启动。

创建Servlet 实例的方法是从 Wrapper. loadServlet 开始的。loadServlet 方法要完成的就是获取servletClass 然后把它交给 InstanceManager 去创建一个基于 servletClass.class的对象。如果这个 Servlet 配置了 jsp-file,那么这个 servletClass 就是 conf/web.xml中定义的 org.apache.jasper.servlet.JspServlet 了。

创建Servlet 对象的相关类结构图如下:

图 3.创建 Servlet 对象的相关类结构

图 5.Servlet 顶层类关联图

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


接下去就要执行 Servlet 的 service 方法了,通常情况下,我们自己定义的 servlet 并不是直接去实现javax.servlet.servlet 接口,而是去继承更简单的 HttpServlet 类或者 GenericServlet类,我们可以有选择的覆盖相应方法去实现我们要完成的工作。

Servlet 的确已经能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet来实现,而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet,然后去实现service 方法,这个方法也就是 MVC 框架的入口。

当 Servlet 从 Servlet 容器中移除时,也就表明该 Servlet 的生命周期结束了,这时 Servlet 的destroy 方法将被调用,做一些扫尾工作。

——————————————————————————————————————————

Session 与 Cookie

前面我们已经说明了 Servlet 如何被调用,我们基于 Servlet 来构建应用程序,那么我们能从 Servlet获得哪些数据信息呢?

Servlet 能够给我们提供两部分数据,一个是在 Servlet 初始化时调用 init 方法时设置的ServletConfig,这个类基本上含有了 Servlet 本身和 Servlet 所运行的 Servlet容器中的基本信息。根据前面的介绍 ServletConfig 的实际对象是StandardWrapperFacade,到底能获得哪些容器信息可以看看这类提供了哪些接口。还有一部分数据是由ServletRequest 类提供,它的实际对象是 RequestFacade,从提供的方法中发现主要是描述这次请求的 HTTP协议的信息。所以要掌握 Servlet 的工作方式必须要很清楚 HTTP协议,如果你还不清楚赶紧去找一些参考资料。关于这一块还有一个让很多人迷惑的 Session 与 Cookie。

Session 与 Cookie 不管是对 Java Web 的熟练使用者还是初学者来说都是一个令人头疼的东西。Session 与Cookie的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷。然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用Cookie 来传递信息时,随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大,试想假如 Cookie 占用 200个字节,如果一天的 PV 有几亿的时候,它要占用多少带宽。所以大访问量的时候希望用 Session,但是 Session的致命弱点是不容易在多台服务器之间共享,所以这也限制了 Session 的使用。

不管 Session 和 Cookie 有什么不足,我们还是要用它们。下面详细讲一下,Session 如何基于 Cookie来工作。实际上有三种方式能可以让 Session 正常工作:

  1. 基于 URL Path Parameter,默认就支持
  2. 基于 Cookie,如果你没有修改 Context 容器个 cookies 标识的话,默认也是支持的
  3. 基于 SSL,默认不支持,只有 connector.getAttribute(“SSLEnabled”) 为 TRUE时才支持

第一种情况下,当浏览器不支持 Cookie 功能时,浏览器会将用户的 SessionCookieName 重写到用户请求的 URL参数中,它的传递格式如 /path/Servlet;name=value;name2=value2?Name3=value3,其中“Servlet;”后面的 K-V 对就是要传递的 Path Parameters,服务器会从这个Path Parameters 中拿到用户配置的 SessionCookieName。关于这个SessionCookieName,如果你在 web.xml 中配置 session-config 配置项的话,其cookie-config 下的 name 属性就是这个 SessionCookieName 值,如果你没有配置session-config 配置项,默认的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接着Request 根据这个 SessionCookieName 到 Parameters 拿到 Session ID 并设置到request.setRequestedSessionId 中。

请注意如果客户端也支持 Cookie 的话,Tomcat 仍然会解析 Cookie 中的 Session ID,并会覆盖 URL 中的Session ID。

如果是第三种情况的话将会根据 javax.servlet.request.ssl_session 属性值设置 SessionID。

有了 Session ID 服务器端就可以创建 HttpSession 对象了,第一次触发是通过 request.getSession() 方法,如果当前的 Session ID 还没有对应的 HttpSession对象那么就创建一个新的,并将这个对象加到 org.apache.catalina. Manager 的 sessions容器中保存,Manager 类将管理所有 Session 的生命周期,Session 过期将被回收,服务器关闭,Session将被序列化到磁盘等。只要这个 HttpSession 对象存在,用户就可以根据 Session ID来获取到这个对象,也就达到了状态的保持。

图 11.Session 相关类图


图 12.Session 工作的时序图


——————————————————————————————————————————

Servlet 中的 Listener

整个 Tomcat 服务器中 Listener 使用的非常广泛,它是基于观察者模式设计的,Listener 的设计对开发Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据。目前 Servlet 中提供了 5种两类事件的观察者接口,它们分别是:4 个 EventListeners类型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener和 2 个 LifecycleListeners类型的,ServletContextListener、HttpSessionListener。如下图所示:

图 13.Servlet 中的 Listener

总结

本文涉及到内容有点多,要把每个细节都说清楚,似乎不可能,本文试着从 Servlet 容器的启动到 Servlet 的初始化,以及Servlet的体系结构等这些环节中找出一些重点来讲述,目的是能读者有一个总体的完整的结构图,同时也详细分析了其中的一些难点问题,希望对大家有所帮助。

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

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/185845.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号