サーブレットの動作原理の分析

巴扎黑
リリース: 2017-04-30 10:01:16
オリジナル
1160 人が閲覧しました

Web テクノロジは、今日主流のインターネット Web アプリケーション テクノロジの 1 つとなっており、サーブレットは Java Web テクノロジの中核基盤です。したがって、サーブレットの動作原理を習得することは、資格のある Java Web テクノロジ開発者になるための基本要件です。この記事では、サーブレットに基づいて Java Web テクノロジがどのように機能するかを理解します。サーブレット コンテナがどのように機能するかを理解するには、Tomcat を例に挙げます。 Web プロジェクトはサーブレット コンテナでどのように開始されるのでしょうか?サーブレット コンテナは、web.xml で定義したサーブレットをどのように解析するのでしょうか?ユーザーリクエストは指定されたサーブレットにどのように割り当てられますか?サーブレット コンテナはサーブレットのライフ サイクルをどのように管理しますか?また、最新のサーブレット API のクラス階層と、サーブレットのいくつかの難しい問題の分析についても学びます。

サーブレットコンテナから始めましょう

サーブレットを紹介するには、まずサーブレット コンテナについて明確に説明する必要があります。サーブレットとサーブレット コンテナの関係は、銃と弾丸の関係に似ています。銃は弾丸のために作られており、弾丸が銃を致命的にします。それらは相互依存していますが、互いに独立して発展しており、それらはすべて工業生産に適応した結果です。技術的な観点から見ると、標準化されたインターフェイスを通じて相互に分離し、連携することになります。インターフェイスはサーブレットとサーブレット コンテナを接続するための鍵であるため、インターフェイスから始めましょう。

前述したように、サーブレット コンテナは独自に開発された標準製品です。現在、さまざまな種類がありますが、それぞれに独自の市場での位置付けがあり、それぞれに独自の特徴があります。たとえば、人気の Jetty は、カスタマイズとモバイルの分野で大きな進歩を遂げています。 ここでは、誰にとっても最も馴染みのある Tomcat を例として、サーブレット コンテナがどのようにサーブレットを管理するかを紹介します。 Tomcat 自体も非常に複雑です。ここでは、サーブレットとサーブレット コンテナ間のインターフェイスについてのみ説明します。Tomcat の詳細については、私の別の記事「Tomcat システム アーキテクチャとパターン設計分析」を参照してください。

Tomcat のコンテナ レベルでは、Context コンテナはコンテナ内のサーブレットを直接管理するラッパー クラス Wrapper であるため、Context コンテナの実行方法はサーブレットの動作方法に直接影響します。

図 1. Tomcat コンテナー モデル

图 1 . Tomcat 容器模型

上の図からわかるように、Tomcat のコンテナは 4 つのレベルに分かれており、実際にサーブレットを管理するコンテナは、次のように Web プロジェクトに対応する Context コンテナです。

リスト 1 コンテキスト構成パラメーター

 <Context path="/projectOne " docBase="D:\projects\projectOne" 
 reloadable="true" />
ログイン後にコピー

以下は、サーブレットの構築方法を含む、Tomcat が Context コンテナを解析するプロセスの詳細な紹介です。

サーブレットコンテナの起動処理

Tomcat7 では、組み込み関数のサポートも開始され、スタートアップ クラス org.apache.catalina.startup.Tomcat が追加されました。 Tomcat は、インスタンス オブジェクトを作成し、start メソッドを呼び出すことで簡単に起動できます。また、このオブジェクトを使用して、コンテキストやサーブレットなどを動的に追加するなど、Tomcat の構成パラメータを追加および変更することもできます。次に、この Tomcat クラスを使用して新しい Context コンテナを管理します。Tomcat7 に付属するサンプル Web プロジェクトを選択し、それがこの Context コンテナにどのように追加されるかを確認します。

リスト 2. Web プロジェクトを Tomcat に追加する

 Tomcat tomcat = getTomcatInstance(); 
 File appDir = new File(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("<h1>Hello World!</h1>") > 0);
ログイン後にコピー

