A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

Servlet


Servlet是一种小型的Java程序,它扩展了Web服务器的功能。作为一种服务器端的应用,当被请求时开始执行。Servlet提供的功能大多与JSP类似,不过实现的方式不同。JSP通常是大多数HTML代码中嵌入少量的Java代码,而servlets全部由Java写成并且生成HTML。



Servlet 工作原理解析
从 Servlet 容器说起
[size=1.0625]要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。从技术角度来说是为了解耦,通过标准化接口来相互协作。既然接口是连接 Servlet 与 Servlet 容器的关键,那我们就从它们的接口说起。
[size=1.0625]前面说了 Servlet 容器作为一个独立发展的标准化产品,目前它的种类很多,但是它们都有自己的市场定位,很难说谁优谁劣,各有特点。例如现在比较流行的 Jetty,在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理 Servlet。Tomcat 本身也很复杂,我们只从 Servlet 与 Servlet 容器的接口部分开始介绍,关于 Tomcat 的详细介绍可以参考我的另外一篇文章《 Tomcat 系统架构与模式设计分析》。
[size=1.0625]Tomcat 的容器等级中,Context 容器是直接管理 Servlet 在容器中的包装类 Wrapper,所以 Context 容器如何运行将直接影响 Servlet 的工作方式。
图 1 . Tomcat 容器模型
[size=1.0625]从上图可以看出 Tomcat 的容器分为四个等级,真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程,在 Tomcat 的配置文件中可以很容易发现这一点,如下:
清单 1 Context 配置参数
[size=0.875]1

[size=0.875]2

[size=0.875][size=0.875]<Context path="/projectOne " docBase="D:\projects\projectOne"
[size=0.875]reloadable="true" />




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

[size=0.875]2

[size=0.875]3

[size=0.875]4

[size=0.875]5

[size=0.875]6

[size=0.875]7

[size=0.875][size=0.875]Tomcat tomcat = getTomcatInstance();
[size=0.875]File appDir = new File(getBuildDirectory(), "webapps/examples");
[size=0.875]tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
[size=0.875]tomcat.start();
[size=0.875]ByteChunk res = getUrl("http://localhost:" + getPort() +
[size=0.875]              "/examples/servlets/servlet/HelloWorldExample");
[size=0.875]assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);




[size=1.0625]清单 1 的代码是创建一个 Tomcat 实例并新增一个 Web 应用,然后启动 Tomcat 并调用其中的一个 HelloWorldExample Servlet,看有没有正确返回预期的数据。
[size=1.0625]Tomcat 的 addWebapp 方法的代码如下:
清单 3 .Tomcat.addWebapp
[size=0.875]1

[size=0.875]2

[size=0.875]3

[size=0.875]4

[size=0.875]5

[size=0.875]6

[size=0.875]7

[size=0.875]8

[size=0.875]9

[size=0.875]10

[size=0.875]11

[size=0.875]12

[size=0.875]13

[size=0.875]14

[size=0.875]15

[size=0.875]16

[size=0.875]17

[size=0.875]18

[size=0.875]19

[size=0.875]20

[size=0.875][size=0.875]public Context addWebapp(Host host, String url, String path) {
[size=0.875]       silence(url);
[size=0.875]       Context ctx = new StandardContext();
[size=0.875]       ctx.setPath( url );
[size=0.875]       ctx.setDocBase(path);
[size=0.875]       if (defaultRealm == null) {
[size=0.875]           initSimpleAuth();
[size=0.875]       }
[size=0.875]       ctx.setRealm(defaultRealm);
[size=0.875]       ctx.addLifecycleListener(new DefaultWebXmlListener());
[size=0.875]       ContextConfig ctxCfg = new ContextConfig();
[size=0.875]       ctx.addLifecycleListener(ctxCfg);
[size=0.875]       ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
[size=0.875]       if (host == null) {
[size=0.875]           getHost().addChild(ctx);
[size=0.875]       } else {
[size=0.875]           host.addChild(ctx);
[size=0.875]       }
[size=0.875]       return ctx;
[size=0.875]}




