9.1 Servletの改良
Servletインターフェースを直接実装してServletクラスを作成する際の欠点は何でしょうか?
- serviceメソッドのみが必要な場合でも、他のメソッドの多くは不要です。コードが冗長になります。
アダプターパターン(Adapter Pattern)について:
- モバイル端末を220Vの電源に直接接続すると、端末が破損してしまいます。どうすればよいでしょうか?充電器を使用します。この充電器がアダプターです。モバイル端末をアダプターに接続し、アダプターを220Vの電源に接続することで問題を解決できます。
-
GenericServletクラスを作成します。このクラスは抽象クラスで、抽象メソッドserviceを持ちます。
-
GenericServletはServletインターフェースを実装します。
-
GenericServletはアダプターです。
-
これ以降、作成するすべてのServletクラスはGenericServletを継承し、serviceメソッドをオーバーライドするだけで済みます。
-
考察:GenericServletクラスを改良する必要はありますか?どのように改良すれば、サブクラスのプログラム作成に役立ちますか?
-
考察1:GenericServletを提供した後、initメソッドはまだ実行されますか?
-
はい、実行されます。GenericServletクラスのinitメソッドが実行されます。
-
考察2:initメソッドは誰が呼び出しますか?
-
Tomcatサーバーが呼び出します。
-
考察3:initメソッドのServletConfigオブジェクトは誰が作成し、誰が渡しますか?
-
すべてTomcatが行います。
-
TomcatサーバーはまずServletConfigオブジェクトを作成し、次にinitメソッドを呼び出してServletConfigオブジェクトをinitメソッドに渡します。
-
**考察:**Tomcatサーバーの疑似コード:
public class Tomcat {
public static void main(String[] args){
// .....
// Tomcatサーバー疑似コード
// LoginServletオブジェクトを作成(リフレクションメカニズムを使用し、パラメータなしのコンストラクタを呼び出してLoginServletオブジェクトをインスタンス化)
Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
Object obj = clazz.newInstance();
// ダウンキャスト
Servlet servlet = (Servlet)obj;
// ServletConfigオブジェクトを作成
// TomcatサーバーがServletConfigオブジェクトをインスタンス化します。
// 多態性(TomcatサーバーはServlet仕様を完全に実装)
ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();
// Servletのinitメソッドを呼び出す
servlet.init(servletConfig);
// Servletのserviceメソッドを呼び出す
// ....
}
}
9.2 GenericServletソースコード解析
関連知識
①:public void init(ServletConfig config) の中のServletConfigとは何ですか?
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;
import java.util.Enumeration;
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
jakarta.servlet.ServletConfigはインターフェースで、jakarta.servlet.Servletもインターフェースです。
②:ServletConfigインターフェースは誰が実装していますか?
org.apache.catalina.core.StandardWrapperFacade
Tomcatサーバーが実装しています。
考察:Tomcatサーバーをjettyサーバーに変更した場合、ServletConfigの出力情報は同じでしょうか?
必ずしも同じではない場合があります。パッケージ名やクラス名はTomcatと異なる可能性がありますが、両者はこのインターフェースを実装しています。
結論:WebサーバーがServletConfigインターフェースを実装しています。
1つのServletオブジェクトには1つのServletConfigオブジェクトが対応しています(ServletオブジェクトとServletConfigオブジェクトは1対1の対応です)。
③:ServletConfigオブジェクトは誰が作成し、いつ作成しますか?
Tomcatが作成し、Servletオブジェクトと同時に作成します。
④:ServletConfigインターフェースは何のために存在しますか?
Configuration:設定。
ServletConfigはServletオブジェクトの設定情報オブジェクトと翻訳されます。
⑤:ServletConfigオブジェクトにはどのような情報が含まれていますか?
ServletConfigにはweb.xmlファイルのservletタグ内の情報が含まれています。
Tomcatはweb.xmlファイルを解析し、web.xmlファイルのservletタグの設定情報をServletConfigオブジェクトに自動的にパッケージ化します。
⑥:ServletConfigにはどのような特別なメソッドがありますか?
- getInitParameter と getInitParameterNamesメソッド
- getInitParameterNamesはweb.xmlファイルの初期化パラメータのnameを取得します。
- getInitParameterはnameに基づいて初期化パラメータの値を取得します。
web.xmlの一部の設定ファイル:
<servlet>
<servlet-name>configTest</servlet-name>
<servlet-class>com.ljy.javaweb.servlet.ConfigTestServlet</servlet-class>
<!--初期化パラメータの設定-->
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/zwm</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
init-paramの情報はTomcatサーバーによってServletConfigオブジェクトにパッケージ化されます。
⑦:web.xmlの初期化情報を取得するメソッド
// web.xmlファイル内の初期化情報を取得するために使用します。
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
// すべての初期化パラメータのnameを取得する
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
デモコード:
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
// コレクションを反復処理
while (initParameterNames.hasMoreElements()){ // もう要素があるかどうか
String name = initParameterNames.nextElement();
String driver = servletConfig.getInitParameter(name);
out.print(name+"---"+driver);
out.print("<br>");
}
結果:
password---root
driver---com.mysql.cj.jdbc.Driver
user---root
url---jdbc:mysql://localhost:3306/zwm
- getServletContextメソッド
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
テスト:
// ServletConfigを通じてServletContextオブジェクトを取得
ServletContext context = servletConfig.getServletContext();
out.print("<br>"+context);
ServletContext context2 = this.getServletContext();
out.print("<br>"+context2);
実行結果:
org.apache.catalina.core.ApplicationContextFacade@749dd4c2
org.apache.catalina.core.ApplicationContextFacade@749dd4c2
GenericServletに含まれる4つのメソッド:
public String getInitParameter(String name)
public Enumeration<String> getInitParameterNames()
public ServletConfig getServletConfig()
public ServletContext getServletContext()
上記の4つのメソッドは、自分で作成したServletでもthisを使用して直接呼び出すことができます。
ServletConfigオブジェクト
- ServletConfigとは何ですか?
Servletオブジェクトの設定情報オブジェクトです。 ServletConfigオブジェクトには、web.xmlファイルのタグ内の設定情報がパッケージ化されています。
1つのServletには1つのServletConfigオブジェクトが対応します。
- ServletConfigオブジェクトは誰が作成しますか?
Tomcatサーバーが作成し、Servletオブジェクトと同様にTomcatサーバーが作成します。デフォルトでは、ユーザーが最初のリクエストを送信したときに作成されます。
TomcatサーバーはServletオブジェクトのinitメソッドを呼び出す際に、ServletConfigオブジェクトのパラメータをinitメソッドに渡す必要があります。
ServletConfigインターフェースの実装クラスはTomcatサーバーによって提供されます。(TomcatサーバーとはWEBサーバーのことです。)
- ServletConfigインターフェースにはどのような一般的なメソッドがありますか?
// 初期化パラメータのnameからvalueを取得 public String getInitParameter(String name); // すべての初期化パラメータのnameを取得 public Enumeration<String> getInitParameterNames(); // ServletContextオブジェクトを取得 public ServletContext getServletContext(); // Servletのnameを取得 public String getServletName();
上記のメソッドはServletクラス内でthisを使用して呼び出すことができます。なぜなら、GenericServletはServletConfigインターフェースを実装しているからです。
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext servletContext = this.getServletContext();
out.print("A---"+servletContext);
}
}
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext servletContext = this.getServletContext();
out.print("B---"+servletContext);
}
}
実行結果:
//A---org.apache.catalina.core.ApplicationContextFacade@7db94b27
//B---org.apache.catalina.core.ApplicationContextFacade@7db94b27
ServletContextはインターフェースで、Servlet仕様の一部です。
Tomcatサーバー(Webサーバー)がServletContextインターフェースを実装しています。
org.apache.catalina.core.ApplicationContextFacade
Webサーバーが起動したときに作成され、サーバーがシャットダウンしたときに破棄されます。
1つのwebappに対して、ServletContextオブジェクトは1つだけ存在します。
Servletコンテキストオブジェクト/環境オブジェクトと呼ばれ、またはweb.xmlオブジェクトに対応します。
1つの教室に複数の学生がいる場合、各学生は1つのServletです。これらの学生は同じ教室にいるため、この教室をServletContextオブジェクトと呼ぶことができます。つまり、このServletContextオブジェクト(環境)に置かれたデータは、同じ教室のすべての学生が共有できます。例えば:教室にエアコンがあり、すべての学生が操作できます。エアコンは共有されています。なぜなら、エアコンは教室に置かれているからです。教室がServletContextオブジェクトです。
Tomcatはコンテナであり、1つのコンテナには複数のwebappを配置できます。1つのwebappは1つのServletContextに対応します。
Tomcatサーバーにはwebappsフォルダがあり、webappsには複数のwebappを保存できます。100個のwebappがある場合、100個のServletContextオブジェクトがあります。
ServletContextはServletコンテキストオブジェクトまたはServletの周辺環境オブジェクトと呼ばれます。
1つのServletContextは1つのweb.xmlファイルに対応します。
public String getInitParameter(String name); // 初期化パラメータのnameからvalueを取得
public Enumeration getInitParameterNames(); // すべての初期化パラメータのnameを取得
web.xml
<!-- コンテキストの初期化パラメータ-->
pageSize
10
startIndex
0
<!--注意:上記の設定情報はアプリケーションレベルの設定情報です。通常、1つのプロジェクトで共有される設定情報は上記のタグに配置されます。-->
<!-- もし設定情報が特定のservletのみを参照するために使用される場合、servletタグに配置し、ServletConfigオブジェクトを使用して取得できます。-->
Enumeration<String> initParameterNames = context.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String s = initParameterNames.nextElement();
out.print(s+"= "+context.getInitParameter(s)+"<br>");
}
結果:
startIndex= 0
pageSize= 10
② getContextPath()
ServletContext servletContext = this.getServletContext();
String contextPath = servletContext.getContextPath();
out.print(contextPath);
結果:
/s4
- アプリケーションのルートを動的に取得します。
- アプリケーションのルートパスを取得します(非常に重要)。なぜなら、Javaソースコードの一部の場所でアプリケーションのルートパスが必要になる場合があり、このメソッドを使用して動的に取得できるからです。
- Javaソースコードでは、アプリケーションのルートパスをハードコードしないでください。なぜなら、このアプリケーションが最終的にデプロイされたときにどのような名前で起動されるかはわからないからです。
③ getRealPath(String path)
// ファイルの絶対パス(実際のパス)を動的に取得する
public String getRealPath(String path);
例:
String realPath = context.getRealPath("/index.jsp");
//String realPath = context.getRealPath("index.jsp"); も可能(デフォルトの起点はwebフォルダ)
out.print("<br>"+realPath);
結果:
D:\Test-For_Money\javaweb\out\artifacts\servlet04_war_exploded\index.jsp
④ log(String message) と log(String message, Throwable t);
ServletContextオブジェクトを使用してもログを記録できます。
public void log(String message);
public void log(String message, Throwable t);
// これらのログ情報はどこに記録されますか?
// localhost.2023-07-07.log
// Tomcatサーバーのlogsディレクトリにはどのようなログファイルがありますか?
//catalina.2023-07-07.log サーバー側のJavaプログラムの実行時のコンソール情報。
//localhost.2023-07-07.log ServletContextオブジェクトのlogメソッドが記録したログ情報はこのファイルに保存されます。
//localhost_access_log.2023-07-07.txt アクセスログ
⑤ setAttribute getAttribute removeAttribute
ServletContextオブジェクトには別の名前があります:アプリケーションスコープ(後でリクエストスコープ、セッションスコープなどの他のスコープがあります)
すべてのユーザーが共有するデータがあり、このデータがほとんど変更されず、データ量が少ない場合、これらのデータをServletContextこのアプリケーションスコープに配置できます
なぜすべてのユーザーが共有するデータなのか?なぜデータ量が少ない必要があるのか?なぜこれらの共有データはほとんど変更されず、またはまったく変更されないのか?
- 共有されていない場合、意味がありません。なぜなら、ServletContextこのオブジェクトは1つだけ存在するからです。共有データを配置することに意味があるのはそのためです。
- データ量が大きい場合、ヒープメモリを多く消費し、このオブジェクトのライフサイクルが長いため、サーバーがシャットダウンしたときにこのオブジェクトが破棄されます。大量のデータはサーバーのパフォーマンスに影響を与えます。メモリ使用量が少ないデータ量は考慮して配置できます。
- すべてのユーザーが共有するデータが変更操作を含む場合、スレッドの同時実行によるセキュリティ問題が必ず発生します。そのため、ServletContextオブジェクトに配置されるデータは通常読み取り専用です。
データ量が少なく、すべてのユーザーが共有し、ほとんど変更されないデータをServletContextこのアプリケーションスコープに配置すると、パフォーマンスが大幅に向上します。なぜなら、アプリケーションスコープはキャッシュのようなもので、キャッシュに配置されたデータは、次回使用する際にデータベースから再度取得する必要がなくなるため、実行効率が大幅に向上するからです。
// 保存(ServletContextアプリケーションスコープにデータを保存する方法)
public void setAttribute(String name, Object value); // map.put(k, v)
// 取得(ServletContextアプリケーションスコープからデータを取得する方法)
public Object getAttribute(String name); // Object v = map.get(k)
// 削除(ServletContextアプリケーションスコープからデータを削除する方法)
public void removeAttribute(String name); // map.remove(k)
例:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class CServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext servletContext = this.getServletContext();
// データを準備
User user = new User("tom", 12);
// データを保存
servletContext.setAttribute("userObj",user);
// データを取得
Object userObj = servletContext.getAttribute("userObj");
out.print(userObj);
}
}
結果:
User{name='tom', age=12}
他のServletファイルで以下のように使用すると:
Object userObj = servletContext.getAttribute("userObj");
out.print(userObj);
CServletにアクセスした後に値が取得できます。それ以外の場合、対応する値はnullになります。
注意
- 今後Servletクラスを作成する際、実際にはGenericServletクラスを直接継承することはありません。なぜなら、私たちが扱うB/S構造のシステムはHTTPハイパーテキスト転送プロトコルに基づいているため、Servlet仕様ではHTTPプロトコル専用のクラスであるHttpServletが提供されています。作成するServletクラスはHttpServletを継承する必要があります。(HttpServletはHTTPプロトコル専用です。)HttpServletを使用してHTTPプロトコルを処理すると、より便利になります。
- HttpServletの継承構造:
jakarta.servlet.Servlet(インターフェース)【祖父】
jakarta.servlet.GenericServlet implements Servlet(抽象クラス)【父】
jakarta.servlet.http.HttpServlet extends GenericServlet(抽象クラス)【孫】
今後作成するServletはHttpServletクラスを継承する必要があります。