リスト 1 のコードは、Tomcat インスタンスを作成して Web アプリケーションを追加し、次に Tomcat を起動して HelloWorldExample サーブレットの 1 つを呼び出して、予期したデータが正しく返されるかどうかを確認します。

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 コンテナに対応することをすでに紹介しました。Web アプリケーションを追加すると、StandardContext コンテナが作成され、Context コンテナに必要なパラメータが設定されます。 URL とパスはそれぞれアプリケーションを表します。Tomcat のアクセス パスとこのアプリケーションの実際の物理パスは、リスト 1 の 2 つのパラメーターと一致します。最も重要な構成は ContextConfig です。このクラスは Web アプリケーション構成全体の解析を担当します。これについては後で詳しく説明します。最後に、この Context コンテナを親コンテナの Host に追加します。

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

  图 2. Tomcat 主要类的启动时序图(查看大图)

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

  上图描述了 Tomcat 启动过程中,主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启动过程。

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

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

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


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


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


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


  5. 设置 Context 的 DocBase

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

  1. 创建读取资源文件的对象


  2. 创建 ClassLoader 对象


  3. 设置应用的工作目录


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


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


  6. 子容器的初始化


  7. 获取 ServletContext 并设置必要的参数


  8. 初始化“load on 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<String,String> params = servlet.getParameterMap(); 
            for (Entry<String, String> entry : params.entrySet()) { 
                wrapper.addInitParameter(entry.getKey(), entry.getValue()); 
            } 
            wrapper.setRunAs(servlet.getRunAs()); 
            Set<SecurityRoleRef> 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 到底起到什么作用了。


サーブレットインスタンスを作成します

サーブレットの解析作業は以前に完了しており、StandardWrapper にパッケージ化されて Context コンテナに追加されましたが、インスタンス化されていないため、まだ機能しません。以下では、Servlet オブジェクトがどのように作成され、どのように初期化されるかを紹介します。

サーブレットオブジェクトを作成する

サーブレットの起動時ロード設定項目が 0 より大きい場合、コンテキスト コンテナの起動時にインスタンス化されます。前述したように、web.xml にある設定ファイルの解析時にデフォルトの globalWebXml が読み取られます。 conf の下のファイル いくつかのデフォルト設定項目が定義されています。これらは 2 つのサーブレット、つまり org.apache.catalina.servlets.DefaultServlet と org.apache.jasper.servlet.JspServlet を定義します。それらの起動時の負荷はそれぞれ 1 と 3 です。つまり、これら 2 つのサーブレットは、Tomcat の起動時に起動されます。

サーブレット インスタンスを作成するメソッドは、Wrapper.loadServlet から始まります。 loadServlet メソッドが実行する必要があるのは、servletClass を取得し、それを InstanceManager に渡して、servletClass.class に基づいてオブジェクトを作成することです。このサーブレットが jsp ファイルで設定されている場合、この servletClass は conf/web.xml で定義された org.apache.jasper.servlet.JspServlet になります。

サーブレット オブジェクトを作成するための関連するクラス構造図は次のとおりです:

図 3. サーブレット オブジェクトを作成するための関連クラス構造

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

サーブレットを初期化します

StandardWrapper の initServlet メソッドでサーブレットを初期化する このメソッドは、サーブレットの init メソッドを呼び出すと同時に、StandardWrapper オブジェクトをラップする StandardWrapperFacade を ServletConfig としてサーブレットに渡すだけです。 Tomcat コンテナが StandardWrapperFacade を Servlet オブジェクトに渡す理由については、後で詳しく分析します。

サーブレットが jsp ファイルに関連付けられている場合、Jsp サーブレットは事前に初期化されます。次に、jsp ファイルをクラスにコンパイルしてクラスを初期化するために、jsp ファイルを呼び出す単純なリクエストがシミュレートされます。

このようにして、サーブレット オブジェクトが初期化されます。実際には、Web でのサーブレットの解析や、予期しないエラーが発生した場合の動作の判定などが行われます。ここでは、全体的なコンテキストを皆さんに提供するために、いくつかの重要なリンクのみに焦点を当てて説明します。

以下は、一部の詳細を省略した、このプロセスの完全なタイミング図です。

図 4. サーブレットの初期化のシーケンス図 (拡大画像を表示)

图 4. 初始化 Servlet 的时序图


サーブレットアーキテクチャ

Java Web アプリケーションがサーブレット仕様に基づいて実行されることはわかっていますが、サーブレット自体はどのように実行されるのでしょうか?なぜそのようなアーキテクチャを設計するのか。

図5.サーブレットの最上位クラスの関連付け図

图 5.Servlet 顶层类关联图

