1. ホーム

[解決済み】サーブレットはどのように動作するのですか?インスタンス化、セッション、共有変数とマルチスレッド

2022-03-23 02:09:40

質問

多数のサーブレットを保持するウェブサーバーがあるとします。これらのサーブレット間で情報をやり取りするために、セッション変数とインスタンス変数を設定しています。

ここで、2人以上のユーザーがこのサーバーにリクエストを送信した場合、セッション変数はどうなるでしょうか?

すべてのユーザーで共通なのか、それともユーザーごとに異なるのか?

もし、異なるのであれば、サーバーはどのようにしてユーザーを区別していたのでしょうか?

もう一つ似たような質問ですが、もし n が特定のサーブレットにアクセスした場合、このサーブレットは最初のユーザーがアクセスしたときのみインスタンス化されるのでしょうか、それともすべてのユーザーに対して別々にインスタンス化されるのでしょうか?

言い換えれば、インスタンス変数はどうなるのでしょうか?

どのように解決するのか?

ServletContext

サーブレットコンテナ(のようなもの)の場合 Apache Tomcat ) が起動すると、そのすべてのウェブアプリケーションをデプロイして読み込みます。ウェブアプリケーションがロードされると、サーブレットコンテナはそのアプリケーションのための ServletContext 一度だけ、サーバーのメモリに保存します。ウェブアプリの web.xml と、それに含まれるすべての web-fragment.xml ファイルが解析され、それぞれの <servlet> , <filter><listener> でアノテーションされた各クラスが見つかりました(または @WebServlet , @WebFilter@WebListener それぞれ) は一度だけインスタンス化され、サーバのメモリにも保持されます。 ServletContext . インスタンス化された各フィルタに対して、その init() メソッドが呼び出され、新しい FilterConfig の引数を取り、その引数に関係する ServletContext .

を指定した場合 Servlet には <servlet><load-on-startup> または @WebServlet(loadOnStartup) よりも大きい値 0 であれば、その init() メソッドも起動時に起動され、新しい ServletConfig の引数を取り、その引数に関係する ServletContext . それらのサーブレットは、その値で指定されたのと同じ順序で初期化されます ( 1 が1番目です。 2 は2番目、など)。複数のサーブレットに同じ値が指定された場合、それらのサーブレットはそれぞれ web.xml , web-fragment.xml または @WebServlet クラスローディングを行います。もし、quot;load-on-startup" の値がない場合は init() メソッドが呼び出されます。 HTTPリクエスト はそのサーブレットに初めてヒットします。

サーブレットコンテナが上記の初期化ステップをすべて終了すると、次に ServletContextListener#contextInitialized() が呼び出されます。 ServletContextEvent の引数を取り、その引数には関係する ServletContext . これによって、開発者はプログラム上でさらに別の Servlet , Filter または Listener .

サーブレットコンテナがシャットダウンするとき、すべてのウェブアプリケーションをアンロードして、そのアプリケーションを起動します。 destroy() メソッド、すべての初期化されたサーブレットとフィルタ、そしてすべての Servlet , FilterListener インスタンスを登録します。 ServletContext はゴミ箱に捨てられます。最後に ServletContextListener#contextDestroyed() が呼び出され ServletContext はゴミ箱に捨てられます。

HttpServletRequestHttpServletResponse

サーブレットコンテナは、特定のポート番号でHTTPリクエストをリッスンするWebサーバーに接続されています(通常、開発時には8080番ポート、本番時には80番ポートが使用されます)。クライアント (例えばウェブブラウザを持つユーザ、あるいは を使用してプログラム的に URLConnection ) が HTTP リクエストを送ると、サーブレットコンテナは新しい HttpServletRequest HttpServletResponse オブジェクトを作成し、定義された任意の Filter が連鎖し、最終的には Servlet インスタンスを作成します。

の場合は フィルター を使用する場合、その doFilter() メソッドが呼び出されます。サーブレットコンテナのコードが chain.doFilter(request, response) の場合、リクエストとレスポンスは次のフィルターに進み、 フィルターが残っていない場合はサーブレットにぶつかります。

の場合 サーブレット を指定すると service() メソッドが呼び出されます。デフォルトでは、このメソッドは doXxx() に基づいて呼び出されるメソッドです。 request.getMethod() . 決定されたメソッドがサーブレットに存在しない場合、HTTP 405エラーがレスポンスとして返されます。

