今までおいらが思いつかなかっただけで常套手段な気もする…。
…けど他で見ないし…といった感じのメモです。
List の場合は AbstractList 、 Map の場合は AbstractMap のように AbstractXXX と名前の付くクラスがある。これらは、主に各インタフェースの実装補助に使われる。…で、今日、"監視コレクション"を作っていて、他にもメソッドの集中管理のための使われ方がある気がした…。
まず、実装補助について。AbstractXXX はクライアントのための補助的なメソッドを、幾つかのコアメソッドを使って提供する。そのため、実装に必要な作業はこれらのコアメソッドの提供だけになる。
次に、メソッドの集中管理について。
順を追って説明すると、こうした使い方を考えるきっかけは、上でも触れたように監視コレクション(Observable Collection)の実装を考えていた時。監視コレクションとは、要素が追加や削除される時にそのお知らせ(追加イベント、削除イベント)を希望したクライアントに流すコレクションの事。で、この監視コレクション、実は Java SE 6 では提供されていない…。何故提供しないのかと言うと…、
Java コレクション API の設計に関する FAQ
- 修正されたときにイベントを送出する被監視コレクションを提供しないのはなぜですか。
- 主に、リソースの制約によります。そのような API に取り組むのであれば、万人に役立つものでなければならず、また長期の使用に耐えるものでなければなりません。このような機能も提供していく予定です。それまでの間、public の API の上にそのような機能を実装するのは難しいことではありません。
という事らしい。…前半はそうか…色々事情があるのか…あんたも大変だな…と思う部分だけど、引っかかったのは、public の API の上にそのような機能を実装するのは難しいことではありません。
の部分。ん?そんなに簡単に出切る?と一瞬フリーズ…。
例えば、追加イベントを提供したい場合、追加を行うためのメソッドは複数ある。コレクション本体からの add や addAllメソッド、iterator側からの addメソッド。で、重要なのは、メソッドは他のメソッドに処理を丸投げしてるかもしれないし、それぞれ個別に処理を行っているかもしれない。継承を使うにせよ委譲を使うにせよ、継承先や委譲先の実装は知ることが出来ない(知るべきでない)ため、これらを知ることは出来ない。そのため、どのメソッドを監視すればいいのかも分からない。委譲の場合、全メソッドを監視すればいいんだけど、それも AbstractXXX を一から作るのと同じ位の手間が…。
[ 怠けて監視漏れが出るケース ]
と思ったのもつかの間、AbstractXXX をラッパに使えばいいと気づく!そうすれば、AbstractXXX のコアメソッドを基に全ての処理が構築される。そこさえ監視しておけば、委譲先がどんな処理をしていても気にする必要が無い。めでたし、めでたし。
思うに、こうした使われ方を考慮すると、"インタフェースの完璧な実装をクライアントに提供出来るのなら、AbstractXXX が必要ない" という事は無くなる。たとえインタフェース内のメソッドを完璧に実装したクラスがあったとしても、クライアントはそこに監視系の機能を追加する可能性がある。そうなると、メソッドの集中管理が必要になるため、AbstractXXX が役に立つ。なので、そうした可能性が高い場合は、予めインタフェースと共に AbstractXXX も提供しておくと親切かも…ってメモ。
勘違いから変な事を思い悩んだのでメモ…。
マルチスレッド化可能なクラスを作ってて、内部で使ってる同期の仕組をクライアントに公開し…クライアントと同期の連携を取る。…という事を考えた。で、それにはLockを公開すればいいのか、synchronized用のモニタを公開すればいいのか?…どっちなん?と。
で、もうたぶん、スタートがおかしいんだけど…泣…そこから…以下の様に発想…。
- Collections.synchronizedCollection(c)はthisのモニタで連携を取れと書いてる…?
- でも、そんなのはクライアント側の好みや都合があるから、好きな方を選べるようにすべき。
- synchronizedかLockか選べるようにするには…意外難しい…?
- ロックの取得と解放から本体処理への流れをメソッド化…??
- で、それを…
- オーバライド出来るようにする…???
- コンストラクタで指定したフラグでロックの方針を切り替える…???
- どっちも激しく変!
結局、まず間違いは、内部で使う不変条件を保つための同期と外部で使う複数のメソッド呼び出しを繋ぐための同期を連携という名の元に混ぜてしまった事。こいつらは確かに混ぜる事は出来るけど、出来るからといってすべきじゃない。分けた方が各同期用オブジェクトの意味がはるかに分かり易くなる。分けるとだいたいこんな感じになる。
- 内部
- その場に適した同期方式を使う。Lockにしろモニタにしろ、protected又はprivateにし、外に公開しない。
- 外部
- 同期ラッパーと手動の同期方式を組み合わせる。大事なのはその両方で同じ同期方式を使う事だけ。Collections.synchronizedCollection(c)ではモニタを使う方針だけど、気に入らなければLockを使うラッパーとそれを使った手動の同期でも構わない。
使用に当たっては、外部の対策だけでもオールラウンダーになれる。逆に内部の対策だけでは無理なのでラップが必要。ただ、内部のみの場合、ロック時間が短いのでその分優位。で、結局、大事なのは、内部でも外部でもLockだろうがsynchronized用のモニタだろうが、お好みの同期方式を使えたって事…。
うーん、微妙な解釈の違いが蓄積して最後は意味不明に…か。例えるなら、仕様書の森での遭難…かな。…ん?…全然うまく言えてない?……もう寝よう…はぁぁ…。
複数のプログラムを協調動作させる場合、単体プログラムを動かす場合と違ってちょっと特殊なセキュリティの問題がある。単体の場合、とても単純。書いた人が信用できるなら、そのプログラムに何でも出来るように権限を与えて動かせばいい。もし信用できないなら、パソコン上の重要なファイル等は読み書きさせないように制限すればいい。これが複数になると事はこう単純には行かなくなる。例えば、信用ある会社の書いたプログラム S(safety) がダウンロードしたネット上のどっかの誰かが書いたプログラム W(worning) と協調動作する場合を考える。
- W 内からは重要な資源にアクセス出来なくする。(失敗)
- この方法はWがSを呼び、Sに重要な資源にアクセスさせる可能性がある。 Sがそのような資源にアクセスするかは、Sが複雑なプログラムだった場合、W から S への呼び出しを見張るだけでは判断できないため、この方法は使えない。
ここでのセキュリティの破り方は誰か権限のある人に悪さをしてもらう構図。…まるで政治家と企業の癒着のよう…。お金の流れが複雑になると、献金の種類等で単純に規制してもうまくいきにくい所も似ている…。けど!Javaはもっとかしこい…振り込まれた口座から資金の出所を確認するまでいちいち一つ一つ追いかける…マルサのJava!! つまり、スタックトレースを調べ上げ (もしくはそれと同等の事をし)、呼び出しの背後関係を洗い出し、その中に一つでも怪しいプログラムがあればエラーにする。
…前回の記事の追記に、、、間違い発見!!TypedKeyがimplements Serializableとあるけど、同一性の判定がリファレンス判定(==演算子)だから、シリアライズ前後で同一性が崩れる。普通のクラスならこれでいいんだけど、キー専用クラスでこれはひどい。しかも、finalになってるからequalsメソッドのオーバライドのしようがない。
で、解決策を考えたんだけども…あんましいー案が無い…。final取って、デシリアライズ時に前と同じインスタンスの作り方をするよう個々のメソッドをオーバライドする事を考えたんだけど…。楽しようとしてそれを匿名クラスでやろうとすると、Javaの仕様的にまずい事になる*1。つまり、いちいちstaticクラス(内部クラスも可)を宣言して、SerialVersionUIDをつけて…ん?もうデシリアライズ用のメソッドいらないか、、、でもどのみち面倒…。もうぶっちゃけ振り出しに戻ってStringでいいかも…とも思うけどでもそれはそれでちょい危険だし、、、う〜ん…。
さてさて!!、日本のいい言葉に、「我が振り見て人の振り直せ」という言葉がある。これは自分が失敗した時には、同じ失敗をしている人を見つけて、その人の失敗を指摘してる間に自分の失敗をうやむやにしてしまおうという、素ん晴らしい言葉だ。そう、おいら以外にもインチキSerializableを作っているライブラリを発見!!
Commons FileUpload (お世話になっております) のDiskFileItemクラスがそれだ。ファイルアップロードの場面で確認画面とかを出そうと思ったら一旦ファイルをセッションやらDB側で保存しないといけない。しかも、ユーザは結局そのファイルを使わずどっか行ってしまうかもしれない。そうなるといつ消されるとも分からないファイルが残る。セッション破棄イベントでそれらを消すにしても、ドSなユーザがファイルを沢山アップロードをしたらば、もうお終いになる。(量に制限をかけて、古いファイルを消す手もあるけど、ユーザ数が多すぎてセッション自体を使いたくない場合は本当にお手上げ) と、まぁそんなこんなで、ファイルデータの入ったDiskFileItemをビューステートに入れといてサーバリソースを節約しようとしたら、悲劇! 一時ファイルが読めない!Servletスレッドが終了したら、どっかのタイミングで一時ファイルが消されるらしい。
…まぁ当然と言えば当然なんだけど。なんでこんな当たり前に気づかなかったかと言うと、ドキュメントにimplements Serializableってあったから。そう、ドラマで言うなら、「何か変だと思ったけど信じたの!」 「できないなら最初っから出来ないって言えばいいじゃない!」 「何でウソつくの!?」 「もーいい!自分でやるわ!」 って展開の主人公になる…そうちょっと理不尽。 …ま、…おいらのケースよりはいく分ましだけど…。:-P)
- *1
- 匿名クラスの名前の付け方はコンパイラによって違い、さらにstaticフィールドを付けられないので、serialVersionUIDも宣言できない。これではクラスの特定が出来なくなる。ちなみに、ローカルクラスと非static内部クラスも親クラスインスタンスへの暗黙フィールドの名前の付け方がコンパイラによって差があってシリアライズの障害になる。
あるフレームワークが文脈情報をあっちからこっち、こっちからあっちと運んでいる時、自分の作ったデータも一緒に運んでもらいたい時がある。そう、無人島で遭難した時に、海流に乗って流す小瓶みたいなもん。たぶんフィリピン位で流すとだいたい黒潮に乗って日本にやって…くる…らしい…?
動的な型付けを持つ言語では、これは簡単で単に文脈情報にプロパティを追加すればいい。これで普段通り。なんだけど、Javaのような静的型付けを持つ言語は、Mapを使う事になる。ServletやJSFでのリクエスト属性やセッション属性がこれに当たる。で、何が嫌かと言えば、Mapにはいくつか不便な点があって、普段通りプロパティにアクセスするのと大きく違ってしまう。
- まず、Mapのキーが文字列の場合。そう、さっきのビンに「鈴木太郎です。遭難しました。」と書いても、一度に二人以上、鈴木太郎さんが遭難してたらば、もう一方の関係者は激怒するに決まってる。何故、社会保障番号を導入しないんだ!!と。…まぁJavaでは大概FQCNという、一意になる名前を使うけど、もしかすると何も食べてないせいで自分の名前すら間違うかもしれない。
- 次に、仮に全世界社会保障番号みたいなのを書いたタグを持っていたとして、それをさっきのビンに入れたとして今度は、「Oh!! 何て書いてますか?ワカリマセ〜ン!! Sorry!!」 「HaHaaa!!、ジョーン、島の地図があるじゃないか!それはその島の観光協会のCMさ〜!」 「Oh!! But!! ハワイ is No.1!だからイリマセーン!」って事になりかねない。
つまるところ、一意性のあるキーとその取り扱い(型)を示す情報が必要だと思うわけ。これがないとせっかく静的な型付け言語なのに、動的な型付け言語と同じように気をつけて情報を取り扱う事になる…。


