JSP 2.0 での動的コンテンツ キャッシュの詳細な説明
著者:Eve Cole
更新時間:2009-07-03 16:56:34
コンテンツ キャッシュは、Web アプリケーションで最も一般的な最適化手法の 1 つであり、簡単に実装できます。たとえば、カスタム JSP タグ (<jc:cache> という名前にします) を <jc:cache> と </jc:cache> とともに使用して、キャッシュする必要がある各ページ フラグメントをカプセル化できます。カスタム タグは、タグに含まれる部分 (つまり、事前にパッケージ化されたページ フラグメント) がいつ実行されるかを制御でき、動的な出力結果をキャプチャできます。 <jc:cache> タグを使用すると、JSP コンテナ (Tomcat など) がアプリケーション全体の JSP 変数としてコンテンツを 1 回だけ生成し、各キャッシュ フラグメントを保存できるようになります。 JSP ページが実行されるたびに、カスタム タグはキャッシュされたページ フラグメントをロードします。JSP コードを再度実行して出力を生成する必要はありません。 Jakarta プロジェクトの一環として、このテクノロジーを使用してタグ ライブラリが開発されました。これは、キャッシュされたコンテンツをユーザーまたはリクエストごとにカスタマイズする必要がない場合にうまく機能します。
この記事では、JSP 2.0 式言語 (EL) を使用して上記の手法を改良し、JSP ページがリクエストおよびユーザーごとにキャッシュされたコンテンツをカスタマイズできるようにします。キャッシュ ページ フラグメントには、JSP コンテナによって評価されない JSP 式が含まれる場合があります。これらの式の値は、ページが実行されるたびにカスタム タグによって決定されます。したがって、動的コンテンツの作成は最適化されますが、キャッシュされたフラグメントには、ネイティブ JSP 式言語を使用して各リクエストによって生成されたコンテンツの一部が含まれる可能性があります。 JSP 2.0 EL API の助けを借りて、Java 開発者は式言語を使用してこれを可能にします。
コンテンツ キャッシュとデータ キャッシュ コンテンツ キャッシュだけが唯一のオプションではありません。たとえば、データベースから抽出されたデータをキャッシュすることもできます。実際、保存された情報には HTML マークアップが含まれず、必要なメモリも少なくなるため、データ キャッシュの方が効率的である可能性があります。ただし、多くの場合、メモリ内キャッシュの方が実装が簡単です。あるケースでは、アプリケーションが多数のトランザクション オブジェクトで構成され、重要な CPU リソースを占有し、複雑なデータを生成し、データを表示するために JSP ページを使用するとします。すべてが正常に動作していましたが、ある日サーバーの負荷が突然増加し、緊急の解決策が必要になりました。現時点では、トランザクション オブジェクトとプレゼンテーション層の間にキャッシュ層を確立することは、非常に優れた効果的な解決策です。ただし、動的コンテンツをキャッシュする JSP ページは、非常に迅速かつスムーズに変更する必要があります。単純な JSP ページの編集と比較して、アプリケーションのビジネス ロジックの変更には通常、より多くの作業とテストが必要です。さらに、ページが複数の複合ソースからの情報を集約する場合、Web 層は少量の変更だけで済みます。問題は、キャッシュされた情報が古くなったときにキャッシュ領域を解放する必要があり、トランザクション オブジェクトはそれがいつ起こるかを認識する必要があることです。ただし、コンテンツ キャッシュやデータ キャッシュ、またはその他の最適化手法の実装を選択する場合は、考慮する必要がある要素が多数あり、開発中のプログラムに特別な要件が必要になる場合もあります。
データ キャッシュとコンテンツ キャッシュは必ずしも相互に排他的ではなく、一緒に使用することができます。たとえば、データベース駆動型アプリケーションでは、データベースから抽出されたデータと、そのデータを表示する HTML が別々にキャッシュされます。これは、JSP を使用してリアルタイムでテンプレートを生成することに似ています。この記事で説明する EL ベースの API テクニックでは、JSP EL を使用してデータをレンダリング テンプレートにロードする方法を示します。
JSP 変数を使用した動的コンテンツのキャッシュ キャッシュ メカニズムを実装するときは常に、キャッシュ オブジェクトを格納するメソッドが必要になります。この記事では、String 型のオブジェクトが関係します。 1 つのオプションは、オブジェクト キャッシュ フレームワークを使用するか、Java マップを使用してカスタム キャッシュ スキームを実装することです。 JSP には、キャッシュ メカニズムに必要な ID とオブジェクトのマッピングを提供する、いわゆる「スコープ属性」または「JSP 変数」がすでにあります。ページまたはリクエスト スコープを使用する場合、これは意味がありませんが、アプリケーション スコープでは、すべてのユーザーとページで共有されるため、キャッシュされたコンテンツを保存するのに適しています。セッション スコープは、ユーザーごとに個別のキャッシュが必要な場合にも使用できますが、これはあまり効率的ではありません。次の例に示すように、JSP 変数を使用して、JSTL タグ ライブラリを使用してコンテンツをキャッシュできます。
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><c:if test="${empty queuedFragment}">
<c:set var="cachedFragment"scope="application">
...
</c:set></c:if>
キャッシュされたページ フラグメントは、次のステートメントを使用して結果を出力します。
${applicationScope.cachedFragment}
リクエストごとにキャッシュ フラグメントをカスタマイズする必要がある場合はどうなりますか?たとえば、カウンターを含める場合は、2 つのフラグメントをキャッシュする必要があります。
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><c:if test="${sessionScope.counter == null}"> <c :set var="counter"scope="session" value="0"/></c:if><c:set var="counter" value="${counter+1}"scope="session"/ ><c:if test="${空のキャッシュされたフラグメント1}">
<c:set var="cachedFragment1"scope="application">
...
</c:set></c:if><c:if test="${empty queuedFragment2}">
<c:set var="cachedFragment2"scope="application">
...
</c:set></c:if>
次のステートメントを使用して、キャッシュの内容を出力できます。
${cachedFragment1} ${counter} ${cachedFragment2}
専用のタグ ライブラリを利用すると、カスタマイズが必要なページ フラグメントのキャッシュが非常に簡単になります。前述したように、キャッシュされたコンテンツは開始タグ (<jc:cache>) と終了タグ (</jc:cache>) で囲むことができます。各カスタマイズは、別のタグ (<jc:dynamic expr="..."/>) を使用して表現し、JSP 式 (${...}) を出力できます。動的コンテンツは JSP 式を使用してキャッシュされ、キャッシュされたコンテンツが出力されるたびに割り当てられます。これがどのように達成されるかについては、以下のセクションで説明します。 Counter.jsp は、カウンターを含むページ フラグメントをキャッシュし、ユーザーがページを更新するたびにカウンターは自動的に 1 ずつ増加します。
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><%@ taglib prefix="jc" uri=" http://devsphere.com/ Articles/jspcache " %><c:if test="${sessionScope.counter == null}">
<c:set var="counter"scope="session" value="0"/></c:if><c:set var="counter" value="${counter+1}"scope="session "/><jc:cache id="cachedFragmentWithCounter">
... <jc:dynamic expr="sessionScope.counter"/>
...</jc:キャッシュ>
JSP 変数は使いやすく、単純な Web アプリに適したコンテンツ キャッシュ ソリューションです。ただし、アプリケーションが大量の動的コンテンツを生成する場合、キャッシュ サイズを制御できないことは間違いなく問題です。専用のキャッシュ フレームワークは、キャッシュの監視、キャッシュ サイズの制限、キャッシュ ポリシーの制御などを可能にする、より強力なソリューションを提供できます。
JSP 2.0 式言語 API の使用
JSP コンテナ (Tomcat など) は、EL API を使用して JSP ページ内の式を評価し、Java コードで使用できます。これにより、XML ファイル、テキストベースのリソース、カスタム スクリプトなど、JSP EL を使用した Web ページの外部での開発が可能になります。 EL API は、Web ページ内の式をいつ割り当てるか、またはそれらに関連する式を記述するかを制御する必要がある場合にも役立ちます。たとえば、キャッシュされたページ フラグメントにはカスタム JSP 式を含めることができ、キャッシュされたコンテンツが出力されるたびに、EL API を使用してこれらの式が割り当てまたは再評価されます。
この記事では、サンプル プログラムを示します (記事の最後にあるリソースのセクションを参照)。このアプリケーションには、Java クラス (JspUtils) とメソッド eval() が含まれています。このメソッドには、JSP 式、予期される型という 3 つのパラメータがあります。式と JSP コンテキスト オブジェクトの。 Eval() メソッドは、JSP コンテキストから ExpressionEvaluator を取得し、evaluate() メソッドを呼び出して、式、式の予期される型、および JSP コンテキストから取得した変数を渡します。 JspUtils.eval() メソッドは式の値を返します。
パッケージ com.devsphere.articles.jspcache;
インポート javax.servlet.jsp.JspContext;
インポート javax.servlet.jsp.JspException;
インポート javax.servlet.jsp.PageContext;
インポート javax.servlet.jsp.el.ELException;
インポート javax.servlet.jsp.el.ExpressionEvaluator;
import java.io.IOException;public class JspUtils {
public static Object eval(
文字列式、クラス型、JspContext jspContext)
JspExceptionをスローします{
試す {
if (expr.indexOf("${") == -1)
式を返します。
ExpressionEvaluator エバリュエーター
= jspContext.getExpressionEvaluator();
return evaluator.evaluate(expr, type,
jspContext.getVariableResolver()、null);
} catch (ELException e) {
新しい JspException(e) をスローします。
}
}
...}
注: JspUtils.eval() は主に標準の ExpressionEvaluator をカプセル化します。 expr に ${ が含まれていない場合、JSP 式がないため、JSP EL API は呼び出されません。
タグ ライブラリ記述子 (TLD) ファイルの作成 JSP タグ ライブラリでは、タグ、その属性、およびタグ上で動作する Java クラスの名前をカスタマイズするためにタグ ライブラリ記述子 (TLD) ファイルが必要です。 jspcache.tld には、2 つのカスタム タグが記述されています。<jc:cache> には、キャッシュされたページ フラグメントの ID と、常に保存する必要がある JSP ページのコンテンツ スコープである JSP スコープの 2 つの属性があります。 <jc:dynamic> には、キャッシュ フラグメントが出力されるたびに評価する必要がある JSP 式という属性が 1 つだけあります。 TLD ファイルは、次のように、これら 2 つのカスタム タグを CacheTag クラスと DynamicTag クラスにマップします。
<?xml version="1.0" encoding="UTF-8" ?><taglib xmlns=" http://java.sun.com/xml/ns/j2ee "
xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance "
xsi:schemaLocation=" http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
バージョン="2.0">
<tlib-バージョン>1.0</tlib-バージョン>
<short-name>jc</short-name>
<uri>http://devsphere.com/articles/jspcache</uri>
<タグ>
<name>キャッシュ</name>
<tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
<body-content>スクリプトレス</body-content>
<属性>
<名前>ID</名前>
<必須>真</必須>
<rtexprvalue>true</rtexprvalue>
</属性>
<属性>
<名前>スコープ</名前>
<必須>偽</必須>
<rtexprvalue>false</rtexprvalue>
</属性>
</タグ>
<タグ>
<name>動的</name>
<tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
<body-content>空</body-content>
<属性>
<name>式</name>
<必須>真</必須>
<rtexprvalue>false</rtexprvalue>
</属性>
</tag></taglib>
TLD ファイルは、Web アプリケーション記述子ファイル (web.xml) に含まれており、これらの 5 つのファイルには、キャッシュが使用可能かどうかを示す初期パラメーターも含まれています。
<?xml version="1.0" encoding="ISO-8859-1"?><web-app xmlns=" http://java.sun.com/xml/ns/j2ee "
xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance "
xsi:schemaLocation=" http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd"
バージョン="2.4">
<コンテキストパラメータ>
<param-name>com.devsphere.articles.jspcache.enabled</param-name>
<param-value>true</param-value>
</context-param>
<jsp-config>
<タグリブ>
<taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri>
<taglib の場所>/WEB-INF/jspcache.tld</taglib の場所>
</タグリブ>
</jsp-config></web-app>
<jc:cache> の動作メカニズムを理解します。JSP コンテナは、JSP ページ内の各 <jc:cache> タグに対して CacheTag インスタンスを作成して処理します。 JSP コンテナは、SimpleTagSupport から継承された CacheTag クラスである setJsp ()、setParent ()、および setJspBody () メソッドを呼び出す役割を果たします。 JSP コンテナは、操作されたタグの各属性に対して setter メソッドも呼び出します。 SetId() メソッドと setScope() メソッドは、属性値をプライベート フィールドに保存します。この値は、CacheTag() コンストラクターを使用してデフォルト値で初期化されます。
パッケージ com.devsphere.articles.jspcache;
インポート javax.servlet.ServletContext;
インポート javax.servlet.jsp.JspContext;
インポート javax.servlet.jsp.JspException;
インポート javax.servlet.jsp.PageContext;
インポート javax.servlet.jsp.tagext.SimpleTagSupport;
インポート java.io.IOException;インポート java.io.StringWriter;
public class CacheTag extends SimpleTagSupport {
パブリック静的最終文字列 CACHE_ENABLED
= "com.devsphere.articles.jspcache.enabled";
プライベート文字列ID;
プライベート int スコープ。
プライベートブール値cacheEnabled; public CacheTag() {
ID = null; スコープ = PageContext.APPLICATION_SCOPE;
} public void setId(String id) {
this.id = ID;
public void setScope(Stringscope) {
this.scope = JspUtils.checkScope(スコープ);
}
...}
setScope() メソッドは、JspUtils.checkScope() を呼び出して、String 型から int 型に変換されたスコープのプロパティ値を確認します。
...パブリック クラス JspUtils {
...
public static int checkScope(Stringscope) {
if ("ページ".equalsIgnoreCase(scope))
PageContext.PAGE_SCOPE を返します。
else if ("リクエスト".equalsIgnoreCase(scope))
PageContext.REQUEST_SCOPE を返します。
else if ("セッション".equalsIgnoreCase(scope))
PageContext.SESSION_SCOPE を返します。
else if ("アプリケーション".equalsIgnoreCase(scope))
PageContext.APPLICATION_SCOPE を返します。
それ以外
throw new IllegalArgumentException(
"無効なスコープ: " + スコープ);
}}
CacheTag インスタンスがタグ上で操作できる状態になると、JSP コンテナは doTag() メソッドを呼び出し、getJspContext() を使用して JSP コンテキストを取得します。このオブジェクトは PageContext としてキャストされるため、getServletContext() メソッドを呼び出すことができます。サーブレット コンテキストは、キャッシュ メカニズムが有効かどうかを示す初期化パラメータの値を取得するために使用されます。キャッシュが有効な場合、 doTag() は id およびscope 属性値を使用してキャッシュされたページ フラグメントの取得を試みます。ページ フラグメントがキャッシュされていない場合、doTag() は getJspBody().invoke() を使用して、<jc:cache> と </jc:cache> によってカプセル化された JSP コードを実行します。 JSP 本体によって生成された出力は StringWriter にバッファリングされ、toStirng() メソッドによって取得されます。このようにして、doTag() は JSP コンテキストの setAttribute() メソッドを呼び出して、新しい JSP 変数を作成します。この変数は、JSP 式 (${…}) を含む可能性のあるキャッシュ コンテンツを制御します。これらの式は、jspContext.getOut().print() を使用してコンテンツを出力する前に、JspUtils.eval() によって割り当てられます。この動作は、キャッシュが有効な場合にのみ発生します。 それ以外の場合、doTag() は getJspBody().invoke(null) を通じて JSP 本体を実行するだけであり、出力結果はキャッシュされません。
...パブリック クラス CacheTag extends SimpleTagSupport {
...
public void doTag() は JspException、IOException をスローします {
JspContext jspContext = getJspContext();
ServletContext アプリケーション
= ((PageContext) jspContext).getServletContext();
文字列cacheEnabledParam
= application.getInitParameter(CACHE_ENABLED);
キャッシュ有効化 = キャッシュ有効化Param != null
&&cacheEnabledParam.equals("true");
if (キャッシュ有効) {
文字列キャッシュ出力
= (文字列) jspContext.getAttribute(id, スコープ);
if (cachedOutput == null) {
StringWriter バッファ = new StringWriter();
getJspBody().invoke(buffer);
キャッシュされた出力 =buffer.toString();
jspContext.setAttribute(id、cachedOutput、スコープ);
文字列evaluatedOutput = (文字列) JspUtils.eval(
キャッシュされた出力、String.class、jspContext);
jspContext.getOut().print(evaluatedOutput);
} それ以外
getJspBody().invoke(null);
}
...}
1 回の JspUtils.eval() 呼び出しですべての ${…} 式が評価されることに注意してください。多数の ${...} 構造を含むテキストも式であるためです。各キャッシュ フラグメントは、複雑な JSP 式として処理できます。
IsCacheEnabled() メソッドは、doTag() によって初期化された、cacheEnabled の値を返します。
...パブリック クラス CacheTag extends SimpleTagSupport {
... public boolean isCacheEnabled() {
キャッシュ有効を返します。
}}
<jc:cache> タグを使用すると、ページ開発者はキャッシュされたページ フラグメントの ID を選択できます。これにより、キャッシュされたページ フラグメントを複数の JSP ページで共有できるようになり、JSP コードを再利用するときに役立ちます。ただし、競合の可能性を避けるために、依然としていくつかの命名プロトコルが必要です。この副作用は、自動 ID 内に URL を含めるように CacheTag クラスを変更することで回避できます。
<jc:dynamic> の動作を理解する 各 <jc:dynamic> は DynamicTag クラスのインスタンスによって処理され、setExpr() メソッドは expr 属性値をプライベート フィールドに保存します。 DoTag() メソッドは JSP 式を作成し、${ プレフィックスと } サフィックスを expr 属性値に追加します。次に、doTag() は findAncestorWithClass() を使用して、<jc:dynamic> タグ要素を含む <jc:cache> の CacheTag ハンドラーを検索します。見つからない場合、またはキャッシュが無効になっている場合は、JSP 式が JspUtils.eval() で評価され、値が出力されます。それ以外の場合、doTag() は値のない式を出力します。
パッケージ com.devsphere.articles.jspcache;
インポート javax.servlet.jsp.JspException;
インポート javax.servlet.jsp.tagext.SimpleTagSupport;
インポート java.io.IOException;
public class DynamicTag extends SimpleTagSupport {
プライベート文字列式。
public void setExpr(String expr) {
this.expr = expr;
public void doTag() は JspException、IOException をスローします {
文字列出力 = "${" + expr + "}";
CacheTag ancestor = (CacheTag) findAncestorWithClass(
これ、CacheTag.class);
if (ancestor == null || !ancestor.isCacheEnabled())
出力 = (文字列) JspUtils.eval(
出力、String.class、getJspContext());
getJspContext().getOut().print(出力);
}}
上記のコードを分析すると、<jc:cache> と <jc:dynamic> が連携して、可能な限り効率的なソリューションを実現していることがわかります。キャッシュが使用可能な場合、ページ フラグメントは、<jc:dynamic> によって生成された JSP 式とともにバッファーに置かれ、CacheTag 値が割り当てられます。キャッシュが無効になっている場合、キャッシュは無意味になり、<jc:cache> は JSP 本体部分を実行するだけで、DynamicTag は JSP 式に値を割り当てたままになります。特に開発プロセス中にコンテンツが変更され、JSP ページが再コンパイルされる場合、キャッシュの無効化が必要になる場合があります。もちろん、開発後の本番環境ではキャッシュを有効にする必要があります。
要約する
大規模なエンタープライズ レベルのアプリケーションを開発する場合は、JSP 変数を使用するだけでなく、より優れたキャッシュ メカニズムをサポートするフレームワークの使用を検討する必要があります。ただし、EL API に基づくカスタマイズ テクノロジを理解することが役立つことは間違いありません。