> ジェネリック(総称あるいは汎用)プログラミング (generic programming)はデータ形式に依存しないコンピュータプログラミング方式。
これで意味がわかるやつはいるのか。
他のサイトを見てみても、
http://www.techscore.com/tech/Java/JavaSE/JavaLanguage/1/
> ジェネリクスとは、「総称性(Genericity)」「ジェネリック・プログラミング」とも呼ばれるプログラミング技法で、オブジェクト指向とは異なるパラダイムからきたものです。データの型に束縛されず、型そのものをパラメータ化して扱うことができます。
http://ufcpp.net/study/csharp/sp2_generics.html
> ジェネリックス(generics:総称性)、 あるいは、総称的プログラミング(generic programming)とも呼ばれますが、 この機能は、 さまざまな型に対応するために、型をパラメータとして与えて、その型に対応したクラスや関数を生成するもの機能です。
http://d.hatena.ne.jp/Nagise/20101105/1288938415
> ジェネリクスでは、「型」を変数にした「型変数」というものを取り扱う。
http://www.sophia-it.com/content/GENERICS
> C++言語のテンプレートに相当するもので、実行側コードからクラスの因数の型を指定することが可能となっている。
http://www.ne.jp/asahi/hishidama/home/tech/java/generics.html
> 総称型(ジェネリック・ジェネリクス)は、C++のテンプレート(template)と見た目はそっくりの機能。
とお前ら何言ってるんだ的解説ばっかりですし。
C++とか知らんがな。
正直いまだによくわかんないんですが、使用する側から引数の型を指定できるクラス・メソッドってことでいいんですかね。
とりあえず適当にクラスを作ってみます。
public class HogeClass { private Object value= null; public void setValue(Object value) { this.value = value; } public Object getIntegerValue() { return value; } }単にObjectを突っ込んで取り出すだけというクラスです。
これを使うときは、
HogeClass.setValue("string"); String value = (String)HogeClass.getValue();と毎回受け取り側でキャストしてやらないといけません。
HogeClass.setValue("string"); Integer value = (Integer)HogeClass.getValue();と、うっかりStringを突っ込んでIntegerで受け取ろうとすると実行時エラーになってしまいます。
文法的には正しいため、困ったことにコンパイルは通ってしまいます。
このような面倒を省くため、投入できるクラスを制限しましょう。
public class HogeClass { private Integer integerValue= null; private String StringValue= null; public void setValue(Integer value) { this.integerValue = value; } public void setValue(String value) { this.StringValue = value; } public Integer getIntegerValue() { return integerValue; } public String getStringValue() { return StringValue; } }型は限定できるようになりましたが、対応できる型を追加する度にメソッドを記述しなくてはならなくて大変です。
もっと簡単に書きたいということでジェネリックです。
public class HogeClass<T> { private T value= null; public void setValue(T value) { this.value = value; } public T getValue() { return value; } }Tという謎の型が追加されています。
これは型引数というもので、別にどんな文字でもいいのですがEとかKとかVとか大文字一文字(+数値)で表すのが通例になってるみたいです。
この謎の型引数の具体的な値はHogeClassを使用するときにコンストラクタで指定します。
HogeClass<String> hoge = new HogeClass<String>(); hoge.setValue("aaaa"); String tmp = hoge.getValue();型引数として<String>を指定しました。
これによって謎だったT型にString型が当て込まれ、以後全てのTはStringとして扱われます。
HogeClass<String> hoge = new HogeClass<String>(); Integer tmp = hoge.getValue();と書いた場合、HogeClass.getValue()の返り値の型TはStringであるため、コンパイルエラーになります。
ジェネリックを使うことで、これまでは実行するまでわからなかったエラーをコンパイル時点で検出することができるようになりました。
めでたし。
という理解でいいんですかね?
まずはタグ用のJavaサーブレット本体から。
helloTag.java
package tag; import java.io.IOException; import javax.servlet.jsp.tagext.TagSupport; public class helloTag extends TagSupport { public int doStartTag() { try { pageContext.getOut().write("Hello,world"); } catch (IOException e) { e.printStackTrace(); } return SKIP_BODY; } } |
カスタムタグはTagSupportクラスをextendsして作成します。
TagSupportクラスにあるメソッドのうち、doStartTag、doAfterBody、doEndTagの各メソッドを上書きして実行します。
各メソッドは名前のとおり、タグ開始時点、タグボディが終わったあと、タグの終了時点で行う処理を意味しています。
デフォルトでは各メソッドとも何もしません。
今回は文字を書くだけなのでdoStartTagを上書きします。
各メソッドは返り値を返さなければなりません。
EVAL_PAGEやEVAL_BODY_INCLUDE等色々ありますが、今回はタグボディを気にしないのでSKIP_BODYを返します。
次にタグを呼び出すJSPです。こちらは簡単。
helloTag.jsp
<%@ taglib uri="http://localhost:8080/example/tags/helloTag" prefix="hw" %> <hw:helloworld /> |
http://localhost:8080/example/tags/helloTagって何処のこと?
気にせずに次はweb.xml
<jsp-config> <taglib> <taglib-uri>http://localhost:8080/example/tags/helloTag</taglib-uri> <taglib-location>/WEB-INF/lib/hello.tld</taglib-location> </taglib> </jsp-config> |
実はhttp://localhost:8080/example/tags/helloTagではなく何でもいいのです。
命名規則のようなものはありますが比較的どうでもいいです。
web.xmlで<taglib-uri>と<taglib-location>を結び付けるという意味なのです。
<taglib-location>には実在するtldファイルを置かねばなりません。
http://yuubiseiharukana.blog.shinobi.jp/Entry/40/
で作成したタグの使用法が、
<%@taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>
となっていたのも、web.xml(のようなもの)にそのように書かれていたからです。
というわけで最後にhello.tld。
/WEB-INF/lib/hello.tld
<?xml version="1.0" ?> <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> <jsp-version>2.0</jsp-version> <short-name>hw</short-name> <tag> <name>helloTag</name> <tag-class>tag.helloTag</tag-class> <body-content>empty</body-content> </tag> </taglib> |
JSPによるタグファイルの設計図は<%@tag%><%@attribute%>等で記述していたのに対し、tldファイルはタグの設計図をxml形式で記入します。
両者は概ね共通していますが、
<tag-class>にパッケージ付きのフルパス、<name>はタグのクラス名を指定します。
さて、(のようなもの)に嫌な予感を感じた方はいませんか。
上記を見てのとおりカスタムタグを一個追加するために複数のファイルを書かねばなりません。
しかもweb.xmlまで書き換える必要があります。
web.xmlはJavaアプリケーションの根幹であり、PHPでいうところのphp.iniのようなものです。
たかだかタグ追加のためだけにわざわざ編集するなどという面倒なことはしたくありません。
また、自作のすごいカスタムタグを配布したい、という場合に、「導入する場合はweb.xmlを書き換えて」などとヘルプを書くのもどうかという感じです。
というわけでweb.xmlを書き換えずに済む方法があるのですがまあまたいつか。
カスタムタグはJakartaTaglibsしか存在していないというわけではなく、自作することができます。
カスタムタグの部分はJavaの作成者たちが特に力を入れた部分であり、まさに正気の沙汰ではない実装になっています。
タグの作り方は何通りもあるのですが、とりあえず一番簡単な方法から行ってみます。
まずWEB-INF/tagsディレクトリを作成し、hello.tagという名前のテキストファイルを作成します。
これがタグの処理内容を示すファイルになります。
/WEB-INF/tags/tag1.tag
<%@ tag pageEncoding="Shift_JIS" %> <p>Hello World</p> |
本当にただHelloWorldを出力しているだけです。
<%@ tag pageEncoding>には文字コードを指定します。
タグファイルは<filter>の範囲外なので、例によって毎回書かなければなりません。
JSPファイルでは、以下のような書き方で作成したタグを呼び出すことができます。
tag1.jsp
<%@taglib tagdir='/WEB-INF/tags' prefix='tag' %> <tag:tag1 /> |
tagdirで設定したディレクトリの中にある.tagファイルが、ファイル名で自動的にタグとして使えるようになります。
実に簡単ですね。
属性の受け渡しは以下のようになります。
tag2.jsp
<tag:tag2 data="aaaaa"/> |
/WEB-INF/tags/tag2.tag
<%@ tag pageEncoding="Shift_JIS" %> <%@ attribute name="data" type="java.lang.String" required="true" rtexprvalue="true"%> |
${data}が渡されました。
JSP側では普通のHTMLタグのように名前と属性を指定するだけです。
タグファイル側では、<%@ attribute%>で属性の書式を設定します。
設定値は、nameが受け取る属性名、typeが属性の型(デフォルトはString)、requiredが必須かどうか(デフォルトはfalse)、rtexprvalueは属性値としてELを受け付けるかどうか(デフォルトはtrue)です。
デフォルトでは属性を書いても書かなくてもかまいませんが、required="true"を設定すると必ずその属性を記述しなければなりません。
デフォルトでは<tag:tag2 data="${ELdata}"/>といった書き方が可能ですが、rtexprvalue="false"が指定されているとエラーになります。
さて、以上のタグはタグボディを完全に無視していました。
<tag:tag1>ここがタグボディ</tag:tag1>と書いても、無かったことにされてしまうのです。
次はタグボディを評価してみます。
tag3.jsp
<tag:tag3>${data}</tag:tag3> 帰ってきた値:${tag3request} |
/WEB-INF/tags/tag3.tag
<%@ tag pageEncoding="Shift_JIS" %> <%@ tag body-content='scriptless' %> 渡された値:<jsp:doBody /> <jsp:doBody var="tag3request" scope="request" /> スコープに入れた値:${requestScope.tag3request}<br> |
独特な書式ですが、<jsp:doBody>でタグボディを取得することができます。
属性を何も記述しない場合、そのままその部分に文字列で出力します。
varを指定すると指定された変数に格納されます。
タグボディを加工したい場合などに利用します。
<%@ tag body-content%>の部分で、タグボディの評価方法を指定します。
上記のようにscriptlessを指定するとELを使用できます。
dataに入っている値が表示されます。
tagdependentを指定すると、タグボディが文字列として評価されます。
${data}という文字列のまま出力されます。
emptyと指定した場合、タグボディを記述することができません。
<br />等のボディなしで完結するタグ用ですが、タグボディを記述してしまうとエラーになるので不便です。
何れにせよ、指定にかかわらずスクリプトレットは常時使用できません。
tagdependentの場合は単に文字列になりますが、scriptlessの場合タグボディ内にスクリプトを書くとエラーになります。
このように、いくつか制限はありますがJSPでカスタムタグを作るのはわりと簡単です。
では次に、Javaサーブレットで/WEB-INF/tags/tag1.tagと同じものを作ってみましょう。
/helloTag.jsp
<%@ taglib uri="http://localhost:8080/example/tags/helloTag" prefix="hw" %> <hw:helloTag /> |
src/tag/helloTag.java
package tag; |
/WEB-INF/web.xml
<jsp-config> <taglib> <taglib-uri>http://localhost:8080/example/tags/helloTag</taglib-uri> <taglib-location>/WEB-INF/lib/hello.tld</taglib-location> </taglib> </jsp-config> |
/WEB-INF/lib/hello.tld
<?xml version="1.0" ?> <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> <jsp-version>2.0</jsp-version> <short-name>hw</short-name> <tag> <name>helloTag</name> <tag-class>tag.helloTag</tag-class> <body-content>empty</body-content> </tag> </taglib> |
これ考えたやつは何なの?馬鹿なの?死ぬの?
JSPは使ってはいけない、ELだけでは何もできないということで、さらにJSTLという拡張タグライブラリが作られました。
JakartaTaglibsというタグライブラリが一般的に使われているのでそれを使ってみましょう。
何故か最初から入っていないのでダウンロードしてくる必要があります。
http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi
解凍したら、中に入っているlib/jstl.jarとlib/standard.jarを、WEB-INF/libディレクトリにコピーします。
その後、タグライブラリを使用するJSPに
<%@taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>
と書くと、JSPでライブラリを使用することができます。
何故実際にライブラリが入っている場所ではなくて変なURLを書かねばならないのかはまたいつか。
とりあえず使ってみましょう。
<c:out value="あいうえお">
ただでさえ<%out.println>、<%=>、${}と同じようなものがいっぱいあるのにまた増えるの?
また増えました。
特徴は
<c:out value="${sessionScope.data1}" default="データがありません" escapeXml=true>
というふうに何も無かった場合のデフォルトを設定できることと、HTMLタグのエスケープができることです。
これだけやってようやくhtmlspecialcharsに肩を並べることができました。
<c:set var="data2" value="<b>value2</>" scope="page">
setAttributeと何がちがうの?
さあ?
<c:import url="include.jsp" context="/example2">
<%@include file %><jsp:include page>があるのにまた増えました。
上記と違う点は、別のコンテキストに存在するJavaアプリケーションを取り込めることです。
もっともそんなことをしなければならない時点で設計を失敗しているわけですが。
以下は選択及び繰り返し処理の例です。
<c:forEach items="[1,2,3,4,5,6]" var="var" varStatus="varStatus"> |
Javaに限りませんが、Webプログラミングは、デザインからロジックを排除して排除して、その結果何もできなくなるのでこうやってスクリプトっぽいことを追加して、それでまたロジックがデザインに入り込んでくる、そんなことを何度も繰り返しています。
JSTLもまたそのような道に入り込んでいるように見えます。
これまで覚えてきたJSPはどうなるの?
ゴミです。
elsample.jsp
<html> <head> <title>計算</title> </head> <body> ${100 * 100} ${applicationScope.data} </body> </html> |
計算結果の10000が表示されます。
アプリケーションスコープにセットされている変数dataの内容が表示されます。
見てのとおり、Smartyそのものです。
簡単ですか?
とんでもない。
HelloWorld.jsp
<% String a="Hello,World"; %> <html> <head> <title>Hello, World!!</title> </head> <body> ${a} </body> </html> |
何も表示されません。
pageContext.setAttribute("a",a);
と、わざわざスコープにセットしないと、同一ページ内なのに読み取ることができません。
では問題です。
<% pageContext.setAttribute("a","pageContext"); request.setAttribute("a","request"); session.setAttribute("a","session"); application.setAttribute("a","application"); %> <html><body> ${a} </body></html> |
なんと表示されるでしょう。
正解はpageContextです。
ELは、ページスコープ、リクエストスコープ、セッションスコープ、アプリケーションスコープの順で勝手に検索して、最初に見つかったオブジェクトを表示してくれます。
楽といえば楽ですが、別のスコープに対してうっかり同じ変数名を使ってしまうと誤動作してしまう可能性があります。
この場合、セッションスコープの変数を取得したければ${sessionScope.a}と記入します。
Javaの思想のひとつが厳密なスコープ管理だったはずなのですが、こんな中途半端にアバウトな仕様が混入したせいで台無しな趣があります。
ELの特徴のひとつとして、Map等連想配列の取り扱いがあります。
取得だけとはいえELではJavaScriptやPHPと同じくらい簡単に連想配列を扱うことができます。
${map.key}
${map["key"]}
${map['key']}
とJavaScriptそのものの書き方ができます。
連想ではない配列も、
${array[0]}
${array["0"]}
${array['0']}
で取得することができます。
何故か${map[key]}とか${array.0}とは書けません。
配列ではなくMapオブジェクトと呼べとかそういう異議は受け付けない。
実際ELだけでなにもかもを表示するのは、Smartyですべてを表示しようとするのと同じく却ってわかりにくくなるだけなので、結局のところJSPなり他の表現手段なりを併用することになります。
前回、いきなりout.printlnという命令を書いていましたが、このoutはメソッドを見てわかるとおりPrintWriterオブジェクトです。
Javaサーブレットでは毎回毎回PrintWriter out = response.getWriter();と書く必要がありましたが、JSPでは何の宣言も無くいきなり使用しています。
どうなっているのでしょうか。
JSPでは、面倒なクラスのインスタンス化宣言をしなくてすむように、よく使うクラスは最初から自動的にインスタンス化されています。
つまり、こちらの目に見えないところでJspWriter out = pageContext.getOut();が実行されているということです。
これらはJSPの暗黙オブジェクトと呼ばれ、out以外にも最初からインスタンス化されているオブジェクトが幾つか存在します。
前回使ったところではapplication、これは見てのとおりアプリケーションスコープのことで、pageContext.getServletContext()を示します。
applicationに突っ込んだ変数は、Javaサーブレット同様どのファイルからでも参照することができます。
それ以外に有用なところとしては文字通りセッションスコープを示すsession、リクエストとレスポンスを示すrequestとresponseがあります。
他にも何種類かありますが、どうせ使わないので覚えなくていいです。
データをDBに保存する等の永続化の必要がないサイトであれば、概ね暗黙オブジェクトだけで作成することができるでしょう。
ところでこれまでアプリケーションスコープにいろいろなデータを突っ込んできました。
アプリケーションスコープの利点は各サーブレットで簡単にデータを共有できることですが、逆に言うとこれがアプリケーションスコープの欠点となります。
Javaサーブレットで作成したカウンター
http://yuubiseiharukana.blog.shinobi.jp/Entry/26/
JSPで作成したカウンター
http://yuubiseiharukana.blog.shinobi.jp/Entry/28/
同時に動かしてみるとわかりますが、両者で変数strCntが共有されています。
これが一人で作成しているのであれば変数名を変更するだけですみますが、これが複数人で開発しているシステムだったら、あるいは一人で行っていても規模が大きくなってきたら把握しきれるものではありません。
両カウンターともstrCntのパース時に例外処理を行っていないため、誰かが変数strCntに文字を入れたりしたらエラーが発生してしまいます。
場合によってはクラックの突破口となってしまうかもしれません。
Javaにはvar_dumpのような便利関数も存在しないため、どの変数が何処で使われているのかといった情報を調べるのも簡単なことではありません。
面倒なことになる前に面倒なことになる危険性のある行為はやめましょう。
最後にいろいろなスコープに値を突っ込んだ場合の取り出し方を見てみましょう。
どのようなスコープに変数をどうやって突っ込んで、どうやって取り出せばいいかがわかると思います。
セッションやアプリケーションスコープについてはこの例ではあまり意味がないのですが。
ページスコープは本当に1ページだけしか値を保持してくれないので、一回のリクエストで完結するならリクエストスコープ、繋がっている間データを保存したいならセッションスコープが適当です。
ELについてはまた次回。
src/xxx/scopetest.java
package xxx; import java.io.*; public class scopetest extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String strContext="strContext"; //アプリケーションスコープにセット //セッションスコープにセット //リクエストスコープにセット //指定したページをインクルードします |
/jsp/scopetest.jsp
<%@ page contentType="text/html;charset=Shift_JIS" %> session.getAttributeで呼んでみる<br> application.getAttributeで呼んでみる<br> EL:pageScopeで呼んでみる<br> EL:requestScopeで呼んでみる<br> EL:sessionScopeで呼んでみる<br> EL:applicationScopeで呼んでみる<br> EL:いきなり呼んでみる<br> </body> |
Javaサーブレットは結局のところJava言語そのものであり、たかだかWebページを作成するためにJavaサーブレットを用いるというのは鶏を割くのに牛刀を用いるようなものです。
他にも、ちょっとでもソースを変更するたびにコンパイルが必要だったり、ヒアドキュメントが無いので文字表示を行うたびに
response.getWriter().println("Hello World");
等面倒なコードを書かなければならなかったり、デザインとロジックの分離がまったく出来ないという問題点もあり、Webアプリとしてはお話になりません。
また複数言語で同じ処理を行うコードの例を載せているところがありますが、だいたいJavaサーブレットだけコード量が1.5倍から2倍くらいになっています。
いくら理念が優れていたところで現実的に面倒なら使う意味がないのが世の常です。
その欠点を解消すべく登場したのがJSPです。
例によってとりあえずHelloWorldと書いてみましょう。
配置場所はWebContentフォルダ内です。
HelloWorld.jsp
<html> <head> <title>Hello, World!!</title> </head> <body> <% out.println("<p>Hello,World!!</p>"); %> </body> </html> |
PHPやASPにしか見えません。
JSPには、サーブレットで面倒だったコンパイルやクラスファイルの配置が一切必要ありません。
普通にHTMLを作成する感覚で作成することができます。
また、必要となるクラスのインポートやHttpServletの継承、一部オブジェクトのインスタンス化等も自動的に行ってくれるうえ、ややこしいweb.xmlの記入も基本的に必要ありません。
どうして最初からこれらの機能を搭載しなかったのか不思議でなりません。
内部的には、JSPは初めて呼び出されたときに自動的にJavaサーブレットに変換され、さらにコンパイルされてクラスファイルとして実行されます。
では今後JavaサーブレットはすべてJSPに置き換えてしまえばいいかというと全くそういうことはありません。
JSPだけだと却って面倒になったり見難くなったりするからです。
最後に、例によってJavaサーブレットでも行った簡単カウンターをさくっと作ってみます。
counter.jsp
<%@ page contentType="text/html;charset=Shift_JIS" %> |
Javaサーブレットは文字コードのことを全く考えません。
考えないだけならまだしも、何もしなければ勝手にヘッダにISO-8859-1という文字コードを指定してしまいます。
ISO-8859-1は日本語を扱えないので、日本語を表示するためには、あらゆるサーブレットで毎回必ず
req.setCharacterEncoding("Windows-31J");
res.setContentType("text/html; charset=EUC-JP");
とヘッダを上書きするコードを書かなければなりません。
また、ショッピングサイト等、必ず上や左にメニューが付いてくるページがありますが、それを実現するためには全てのページでメニューを書かなければなりません。
Java以外の言語を使用しているサイトでは、メニュー部分を別に作成しておき毎回インクルードする、入口を一本化してパラメータで内容を振り分ける等の仕組みで実現しています
Javaでも同様にincludeする仕組みがいくつもあるのですが、それ以外にもJavaアプリケーションには最初からJavaサーブレットに横断的にフィルタを実装する仕組みがあるので使ってみましょう。
とりあえずweb.xmlから。
<filter> <filter-name>charsetFilter</filter-name> <filter-class>charsetFilter</filter-class> </filter> <filter-mapping> <filter-name>charsetFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
servletとほぼ同じ指定方法です。
servletはurl_patternにマッチしたJavaサーブレットを実行したあと終了しますが、filterは該当するJavaサーブレットを実行した後も、終了せずにservletをそのまま続けて実行します。
またservletは複数のURLにマッチした場合優先順位の高いほう一つしか実行されませんが、filterはマッチするサーブレットすべてが実行されます。
横断的に処理を行うのにうってつけのクラスです。
url-patternには前回までのような直接指定のほかにワイルドカード*が使えます。
Linuxの*ではなくMS-DOSの*なので注意が必要です。
<url-pattern>/*</url-pattern>と指定することで、すべてのクラスにフィルタを適用することができます。
ではfilter-classで指定したcharsetFilterを作成しましょう。
charsetFilter.java
import java.io.*; public class charsetFilter implements Filter { |
フィルタを行うクラスはFilterインターフェイスをimplementsしなければなりません。
Filterインターフェイスにはinit、destroy、doFilterの3つのメソッドが定義されていますが、initとdestroyは使わないので放置します。
<filter>が呼び出されるたびにdoFilterメソッドが呼び出されるのでこれを上書きすることでフィルタを実装します。
このdoFilterメソッドでは文字コードを指定しています。
全てのサーブレットは自動的にこのcharsetFilterクラスを通って実行されるので、今後すべてのサーブレットでsetContentTypeを書く必要がなくなります。
たとえば認証処理を行いたい場合、Javaサーブレット以外の言語では、すべてのファイルで認証用のルーチンを呼び出したりということを行わなければなりません。
Javaサーブレットでは、認証処理を行いたいディレクトリをweb.xmlに指定し、認証を行うサーブレットを書けば、該当するディレクトリに入っているサーブレットは自動的に認証処理を呼び出してくれます。
doFilter内で再度doFilterを呼び出していますが、前者はjavax.servlet.FilterインターフェイスのdoFilterで、後者はjavax.servlet.FilterChainインターフェイスのdoFilterであり、この両者は違うものです。
名前が同じわりに引数も違います。
FilterのdoFilterメソッドにはそこで行う処理を記述しますが、FilterChainのdoFilterメソッドは、次に当てはまる<filter>のdoFilterメソッドを実行します。
次に当てはまる<filter>が無ければ<servlet>を実行します。
よくわからなければとりあえずこう書くものだとだけわかっていればいいです。
最後に、企業や携帯サイトのように必ず上下にヘッダ、フッタを出力するフィルタを作成してみます。
headerFilter.java
public class headerFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { PrintWriter out = response.getWriter(); out.println("ヘッダ<hr>"); chain.doFilter(req, res); out.println("<hr>フッタ"); } public void init(FilterConfig config) throws ServletException{} public void destroy(){} } |
web.xml
<filter> <filter-name>charsetFilter</filter-name> <filter-class>charsetFilter</filter-class> </filter> <filter> <filter-name>headerFilter</filter-name> <filter-class>headerFilter</filter-class> </filter> <filter-mapping> <filter-name>charsetFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>headerFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
フィルタの実行順は、<filter-mapping>の順番です。
文字コードを設定するcharsetFilterサーブレットを先に書く必要があります。
前回までに作成した適当なサーブレットを呼び出してみると、当のサーブレットはまったくいじっていないのに、ヘッダとフッタが勝手に追加されているはずです。
doFilter部分が次のフィルタ、およびJavaサーブレットを実行している部分なので、それより下に処理を書くことでフッタ部分を設定することもできます。
このようにサーブレットを横断する処理を作成できるのが、Javaサーブレットの数少ない利点の一つです。
また、<url-pattern>に適合する限り、そのうち出てくるJSPにも適用されます。
これは便利な機能だ、と思いきやgetServletContext().getRequestDispatcher().forwardでJSPにフォワードした場合のみフィルタが適用されないという例によって意味のわからない仕様になっています。
counter.java
import java.io.*; |
一番意味のわからない型変換は気にしないとして、リロードすると値が増えていきます。
別のPCからアクセスしてもカウンターの値は共通しています。
ファイルに保存していないカウンターの値が何故保持されるのか、それはJavaサーブレットの動作方法に理由があります。
HTTPやPerlといった旧来のWebコンテンツはサーバ上で常に待機しているわけではなく、リクエストがあった場合にサーバから呼び出され、プログラムを実行するなりHTMLファイルを返すなりしたらそのまま終了してしまいます。
プログラムを終了してしまうのですから、それまでメモリ上にあった値はすべて破棄されます。
必要な情報はファイルやDB等に保存しなければなりません。
それに対しJavaやPHPといった最近のWebコンテンツは、サーバ上で常駐し、リクエストがあると常駐しているプログラム内部で処理を行います。
プログラムを終了しないので、メモリ上の値はずっと保存されます。
中でも特に、アクセスしてきた者全員で共有できるスコープをアプリケーションスコープといいます。
web.xmlでは<context-param>で、JavaサーブレットではServletContextで、JSPではapplicationで、ELではapplicationScopeとinitParamでアクセスすることができます。
JSPやELについてはまたそのうち。
アプリケーションスコープに突っ込むだけで簡単に情報を共有できるので、ファイルに保存せずに掲示板等が作成できてしまいます。
bbs.java
import java.io.*; |
サニタイジングとぬるぽ対策以外は何もやっていない非常にいい加減な、とりあえず動けばいい掲示板です。
サーバの再起動等があると当然メモリ上のデータもすべて消えてしまいます。
簡易チャット程度ならいいですが、長期にわたりログを保存する場合は、やはりストレージに記憶することになるでしょう。
次回はリスナーについて、はどうせ使わないからいいか。フィルタの話でもしましょうか。
どうせ後で嫌というほど混乱するので、Javaアプリケーションのスコープについて今は簡単に解説しておきます。
一番広いのがアプリケーションスコープという単位です。
範囲はexampleパッケージ全体であり、いわゆるグローバル領域よりも広い範囲です。
ここに保存された値は、サーバが再起動されるまで保持され続け、誰からでも参照できるようになります。
次がセッションスコープで、いわゆるセッションです。
次がリクエストスコープで、一回のサーブレットの呼び出しの単位で、他プログラミング言語で言うところのグローバル領域です。
最も小さいのがページスコープであり、プログラム一本に値します。
リクエストスコープとページスコープの違いは、サーブレットから他のプログラムを呼び出したとき等に呼び出し先に値が渡されるか否かということです。
前回や今回のようにプログラム一本で完結する場合、両者は同じ意味です。
それを踏まえたうえで初期化パラメータの設定と取得を行ってみましょう。
初期化パラメータとは、サーバ全体、あるいはサーブレット全体に共通して設定しておきたい値を保存しておく場所です。
例えばDBのアドレス、ログインIDやパスワード等です。
WebContents/WEB-INF/web.xml
<web-app> <servlet> |
<web-app><context-param>で設定するのはアプリケーション単位で、どのサーブレットからでも参照することのできる値です。
<web-app><servlet><init-param>がサーブレット単位で、この場合HelloWorldサーブレットからしか参照できません。
ユーザ側から見た場合わけのスコープに対して、初期化パラメータはサーバ側から見た場合わけなので、init-paramに対応するスコープはありません。
次に取得側。
index.java
import java.io.*; |
getServletContextメソッド、getServletConfigメソッドはそれぞれServletContextインターフェイスおよびServletConfigインターフェイスを呼び出しています。
ServletContextインターフェイスのgetInitParameterメソッドでcontext-param初期化パラメータを、ServletConfigインターフェイスのgetInitParameterメソッドでinit-param初期化パラメータを取得することができます。
取得したいパラメータのスコープによって、取得するのに必要なインターフェイスが変わりますので注意が必要です。
例えばgetServletContext().getInitParameter("i-paramater");とすると、アプリケーションスコープのc-paramaterを取得しに行きますが、アプリケーションスコープのi-paramaterには何もセットされていないのでnullが返ってきます。
次はセッションスコープです。
import java.io.*; |
HttpSession.getSession()で自動的にセッション管理を行ってくれます。
この場合、最初の一度だけ新しいセッションと判断され、以後は何度アクセスしても古いセッションとなります。
しかしスコープがセッション単位なので、ブラウザを立ち上げなおせばまた新しくなります。
リクエストスコープとページスコープについてはJavaサーブレットだけでは少々面倒なのでJSPで行うことにします。