上の図からわかるように、サーブレットの仕様はこれらのクラスに基づいており、サーブレットに自動的に関連付けられる 3 つのクラス、つまり ServletConfig、ServletRequest、および ServletResponse があります。これら 3 つのクラスは、サーブレットが初期化されるときにコンテナを通じてサーブレットに渡され、最後の 2 つはリクエストの到着時にサーブレットが呼び出されるときに渡されます。サーブレット操作における ServletRequest と ServletResponse の重要性は明確に理解しましたが、サーブレットにとって ServletConfig と ServletContext の価値は何でしょうか? ServletConfig インターフェースで宣言されたメソッドを詳しく見ると、これらのメソッドはすべて、このサーブレットの一部の構成プロパティを取得するために使用されており、これらの構成プロパティはサーブレットの実行中に使用される可能性があることがわかります。そして、ServletContext は何をするのでしょうか?サーブレットの実行モードは、典型的な「ハンドシェイク対話型」実行モードです。いわゆる「ハンドシェイク型対話」とは、通常、2 つのモジュールがデータを交換するためにトランザクション シナリオを準備し、トランザクションが完了するまでこのシナリオがトランザクション プロセスに従うことを意味します。このトランザクション シナリオの初期化は、トランザクション オブジェクトによって指定されたパラメーターに基づいてカスタマイズされます。これらの指定されたパラメーターは通常、構成クラスです。そのとおりです。トランザクション シナリオは ServletContext によって記述され、カスタマイズされたパラメーター セットは ServletConfig によって記述されます。 ServletRequest と ServletResponse は、対話する特定のオブジェクトであり、通常、対話結果を提供するためのトランスポート ツールとして使用されます。

ServletConfig はサーブレットの初期化中にコンテナから渡されますが、ServletConfig とは正確には何でしょうか?

次の図は、Tomcat コンテナ内の ServletConfig と ServletContext のクラス関係図です。

図6. コンテナ内のServletConfigのクラス関連付け図

图 6. ServletConfig 在容器中的类关联图

上の図からわかるように、StandardWrapper と StandardWrapperFacade はどちらも ServletConfig インターフェイスを実装しており、StandardWrapperFacade は StandardWrapper ファサード クラスです。したがって、サーブレットに渡されるのは StandardWrapperFacade オブジェクトです。このクラスは、ServletConfig が気にしないデータをサーブレットに公開することなく、ServletConfig で指定されたデータを StandardWrapper から取得できるようにします。

同様に、ServletContext も ServletConfig と同様の構造を持ち、Servlet で取得できる ServletContext の実体も ApplicationContextFacade オブジェクトです。また、ApplicationContextFacade は、ServletContex がコンテナから取得すべきデータのみを取得できるようにします。これらはすべてデータのカプセル化に役割を果たし、すべてファサード デザイン パターンを使用します。

ServletContext を介して、アプリケーションの作業パス、コンテナがサポートするサーブレットの最小バージョンなど、Context コンテナ内の必要な情報を取得できます。

サーブレットで定義されている 2 つの ServletRequest と ServletResponse の実際のオブジェクトは何ですか?独自のサーブレット クラスを作成する場合、通常は ServletRequest と ServletResponse を継承する HttpServletRequest と HttpServletResponse を使用します。 Context コンテナによって渡された ServletRequest と ServletResponse が HttpServletRequest と HttpServletResponse に変換できるのはなぜですか?

図 7.リクエスト関連のクラス構造図

图 7.Request 相关类结构图

上の図はTomcatで作成したリクエストとレスポンスのクラス構造図です。 Tomcat はリクエストを受信すると、まず org.apache.coyote.Request と org.apache.coyote.Response を作成します。これらの 2 つのクラスは、リクエストと対応する情報クラスを記述するために Tomcat によって内部的に使用されます。この機能は、サーバーがリクエストを受信した後、単純な分析後にリクエストを後続のスレッドに迅速に割り当てて処理することです。そのため、オブジェクトは非常に小さく、JVM によって簡単にリサイクルできます。次に、リクエストを処理するためにユーザー スレッドに渡されると、org.apache.catalina.connector.Request オブジェクトと org.apache.catalina.connector.Response オブジェクトが作成されます。これら 2 つのオブジェクトは、サーブレットに渡されるまでサーブレット コンテナ全体を通過します。リクエストとレスポンスのファサード クラス RequestFacade と RequestFacade は、前と同じ目的に基づいています。データをコンテナにカプセル化します。リクエストに対応するRequestとResponseのクラス変換は下図の通りです

図 8. リクエストとレスポンスの変換プロセス

图 8.Request 和 Response 的转变过程


サーブレットの仕組み

サーブレットがどのようにロードされるか、サーブレットがどのように初期化されるか、そしてサーブレットのアーキテクチャについてはすでに理解しました。次は、サーブレットをどのように呼び出すかです。

