この記事では、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="${空のキャッシュされたフラグメント}"> <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:cache> |
JSP 変数は使いやすく、単純な Web アプリに適したコンテンツ キャッシュ ソリューションです。ただし、アプリケーションが大量の動的コンテンツを生成する場合、キャッシュ サイズを制御できないことは間違いなく問題です。専用のキャッシュ フレームワークは、キャッシュの監視、キャッシュ サイズの制限、キャッシュ ポリシーの制御などを可能にする、より強力なソリューションを提供できます。
JSP 2.0 式言語 API
JSP コンテナ (Tomcat など) を使用して、JSP で EL API 式を適用します。ページには値が割り当てられており、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; インポート java.io.IOException;パブリック クラス JspUtils { public static Object eval(String expr, クラス型, JspContext jspContext) throwsJspException { 試す { if (expr.indexOf("${") == -1) return expr; ExpressionEvaluator エバリュエーター = jspContext.getExpressionEvaluator(); return evaluator.evaluate(expr, type, jspContext.getVariableResolver()、null); キャッチ (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> には属性が 1 つだけあります。つまり、キャッシュ フラグメントが出力されるたびに JSP 式を割り当てる必要があります。 TLD ファイルは、次のように、これら 2 つのカスタム タグを CacheTag クラスと DynamicTag クラスにマップします。
<?xml バージョン="1.0" エンコーディング="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" version="2.0"> <tlib-version>1.0</tlib-version> <ショートネーム>jc</ショートネーム> <uri>http://devsphere.com/articles/jspcache</uri> <タグ> <名前>キャッシュ</名前> <タグクラス>com.devsphere.articles.jspcache.CacheTag</タグクラス> <body-content>スクリプトレス</body-content> <属性> <名前>id</名前> <必須>true</必須> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <名前>スコープ</名前> <必須>false</必須> <rtexprvalue>false</rtexprvalue> </attribute> </tag> <tag> <name>dynamic</name> <tag-class> com.devsphere.articles.jspcache.DynamicTag</tag-class> <body-content>empty</body-content> <属性> <名前>式</名前> <必須>true</必須> <rtexprvalue>false</rtexprvalue> </attribute> </tag></taglib> |
TLD ファイルは、Web アプリケーション記述子ファイル (web.xml) に含まれており、これらの 5 つのファイルには、キャッシュが使用可能かどうかを示す初期パラメーターも含まれています。
<?xml バージョン="1.0" エンコーディング="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" version="2.4"> <コンテキストパラメータ> <パラメータ名> com.devsphere.articles.jspcache.enabled</param-name> <param-value>true</param-value> </context-param> <jsp-config> <taglib> <taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri> <taglibの場所>/WEB-INF/jspcache.tld</taglibの場所> </taglib> </jsp-config></web-app> |
<jc:cache> の動作メカニズムを理解します。JSP
コンテナは、JSP ページ内の各 <jc:cache> タグに対して CacheTag インスタンスを作成して処理します。 JSP コンテナは、CacheTag クラスが SimpleTagSupport から継承する 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; パブリック クラス CacheTag は SimpleTagSupport を拡張します { パブリック静的最終文字列 CACHE_ENABLED = "com.devsphere.articles.jspcache.enabled"; プライベート文字列 ID; プライベート int スコープ。 プライベートブール値cacheEnabled; { ID = null; スコープ = PageContext.APPLICATION_SCOPE; } public void setId(String id) { this.id = ID; } public void setScope(文字列スコープ) { this.scope = JspUtils.checkScope(スコープ); } ... } setScope() メソッドは、JspUtils.checkScope() を呼び出して、String 型から int 型に変換されたスコープのプロパティ値を確認します。 ...パブリック クラス JspUtils { ... public static int checkScope(文字列スコープ) { 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 を返します。 それ以外の場合は、新しい 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 は SimpleTagSupport を拡張します { ... public void doTag() が JspException、IOException をスローする { JspContext jspContext = getJspContext(); ServletContext アプリケーション = ((PageContext) jspContext).getServletContext(); 文字列cacheEnabledParam= application.getInitParameter(CACHE_ENABLED); キャッシュ有効化 = キャッシュ有効化Param != null &&cacheEnabledParam.equals("true"); if(キャッシュ有効) { String queuedOutput= (String) jspContext.getAttribute(id,scope); if (cachedOutput == null) { StringWriter バッファ = new StringWriter(); getJspBody().invoke(buffer); キャッシュされた出力 =buffer.toString(); jspContext.setAttribute(id、cachedOutput、スコープ); } 文字列評価出力 = (文字列) JspUtils.eval(cachedOutput, String.class, jspContext); jspContext.getOut().print(evaluatedOutput); } それ以外の場合は getJspBody().invoke(null); } ... } |
1 回の JspUtils.eval() 呼び出しですべての ${…} 式が評価されることに注意してください。多数の ${...} 構造を含むテキストも式であるためです。各キャッシュ フラグメントは、複雑な JSP 式として処理できます。 IsCacheEnabled() メソッドは、doTag() によって初期化された、cacheEnabled の値を返します。
IsCacheEnabled() メソッドは、doTag() によって初期化された、cacheEnabled の値を返します。 ...パブリック クラス CacheTag は 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; パブリック クラス DynamicTag は SimpleTagSupport を拡張します { プライベート String expr; 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 ページが再コンパイルされる場合、キャッシュの無効化が必要になる場合があります。もちろん、開発後の本番環境ではキャッシュを有効にする必要があります。
まとめ
コンテンツ キャッシュは、Web アプリケーションのパフォーマンスを向上させるための非常に使いやすい方法です。この記事では、JSP 式言語を使用して、各ユーザーまたはリクエストのキャッシュ コンテンツをカスタマイズすることに焦点を当てます。この記事全体で簡単に紹介したタグ ライブラリは、小規模な Web アプリに適しており、中規模のアプリケーションのパフォーマンスを向上させることができます。大規模なエンタープライズ レベルのアプリケーションを開発する場合は、単に JSP 変数を使用するのではなく、より優れたキャッシュ メカニズムをサポートするフレームワークの使用を検討する必要があります。