はじめに
ReflectionパッケージのProxy
を利用すると、指定したインターフェイスに対して、そのインターフェイスを実装したクラスとインスタンスを動的に作ることができます。
本稿では、Oracle JDBCドライバの制約をProxy
を利用して回避する方法を示すことで、Proxy
の具体的な利用方法を説明します。
Oracle JDBCドライバはJ2SEのAPI規定と異なり、GCによるConnection
やStatement
の自動クローズを行いません。そのため、これらのJDBCオブジェクトを自動的にクローズする他のJDBC実装用のコードを流用するとリソースリークの原因となります。これを回避するには、アプリケーションがすべてのStatement
などのオブジェクトをクローズするか、またはミドルウェアなどでアプリケーションが作成したすべてのJDBCオブジェクトを保持しておき、なんらかのタイミングでクローズするように実装しなければなりません。
JDBCのConnection
やStatement
はインターフェイスですので、Proxy
の適用が可能です。このため、簡単なハンドラクラス(Proxy
からの呼び出しを処理するクラス)を用意することで、後者の方法を実装できます。この場合、アプリケーションにとってはインターフェイスが透過なため呼び出し時の特別な考慮を必要としません。
本記事では、上記のシナリオに沿ってJDBCのインターフェイスに対するProxy
を生成し、Connection
オブジェクトのクローズ時にそのConnection
オブジェクトが生成した未クローズのJDBCオブジェクトを自動的にクローズする、Oracle JDBCドライバの制約回避処理の実装方法を示します。
対象読者
本記事は、Javaプログラミングの初級者から中級者を対象に、java.lang.reflect.Proxy
の利用方法、Proxy
によって既存のクラスへ処理を追加する方法、およびそれによって得られる効果について説明します。
また、スタブを利用したユニットテストプログラミングについても解説します。
必要な環境
本記事のソースをビルド/実行するにはJ2SE 5.0を利用してください。また、antのビルドスクリプトを添付しているので、一括してビルドするにはant 1.6以上を用意すると良いでしょう。
参考までに筆者が利用した本記事のテスト環境は以下のものです。
OS | OS X 10.4.2 |
J2SE | 1.5.0_02 |
Ant | 1.6.2 |
JUnit | 3.8.1 |
IDE | NetBeans4.1J |
なお、元々存在するインターフェイスの実装などを行っているため、ビルド時にdeprecatedメッセージなどが出力されますが、実行には問題ありません。
NetBeans 4.1からの利用
メニューから[ファイル]→[新規プロジェクト]→[プロジェクト―既存のAntスクリプトを使用するJavaプロジェクト]を順に選択し、[次へ]ボタンをクリックします。次に[場所]の[ブラウズ]ボタンをクリックし、ファイルオープンダイアログを表示します。アーカイブを展開したディレクトリの「proxy」ディレクトリを選択し、[開く]ボタン(OS Xの場合は[選択]ボタン)をクリックします。この操作で、[構築スクリプト][プロジェクトフォルダ]が自動的に設定されるため、残る[プロジェクト名]に適当な名前(「proxy」など)を入力し、[次へ]ボタンをクリックしてください。
一般的なメニュー項目のうち、[プロジェクトをテスト]に「junit」を設定します。[次へ]ボタンをクリックし、[ソースパッケージフォルダ]に「src」、[テストパッケージフォルダ]に「test」をそれぞれ追加し、[次へ]ボタンをクリックして完了します。この時、[ソースレベル]は[JDK 1.5]を選択します(デフォルト)。
以後、「build.xml」を右クリックすると表示されるコンテキストメニューで、[ターゲット実行]→[その他のターゲット]をクリックすると、「build.xml」に定義されている各種ターゲットをNetBeans上で実行できるようになります。
ファイル構成
ダウンロードしたファイルはzipで圧縮してあります。展開すると「proxy」というディレクトリを頂点としたディレクトリ階層ができます。すぐに実行できるようにコンパイル済みのクラスファイルも添付してあります。また、ソースファイルはすべてシフトJISでエンコードしています。
proxyディレクトリ
- 「build.xml」
antのビルドスクリプトです。
junit.home
プロパティの値は「junit.jar」が配置されたディレクトリ名に変更してください。以下のターゲットがあります。- デフォルト(build):クリーンアップ後に「src」ディレクトリのプログラムをビルド
- prepare:ビルドに必要なディレクトリを作成
- clean:ビルドしたクラスファイルを削除
- compile:srcディレクトリ下のソールファイルをコンパイル
- junit:
ProxyConnectionTest
とStubGeneratorTest
をビルドして実行
proxy\src\com\example\proxyディレクトリ
- 「ProxyConnection.java」
Proxy
を利用してConnection
のclose
呼び出し時に、Statement
、ResultSet
のclose
漏れを検証するユーティリティ。もし未クローズならば代わりにclose
メソッドを呼び出す。
proxy\src\com\example\toolディレクトリ
- 「StubGenerator.java」
ユニットテスト用のスタブクラスを生成するユーティリティプログラム。
proxy\test\com\example\proxyディレクトリ
- 「ProxyConnectionTest.java」
Statement
、ResultSet
のclose
テストプログラム。
proxy\test\com\example\toolディレクトリ
- 「StubGeneratorTest.java」
多次元配列の型情報の生成テストプログラム。
解説
Proxy
proxy(プロクシ)とは、「代理人」や「代用品」、あるいは「……の代理」という意味の言葉です。コンピュータの世界だと、HTTPサーバーへのリクエスト/レスポンスの中継を行うプロクシサーバーが一番有名かも知れません。
java.lang.reflect.Proxy
は名前の通り、指定したインターフェイスを持つオブジェクトを偽装することで、ユーザーが指定したハンドラへクライアントからの呼び出しを中継します。
図は、インターフェイスを介したクライアントとオブジェクトの関連を示したものです。コードで示すと、たとえば次のようになります。
public void foo(Map map) { map.put("a", "b"); }
上のリストで、クライアント(foo
メソッド)は、Map
インターフェイスを実装したオブジェクトを直接利用しているわけではなく、あくまでもMap
インターフェイスを介して利用しています。ただし、インターフェイスはオブジェクトではなく、あくまでもメソッド定義の集合なので実体があるわけではありません。したがって、クライアントのメソッド呼び出しは、実行時には直接オブジェクトに対して行われます。その意味では、インターフェイスは設計時の文字通りインターフェイスの定義と、コンパイル時のスタブの役割しかないと言っても良いでしょう。
Proxy
はインターフェイスに対して実体を提供します。
図のようにProxy
クラスは、特定のインターフェイスを偽装して(あるいは実行時に動的に実装して)クライアントからの呼び出しをユーザー定義のハンドラオブジェクトへデリゲートします。
Proxy
クラスに特定のインターフェイスを実装したオブジェクトを生成させるには、次のリストのようにProxy.newProxyInstance
staticメソッドを利用します。
foo((Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { if (method.getName().equals("put")) { System.out.println("key=" + args[0] + ", value=" + args[1]); return null; } throw new UnsupportedOperationException("only put can handle"); } });
リストはMap
インターフェイスを実装したProxy
オブジェクトの生成方法を示したものです。
Proxy.newProxyInstance
メソッドの引数は
- Proxyオブジェクトを定義するのに利用するクラスローダ
通常はこの例のように偽装するインターフェイスのクラスローダを指定すれば良いでしょう。
- 偽装するインターフェイスの配列
Proxyは同時に複数のインターフェイスを実装することができます。
- InvocationHandlerのインスタンス
クライアントからの呼び出しを受け取るユーザー供与のオブジェクトです。
の3つです。
このうち3番目の引数として与えるInvocationHandler
インターフェイス(invocationはメソッドの呼び出しの意味)は、3引数のinvoke
メソッドを定義したイベント受信用インターフェイスです。Proxy
オブジェクトはクライアントからの呼び出しを受けるとInvocationHandler#invoke
メソッドを呼び出します(図)。
InvocationHandler#invoke
メソッドの引数は
- Proxyオブジェクト
同一ハンドラで複数の
Proxy
オブジェクトの通知を受ける時の識別に利用できます - Methodオブジェクト
クライアントが呼び出したメソッド
- Object配列
クライアントがメソッドに与えた引数の配列
の3つです。また戻り値は、クライアントが呼び出したメソッドの戻り値と、型を一致させる必要があります。もしvoid
型メソッドの場合にはnull
を返します。この時、戻り値の型が一致しないとClassCastException
がスローされます。また、invoke
メソッドからスローした例外が呼び出されたメソッドで宣言していないチェック例外の場合にはUndeclaredThrowableException
がスローされます。
Proxyの利用用途
Proxy
を利用すると、比較的少ない手数で以下のデザインパターンのインスタンスを生成することができます。
- decorator パターンの実装
Proxy
オブジェクトからのメソッド呼び出し通知を内包するオブジェクトへデリゲートする前後で、新たな機能を追加します(図を参照)。 - adapter パターンの実装
クライアントが利用したいインターフェイスの
Proxy
オブジェクトを生成し、InvocationHandler
内でクライアントからの呼び出しを実際のオブジェクトの呼び出しへ変換します。 - proxy パターンの実装
Proxy
オブジェクトからのメソッド呼び出し通知を内包するオブジェクトへデリゲートする前後で、必要なアクセス制御を行います。
なお、筆者には正直なところ本記事の目的の実装がdecorator パターンなのかproxy パターンなのか、はっきりわかりません。新たな機能を追加しているわけではないのでproxy パターンのインスタンスだと思いますが、プロクシと言うとリモートオブジェクトの転送の受け口のイメージが強いため、decorator パターンのインスタンスのようにも感じるからです。もし、おわかりであればフォーラムの本記事のスレッドにご投稿いただけたらと思います。
なお、Proxy
はデザインパターンの実装以外にも、Mapに対するProxyオブジェクトの例で示したput
された内容を標準出力へ出力するダミーのMap
のように、スタブの作成にも利用できるでしょう。
Proxyを利用したOracle JDBC Connectionの作成
はじめに書いたように、OracleのJDBC実装には、生成したJDBCオブジェクトを明示的にクローズする必要があるという制限があります。そのため、きちんとclose
メソッドを呼ばないとWebアプリケーションのように長期間に渡って実行するアプリケーションではResultSet
などが解放されないまま蓄積されていき、最終的にJVMのヒープが食いつぶされてしまうという問題があります。
「ProxyConnection.java」は、この制限に対応できるように、Connection#close
で、そのConnection
から返されたStatement
、およびそのStatement
から返されたResultSet
で未クローズのオブジェクトのclose
メソッドを呼び出すクラスです。このクラスはProxy
を利用することでクライアントに対してはConnection
として振る舞います。
package com.example.proxy; import java.lang.reflect.*; import java.sql.*; import java.util.*; public class ProxyConnection { Connection connection; Set<Statement> openedStatements; Set<ResultSet> openedResultSets; Object proxy; ProxyConnection(Connection org) { connection = org; openedStatements = new HashSet<Statement>(); openedResultSets = new HashSet<ResultSet>(); proxy = Proxy.newProxyInstance (Connection.class.getClassLoader(), new Class[] { Connection.class }, new ConnectionHandler()); } /** * 指定されたコネクションのプロクシを生成する。 * @param c 生成対象のコネクション * @return PreparedStatementとResultSetのclose検証処理を 含んだコネクション */ public static Connection createProxy(Connection c) { ProxyConnection pc = new ProxyConnection(c); return (Connection)pc.proxy } Object send(Object target, Method m, Object[] args) throws Throwable { try { return m.invoke(target, args); } catch (InvocationTargetException e) { if (e.getCause() != null) { throw e.getCause(); } throw e; } } void remove(Object o) { if (o instanceof ResultSet) { openedResultSets.remove(o); } else if (o instanceof Statement) { openedStatements.remove(o); } else { throw new IllegalArgumentException("bad class:" + o); } } void closeAll() { for (ResultSet rs : openedResultSets) { try { rs.close(); } catch (SQLException e) { } } for (Statement ps : openedStatements) { try { ps.close(); } catch (SQLException e) { } } } class ConnectionHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("close")) { closeAll(); } Object o = send(connection, method, args); if (o instanceof Statement) { openedStatements.add((Statement)o); o = new Delegate(o, PreparedStatement.class).proxy; } return o; } } class Delegate implements InvocationHandler { Object proxy; Object original; Delegate(Object o, Class c) { original = o; proxy = Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("close")) { remove(original); } Object o = send(original, method, args); if (o instanceof ResultSet) { openedResultSets.add((ResultSet)o); o = new Delegate(o, ResultSet.class).proxy; } return o; } } }
import
などの冗長な記述は簡素化してあります。フィールド
ProxyConnection
は以下のフィールドを持ちます。
- Connection
connection
;元のConnection
のインスタンスを保持します。 - Set<Statement>
openedStatements
;Connection
から返されたStatement
のうち、未クローズのオブジェクトを保持します。 - Set<ResultSet>
openedResultSets
;Statement
から返されたResultSet
のうち、未クローズのオブジェクトを保持します。 - Object
proxy
;作成したProxy
のインスタンスを保持します。
コンストラクタ
ProxyConnection
は、デコレーションの対象となるConnection
のインスタンスを受け取り、Connection
インターフェイスに対するProxy
を生成します。
createProxy
外部へ公開するstaticメソッドです。ProxyConnection
を利用するプログラムは、たとえば以下のように当メソッドを呼び出すことを想定します。
Connection connection; ... try { connection = ProxyConnection.createProxy(dataSource.getConnection()); } catch (SQLException e) { log("connectionの取得に失敗", e); }
これはProxyConnection
クラスそのものを直接クライアントに見せる必要が無いからです。なお、一見するとせっかく生成したProxyConnection
のインスタンスが誰からも参照されないため(クライアントへ返しているのはProxy
のインスタンスです)すぐにGCされてしまいそうですが、実際にはインナークラスのConnectionHandler
のインスタンスがthis
を参照しているためConnectionHandler
が有効な間はGCされることはありません。また、ConnectionHandler
の寿命はProxy
と一致するため、結局、クライアントがConnection
オブジェクト(のプロクシ)の参照を保持している限り有効となります。
なお、本記事でJDBCオブジェクトのクローズのトリガーをConnection#close
にしている理由は、ここで意図的にProxyConnection
の呼び出しが必要な以上、Connection
を特別扱いするプログラムの存在を前提できるからです。たとえば、Webアプリケーションであれば、DataSource
のProxy
を作り、getConnection
の呼び出し時にこのメソッドを実行し、リクエストの終了時に作成したConnection
のclose
を呼ばせるように実装することが可能です。
send
内部処理用のメソッドです。Proxy
から与えられたMethod
オブジェクトと引数を利用して、内包するConnection
やStatement
などのオブジェクトへ処理をデリゲートします。
ここでキャッチしているInvocationTargetException
は、Method#invoke
の呼び出し先でスローされた例外を保持する例外です。したがって、Connection
オブジェクトなどがスローした例外はMethod#invoke
内でInvocationTargetException
にラップされることになります。クライアントにProxy
を意識させないために、ここでInvocationTargetException#getCause
により元の例外を取得し再スローしています。リスト上はgetCause
の内容がnullかチェックしていますが、本来nullになることはありません。
なお、InvocationTargetException
以外の例外は、Method#invoke
の処理そのものがスローしたものとなります。このような例外としては、たとえばargs
が呼び出し対象のメソッドとマッチしないなどが考えられます。しかし、クライアントはConnection
インターフェイスのメソッドを呼び出すソースをコンパイルしたと想定できるため、このような状況はバグ以外にはあり得ません。したがって、ここでは特にキャッチせずにそのままクライアントへスローされるようにしています。
remove
登録したResultSet
やStatement
オブジェクトのclose
呼び出し時に利用します。close
が呼ばれた場合、該当オブジェクトをそれ以上保持する必要はないため、当メソッドで登録解除します。
closeAll
Connection#close
の呼び出しを受けて、すべての未クローズのStatement
およびResultSet
オブジェクトをクローズします。なお、この時点でSQLException
をキャッチしても処理できたないため単純に無視しています。意味的にはファイナライザでのクローズと同様の扱いだからです。
ConnectionHandler(InvocationHandler実装)
Connection
オブジェクトのデコレータとして以下の処理を実行します。
close
呼び出し時にcloseAll
を呼び出して未クローズのリソースを解放する。- メソッド呼び出しを元の
Connection
オブジェクトへデリゲートする。 - メソッドの結果が
Statement
のインスタンス(PreparedStatement
などを含みます)であれば、未クローズオブジェクトとして登録します。また、新たなProxy
を生成します。
なお、Statement
かPreoparedStatement
、CallableStatement
の区別なくStatement
インターフェイスおよびその継承インターフェイスをPreparedStatement
インターフェイスとして、Proxy
オブジェクトを生成しています。ここは必要に応じて細分化を行うなどしても良いでしょう。たとえば、クライアントが何らかの処理で、PreparedStatement
インターフェイスかStatement
インターフェイスかによって処理を分岐するような場合には、この実装では正しく処理できないことに注意してください。
Delegate(InvocationHandler実装)
Connection
オブジェクト専用のConnectionHandler
と異なり、Delegate
クラスは、Statement
とResultSet
の両方を扱うハンドラです。なお、ここでは個々のリソース単位に新たなDelegate
のインスタンスを割り当てていますが(ConnectionHandler#invoke
内)、InvocationHandler#invoke
メソッドの第1引数で説明したように、実際には単一のインスタンスで複数のProxy
オブジェクトを扱うことなども可能です。しかし、ここでは処理の単純さを重視してオブジェクトごとに作成しています。
コンストラクタでは、デリゲート先のオブジェクト(元のオブジェクト)をフィールドへ保持し、Proxy
オブジェクトを生成しています。ここで生成したProxy
オブジェクトは、Delegate
のインスタンスの生成と同時にメソッドの戻り値としてそのままクライアントへ返します(各InvocationHandler
がDelegate
オブジェクトを生成している個所を参照)。
invoke
メソッドでは、ResultSet
、Statement
(および派生したPreparedStatement
)に対するデリゲートを実行します。ただし、close
メソッドが呼び出された場合はremove
ソッドを呼び出して該当するオブジェクトの登録を解除します。また戻り値の型がResultSet
の場合には新規に対応するDelegate
オブジェクトを生成しクライアントへはその結果を返します。
別の選択肢――Stubの生成
スタブを利用したユニットテスト
次のリストは、ProxyConnection
のテストプログラムです。
package com.example.proxy; import com.example.tool.*; import java.lang.reflect.*; import java.sql.*; import junit.framework.*; public class ProxyConnectionTest extends TestCase { int close; protected void setUp() throws Exception { close = 0; } // PreparedStatement, ResultSetがクローズされるか検証 public void testClose() throws Exception { Connection c = new StubConnection() { public void close() { // 最初のclose呼び出しか確認 assertEquals(2, close); close++; } public PreparedStatement prepareStatement(String s) { return new TestStubPreparedStatement(); } }; c = ProxyConnection.createProxy(c); PreparedStatement ps = c.prepareStatement("select"); ResultSet rs = ps.executeQuery(); c.close(); assertEquals(3, close); } // PreparedStatement, ResultSetがクローズされている場合に // 何も行わないことを検証 public void testClosed() throws Exception { Connection c = new StubConnection() { public void close() { // 既にclose呼び出し済みか確認 assertEquals(2, close); close++; } public PreparedStatement prepareStatement(String s) { return new TestStubPreparedStatement(); } }; c = ProxyConnection.createProxy(c); PreparedStatement ps = c.prepareStatement("select"); ResultSet rs = ps.executeQuery(); rs.close(); ps.close(); c.close(); assertEquals(3, close); } // 例外が透過に受け取れるか検証 public void testTransparent() throws Exception { Connection c = new StubConnection() { public PreparedStatement prepareStatement(String s) throws SQLException { throw new SQLException("test exception"); } }; c = ProxyConnection.createProxy(c); try { c.prepareStatement("select"); fail("no exception"); } catch (SQLException e) { assertEquals("test exception", e.getMessage()); } } class TestStubPreparedStatement extends StubPreparedStatement { public void close() { close++; } public ResultSet executeQuery() { /* * スタブを利用した実装 return new TestStubResultSet(); */ /* * Proxyを利用した実装 */ return (ResultSet)Proxy.newProxyInstance( ResultSet.class.getClassLoader(), new Class[] { ResultSet.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("close")) { close++; return null; } else if (method.getName().equals("next")) { return Boolean.FALSE; } else if (method.getName().equals("equals") || method.getName().equals("hashCode")) { return method.invoke(this, args); } throw new UnsupportedOperationException ("only close can handle"); } }); } } class TestStubResultSet extends StubResultSet { public void close() { close++; } } public static void main(String[] args) { junit.textui.TestRunner.run(ProxyConnectionTest.class); } }
JDBCドライバを直接利用してテストをすることは環境の用意の問題があることや、単純に実装するとテストケースごとにコネクションが必要となりテスト実行に時間がかかることから、ロジックのテストについてはスタブやモックを利用したユニットテストプログラムを記述します。ProxyConnectionTest
もそのようなスタブを利用したプログラムの例です。
このプログラムでは、StubConnection
およびStubPreparedStatement
という2つのスタブクラス(いずれもソースは、アーカイブの「proxy\test\com\example\tool」ディレクトリに格納してあります)および、ResultSet
についてはProxy
オブジェクトを使ったスタブを利用しています(スタブクラスを利用するためのコードも含んでいます)。
スタブクラスはすべてのメソッドのデフォルト処理(大抵はnull
や0
を返します)を実装しただけのクラスで、Proxy
と異なり、継承可能であればインターフェイスだけではなくクラスについても作成可能だという長所があります。スタブクラスを利用したテストの場合、テストの都合に合わせて呼び出しの対象となるメソッドをオーバーライドした無名クラスを、その都度生成することも(StubConnection
の利用方法)、オーバーライドしたメソッドがテスト全体で共用されるのであれば、テストプログラム内でスタブクラスを継承したクラスを定義することも(StubPreparedStatement
の利用方法)可能です。
一方、Proxy
オブジェクトを利用する場合(TestStubPreparedStatement#executeQuery
メソッドでResultSet
へ適用しています)は、InvocationHandler#invoke
でテストに必要なメソッドについて処理を実装します。なお、ProxyConnectionTest
の例では、HashSet
に格納されるため、hashCode
メソッドおよびequals
メソッドについても処理していますが、スタブクラスの場合はスタブクラス自体が実装を持つためそのまま利用可能です。そのため同じくHashSet
に格納されるTestStubPreparedStatement
ではequals
メソッドやhashCode
メソッドを特にオーバーライドせずに利用しています。
このような点からテスト用途にはProxy
クラスの利用は手軽ですが、再利用性を考えるとスタブクラスを用意したほうがより望ましいと言えます。
また、Proxy
よりも直接内包するオブジェクトへデリゲートするフィルタのほうが、処理効率は(呼び出し頻度にも依存しますが)一般的に良好です。このようなフィルタの例として、J2SEにはjava.io.FilterReader
/FilterWriter
などが存在します。これらのクラスはコンストラクタで指定したオブジェクトを内包しすべてのメソッドをデリゲートします。プロクシパターンやデコレータパターンをインスタンス化したい場合には、必要なメソッドをオーバーライドして利用することになります。
スタブジェネレータ
アーカイブに添付したJDBCのスタブクラスは以下のような機械的なプログラムです。
package com.example.tool; /** * java.sql.Connectionのスタブクラス。 * @author StubGenerator by arton. */ public class StubConnection implements java.sql.Connection { public StubConnection() { } public java.sql.Statement createStatement (int a0, int a1, int a2) throws java.sql.SQLException { return null; } public java.sql.Statement createStatement(int a0, int a1) throws java.sql.SQLException { return null; } public java.sql.Statement createStatement() throws java.sql.SQLException { return null; }
このようなプログラムは一度作れば何度でも利用できるとは言え、手で打ち込むのはあまり賢い方法ではありません。
本記事のおまけとして、「proxy\src\com\example\tool」ディレクトリにreflectionを利用して指定されたインターフェイスや、クラスのスタブを生成するユーティリティを添付してありますので、興味があったら利用してください。アーカイブの「proxy\test\com\example\tool」ディレクトリ下には、当クラスの一部機能のテストプログラムであるStubGeneratorTest
を除いて、StubGenerator
によって生成したクラスを格納してあります。
なお、StubGenerator
については、アーカイブ内の他のソースと異なり、フェアライセンス(日本語訳)を適用します。ただし、生成したソースにまでは効力は及ばないものとします。
まとめ
Proxy
クラスを利用して、decoratorパターンやproxyパターンを比較的簡単に実装することができます。また、テスト用のスタブが必要な機能が少なければProxy
クラスを利用してスタブを作ることも可能です。特にメソッドが多数含まれているインターフェイスのごく1部のメソッドのみテストで利用したい場合にはProxy
クラスの利用は効果的です。
本記事のサンプルソースは自由に使ってかまいません(ただし、StubGenerator
についてはフェアライセンスを適用します)。いろいろ試してJavaの学習などに利用してみてください。
なお、ProxyConnection
およびその派生物を実際の運用に利用する場合については、無保証であること、筆者および翔泳社は、利用したことによって生じたいかなる問題についても一切の責を追わないことを認めることを利用許諾の条件とします。利用される場合にはProxyConnection
のソースコードについては特にライセンスを定めません(元の著作権者の表示を不要とするライセンスが見つからなかったのが理由です)ので、むしろ改変して利用するようにしてください。