ユーザーがブラウザからサーバーへのリクエストを開始すると、通常、そのリクエストには http://hostname: port /contextpath/servletpath という情報が含まれます。ホスト名とポートは、サーバーとの TCP 接続を確立するために使用されます。 URL は、サーバー内のそのサブコンテナがユーザーのリクエストを処理することを選択するために使用されます。では、サーバーはどのようにしてこの URL に基づいて正しいサーブレット コンテナに到達するのでしょうか?

Tomcat7.0 では、この問題は簡単に解決できます。この種のマッピング作業には、org.apache.tomcat.util.http.mapper という特別なクラスが必要です。このクラスは、Tomcat のコンテナ内のすべての子を保存します。コンテナ情報org.apache.catalina.connector.Request クラスが Container コンテナに入ると、マッパーはこのリクエストの hostnane と contextpath に基づいて、ホストとコンテキスト コンテナをリクエストの MappingData 属性に設定します。したがって、リクエストが Container コンテナに入る前に、リクエストがアクセスしたいサブコンテナはすでに決定されています。

図 9. Request のマッパー クラス図

图 9.Request 的 Mapper 类关系图

マッパー内のコンテナー間の完全な関係はどのようにして実現できるのでしょうか?

リスト 5. MapperListener.init

 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 中。

  图 10.Request 在容器中的路由图

图 10.Request 在容器中的路由图

  上图描述了一次 Request 请求是如何达到最终的 Wrapper 容器的,我们现正知道了请求是如何达到正确的 Wrapper 容器,但是请求到达最终的 Servlet 还要完成一些步骤,必须要执行 Filter 链,以及要通知你在 web.xml 中定义的 listener。

  接下去就要执行 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 内のセッション ID を解析し、URL 内のセッション ID を上書きすることに注意してください。

3 番目の場合、セッション ID は javax.servlet.request.ssl_session 属性値に従って設定されます。

セッション ID を使用すると、サーバーは getSession() メソッドを通じて HttpSession オブジェクトを作成できます。現在のセッション ID に対応する HttpSession オブジェクトがない場合は、このオブジェクトを保存場所に追加します。 org.apache.catalina.Manager のセッション コンテナでは、Manager クラスがすべてのセッションのライフ サイクルを管理し、セッションは期限切れになるとリサイクルされ、サーバーはシャットダウンされ、セッションはディスクにシリアル化されます。この HttpSession オブジェクトが存在する限り、ユーザーはセッション ID に基づいてこのオブジェクトを取得できるため、状態が維持されます。

図11.セッション関連のクラス図

图 11.Session 相关类图

上の図からわかるように、request.getSession から取得した HttpSession オブジェクトは、実際には StandardSession オブジェクトのファサード オブジェクトです。これは、前の Request および Servlet と同じ原理です。次の図は、セッション作業のタイミング図です:

図 12. セッション作業のタイミング図 (大きな画像を表示)

图 12.Session 工作的时序图

もう 1 つのポイントは、セッションに関連付けられた Cookie は他の Cookie と変わらないということです。この構成の構成は、web.xml の session-config 構成項目を通じて指定できます。


サーブレットのリスナー

リスナーは、Tomcat サーバー全体で広く使用されており、オブザーバー パターンに基づいて設計されており、サーブレット アプリケーションを開発するための迅速な方法を提供し、別の垂直方向からプログラムとデータを簡単に制御できます。現在、サーブレットは 2 つのタイプのイベントに対して 5 つのオブザーバー インターフェイスを提供しています。それらは、4 つの EventListeners タイプ (ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener) と 2 つの LifecycleListeners タイプ (ServletContextListener、HttpSessionListener) です。下の写真に示すように:

図 13. サーブレットのリスナー (拡大画像を表示)

图 13.Servlet 中的 Listener

これらは基本的に、サーブレットのライフサイクル全体で関心のあるすべてのイベントをカバーしています。これらのリスナー実装クラスは、web.xml の タグで構成できます。もちろん、リスナーをアプリケーションに動的に追加することもできます。ServletContextListener は、コンテナーの開始後にリッスンするイベントが表示されなくなるため、新しいリスナーを追加できないことに注意してください。これらのリスナーの使用をマスターすると、プログラムの設計がより柔軟になります。


まとめ

この記事には多くの内容が含まれており、すべての詳細を明確に説明するのは不可能と思われます。この記事では、サーブレット コンテナの起動からサーブレットの初期化、およびサーブレットのアーキテクチャに至るまでのいくつかの重要なポイントを説明します。全体的かつ完全な構造図を読者に提供し、いくつかの難しい問題を詳細に分析することもできれば幸いです。

以上がサーブレットの動作原理の分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート