多数のサーブレットを保持する Web サーバーがあるとします。これらのサーブレット間で情報を渡すために、セッション変数とインスタンス変数を設定しています。
さて、2 人以上のユーザーがこのサーバーにリクエストを送信した場合、セッション変数はどうなるでしょうか? セッション変数は
すべてのユーザーに共通でしょうか、それともユーザーごとに異なるでしょうか?
異なる場合、サーバーはどのようにして異なるユーザーを区別できるのでしょうか?
もう 1 つ似たような質問ですが、特定のサーブレットにアクセスするユーザーがいる場合n
、このサーブレットは最初のユーザーが最初にアクセスしたときにのみインスタンス化されますか、それともすべてのユーザーに対して個別にインスタンス化されますか?
つまり、インスタンス変数はどうなりますか?
ベストアンサー1
ServletContext
サーブレットコンテナ(アパッチトムキャット) が起動すると、すべてのウェブアプリケーションがデプロイされ、ロードされます。ウェブアプリケーションがロードされると、サーブレットコンテナは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()
メソッドも起動時に呼び出されます。これらのサーブレットは、その値で指定された順序で初期化されます。複数のサーブレットに同じ値が指定されている場合、それらのサーブレットはそれぞれ、、、web.xml
またはweb-fragment.xml
クラスロードで表示される順序でロードされます@WebServlet
。「load-on-startup」値が存在しないか負の場合、init()
メソッドは、HTTPリクエストサーブレットに初めてアクセスするinit()
メソッドが2つあります。1つはServletConfig
引数として、関連する が含まれますServletContext
。また、引数を取らない別の は、ServletContext
継承されたメソッドによって使用できますgetServletContext()
。
サーブレットコンテナが上記の初期化手順をすべて完了すると、ServletContextListener#contextInitialized()
呼び出されますServletContextEvent
引数には、関連する が含まれます。これにより、開発者はプログラムでさらに別の、または をServletContext
登録できるようになります。Servlet
Filter
Listener
サーブレットコンテナがシャットダウンすると、すべてのウェブアプリケーションがアンロードされ、destroy()
初期化されたすべてのサーブレットとフィルタの メソッドが呼び出され、を介して登録されたすべてのServlet
、Filter
およびインスタンスが破棄されます。最後に、Listener
ServletContext
ServletContextListener#contextDestroyed()
が呼び出され、ServletContext
自体は破棄されます。
HttpServletRequest
そしてHttpServletResponse
サーブレットコンテナは、特定のポート番号(開発時にはポート8080、本番時にはポート80が使用されるのが一般的)でHTTPリクエストをリッスンするウェブサーバーに接続されます。クライアント(ウェブブラウザを持つユーザーなど)が、プログラム的に使用URLConnection
)がHTTPリクエストを送信すると、サーブレットコンテナは新しいインスタンスを作成します。HttpServletRequest
そしてHttpServletResponse
Filter
そして、それらをチェーン内で定義されているもの、そして最終的にはServlet
インスタンスに渡します。
の場合フィルター、doFilter()
メソッドが呼び出されます。サーブレット コンテナのコードが を呼び出すとchain.doFilter(request, response)
、リクエストとレスポンスは次のフィルタに進むか、フィルタが残っていない場合はサーブレットにヒットします。
の場合サーブレット、メソッドが呼び出されます。デフォルトでは、このメソッドは に基づいてservice()
どのメソッドを呼び出すかを決定します。決定されたメソッドがサーブレットに存在しない場合、応答で HTTP 405 エラーが返されます。doXxx()
request.getMethod()
リクエストオブジェクトは、HTTPリクエストに関するすべての情報へのアクセスを提供します。メールアドレス、ヘッダー、クエリ文字列および本文。レスポンス オブジェクトは、たとえばヘッダーと本文 (通常は JSP ファイルから生成された HTML コンテンツを使用) を設定できるようにすることで、HTTP レスポンスを希望どおりに制御および送信する機能を提供します。HTTP レスポンスがコミットされて終了すると、リクエスト オブジェクトとレスポンス オブジェクトの両方がリサイクルされ、再利用できるようになります。
HttpSession
クライアントが初めてウェブアプリにアクセスしたとき、またはHttpSession
が を介して初めて取得されるとrequest.getSession()
、サーブレットコンテナは新しいHttpSession
オブジェクトを作成し、長くてユニークなID( で取得可能session.getId()
)を生成し、それをサーバーのメモリに格納します。サーブレットコンテナはまた、Cookie
Set-Cookie
HTTP 応答のヘッダーに、JSESSIONID
名前として 、値として一意のセッション ID が含まれます。
によるとHTTP クッキー仕様(まともなウェブブラウザとウェブサーバーが遵守しなければならない契約)クライアント(ウェブブラウザ)はこれを送信する必要がある。クッキー back in subsequent requests in the Cookie
header for as long as the cookie is valid (i.e. the unique ID must refer to an unexpired session and the domain and path are correct). Using your browser's built-in HTTP traffic monitor, you can verify that the cookie is valid (press F12 in Chrome / Edge / Firefox 23+ / IE9+, and check the Net/Network tab). The servlet container will check the Cookie
header of every incoming HTTP request for the presence of the cookie with the name JSESSIONID
and use its value (the session ID) to get the associated HttpSession
from server's memory.
The HttpSession
stays alive until it has been idle (i.e. not used in a request) for more than the timeout value specified in <session-timeout>
, a setting in web.xml
. The default timeout value depends on the servlet container and is usually 30 minutes. So, when the client doesn't visit the web app for longer than the time specified, the servlet container trashes the session. Every subsequent request, even with the cookie specified, will not have access to the same session anymore; the servlet container will create a new session.
On the client side, the session cookie remains as long as the browser instance is running (normally). Unless the browser is configured to restore the last browser session, when the client closes the browser instance (all tabs/windows), the session is lost on the client's side. In a new browser instance, the cookie associated with the session wouldn't exist, so it would no longer be sent. This causes an entirely new HttpSession
to be created, with an entirely new session cookie being used.
In a nutshell
- The
ServletContext
lives for as long as the web app lives. It is shared among all requests in all sessions. - The
HttpSession
lives for as long as the client is interacting with the web app with the same browser instance, and the session hasn't timed out at the server side. It is shared among all requests in the same session. - The
HttpServletRequest
andHttpServletResponse
live from the time the servlet receives an HTTP request from the client, until the complete response (the web page) has arrived. It is not shared elsewhere. - All
Servlet
,Filter
andListener
instances live as long as the web app lives. They are shared among all requests in all sessions. - Any
attribute
that is defined inServletContext
,HttpServletRequest
andHttpSession
will live as long as the object in question lives. The object itself represents the "scope" in bean management frameworks such as JSF, CDI, Spring, etc. Those frameworks store their scoped beans as anattribute
of its closest matching scope.
Thread Safety
That said, your major concern is possibly thread safety. これで、サーブレットとフィルターがすべてのリクエスト間で共有されることがわかりました。これが 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.
}
}