リクエストオブジェクトは、HTTPリクエストに関するすべての情報へのアクセスを提供します。 URL , ヘッダー , クエリ文字列 およびボディを含む。レスポンスオブジェクトは、例えばヘッダやボディ (通常は JSP ファイルから生成された HTML コンテンツ) を設定することによって、HTTP レスポンスを思い通りに制御し送信する機能を提供します。HTTP レスポンスがコミットされて終了すると、リクエストオブジェクトとレスポンスオブジェクトの両方がリサイクルされ、再利用できるようになります。

HttpSession

クライアントが初めてウェブアプリケーションにアクセスしたとき、および/または HttpSession を経由して初めて取得されます。 request.getSession() の場合、サーブレットコンテナは新しい HttpSession オブジェクトを生成し、長くてユニークな ID (これは session.getId() ) を作成し、サーバーのメモリに保存します。また、サーブレットコンテナは Cookie の中にある Set-Cookie ヘッダを持つ HTTP レスポンスの JSESSIONID を名前に、その値として一意のセッションIDを指定します。

のように HTTP クッキー仕様 (まともなウェブブラウザとウェブサーバが遵守すべき契約)、クライアント(ウェブブラウザ)は、この クッキー を返し、それ以降のリクエストでは Cookie ヘッダは、そのクッキーが有効である限り(すなわち、一意の ID が期限切れのないセッションを参照し、ドメインとパスが正しいこと)、有効です。ブラウザに組み込まれたHTTPトラフィックモニタを使って、クッキーが有効であることを確認できます(Chrome / Firefox 23+ / IE9+でF12キーを押し、そのヘッダをチェックします)。 ネット/ネットワーク タブをクリックします)。サーブレットコンテナは Cookie という名前のクッキーの存在を確認するために、すべての受信 HTTP リクエストの JSESSIONID を取得し、その値(セッションID)を使って、関連する HttpSession をサーバーのメモリから取得します。

HttpSession で指定されたタイムアウト値以上アイドル (つまり、リクエストで使われない) になるまで、生き続けます。 <session-timeout> での設定は web.xml . タイムアウト値のデフォルトは30分です。そのため、クライアントが指定された時間以上ウェブアプリにアクセスしない場合、サーブレットコンテナは セッション . それ以降のすべてのリクエストは、たとえクッキーが指定されていたとしても、同じセッションにアクセスすることはできなくなり、サーブレットコンテナは新しいセッションを作成します。

クライアント側では、ブラウザのインスタンスが動作している限り、セッションクッキーは生き続けます。したがって、クライアントがブラウザのインスタンス(すべてのタブ/ウィンドウ)を閉じると、クライアント側ではセッションがゴミ箱に捨てられます。新しいブラウザのインスタンスでは、セッションに関連するクッキーは存在しないので、もはや送信されないでしょう。このため、まったく新しい HttpSession が作成され、全く新しいセッションクッキーが使用されます。

簡単に説明すると

  • ServletContext は、ウェブアプリが生きている限り存続します。の間で共有されます。 すべて のリクエストを すべて セッションで使用されます。
  • HttpSession は、クライアントが同じブラウザインスタンスでウェブアプリとやりとりしていて、サーバー側でセッションがタイムアウトしていない限り、存続します。このセッションは すべて のリクエストは 同じ セッションで使用されます。
  • HttpServletRequestHttpServletResponse サーブレットがクライアントからHTTPリクエストを受け取ってから、完全なレスポンス(Webページ)が到着するまでの間、生きています。それは ではない は他の場所で共有されます。
  • すべて Servlet , FilterListener インスタンスは、ウェブアプリが存続する限り存続します。これらのインスタンスは すべて のリクエストを すべて セッションで使用されます。
  • 任意の attribute で定義されているものは ServletContext , HttpServletRequestHttpSession は、当該オブジェクトが生きている限り生き続ける。オブジェクト自体は、JSF、CDI、Springなどのビーン管理フレームワークにおいて、"scope"を表します。それらのフレームワークは、スコープされたビーンを attribute の、最も近いスコープにマッチする。

スレッドの安全性

とはいえ、あなたの大きな関心事は、もしかしたら スレッドセーフ . サーブレットとフィルタはすべてのリクエストで共有されることがおわかりいただけたかと思います。これがJavaの良いところです。Javaはマルチスレッドで、異なるスレッド(HTTPリクエストと読みます)が同じインスタンスを利用することができるのです。そうでなければ、再作成するにはあまりにもコストがかかりすぎます。 init()destroy() を、すべてのリクエストに適用します。

また、次のことを認識する必要があります。 決して リクエストまたはセッションスコープのデータを インスタンス 変数を使用します。それは他のセッションの他のすべてのリクエストの間で共有されます。それは ではなく スレッドセーフ 以下の例では、このことを説明しています。

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

こちらもご覧ください。