[size=1.0625]前面已经介绍了一个 Web 应用对应一个 Context 容器,也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会创建一个 StandardContext 容器,并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径,这个两个参数与清单 1 中的两个参数是一致的。其中最重要的一个配置是 ContextConfig,这个类将会负责整个 Web 应用配置的解析工作,后面将会详细介绍。最后将这个 Context 容器加到父容器 Host 中。
[size=1.0625]接下去将会调用 Tomcat 的 start 方法启动 Tomcat,如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat 的启动逻辑,Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle 接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《 Tomcat 的系统架构与设计模式,第二部分:设计模式》。Tomcat 启动的时序图可以用图 2 表示。
图 2. Tomcat 主要类的启动时序图(查看大图
[size=1.0625]上图描述了 Tomcat 启动过程中,主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启动过程。
[size=1.0625]当 Context 容器初始化状态设为 init 时,添加在 Contex 容器的 Listener 将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用清单 3 时被加入到 StandardContext 容器中。ContextConfig 类会负责整个 Web 应用的配置文件的解析工作。
[size=1.0625]ContextConfig 的 init 方法将会主要完成以下工作:
  • 创建用于解析 xml 配置文件的 contextDigester 对象
  • 读取默认 context.xml 配置文件,如果存在解析它
  • 读取默认 Host 配置文件,如果存在解析它
  • 读取默认 Context 自身的配置文件,如果存在解析它
  • 设置 Context 的 DocBase
[size=1.0625]ContextConfig 的 init 方法完成后,Context 容器的会执行 startInternal 方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:
  • 创建读取资源文件的对象
  • 创建 ClassLoader 对象
  • 设置应用的工作目录
  • 启动相关的辅助类如:logger、realm、resources 等
  • 修改启动状态,通知感兴趣的观察者(Web 应用的配置)
  • 子容器的初始化
  • 获取 ServletContext 并设置必要的参数
  • 初始化“load on startup”的 Servlet
Web 应用的初始化工作
[size=1.0625]Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始化主要是要解析 web.xml 文件,这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口。
[size=1.0625]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 的支持。
[size=1.0625]接下去将会将 WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener 等等。这段代码在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代码片段:
清单 4. 创建 Wrapper 实例
[size=0.875]1

[size=0.875]2

[size=0.875]3

[size=0.875]4

[size=0.875]5

[size=0.875]6

[size=0.875]7

[size=0.875]8

[size=0.875]9

[size=0.875]10

[size=0.875]11

[size=0.875]12

[size=0.875]13

[size=0.875]14

[size=0.875]15

[size=0.875]16

[size=0.875]17

[size=0.875]18

[size=0.875]19

[size=0.875]20

[size=0.875]21

[size=0.875]22

[size=0.875]23

[size=0.875]24

[size=0.875]25

[size=0.875]26

[size=0.875]27

[size=0.875]28

[size=0.875]29

[size=0.875]30

[size=0.875]31

[size=0.875]32

[size=0.875]33

[size=0.875]34

[size=0.875]35

[size=0.875]36

[size=0.875]37

[size=0.875]38

[size=0.875]39

[size=0.875]40

[size=0.875]41

[size=0.875]42

[size=0.875]43

[size=0.875]44

[size=0.875]45

[size=0.875]46

[size=0.875]47

[size=0.875]48

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




[size=1.0625]这段代码清楚的描述了如何将 Servlet 包装成 Context 容器中的 StandardWrapper,这里有个疑问,为什么要将 Servlet 包装成 StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。
[size=1.0625]除了将 Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中,其它的所有 web.xml 属性都被解析到 Context 中,所以说 Context 容器才是真正运行 Servlet 的 Servlet 容器。一个 Web 应用对应一个 Context 容器,容器的配置属性由应用的 web.xml 指定,这样我们就能理解 web.xml 到底起到什么作用了。

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马