Futureパターンとは別スレッドに処理を委譲しつつ、処理結果の代わりのオブジェクトを返すパターンで、このオブジェクトを通じて実処理が完了するまで待機したりする事ができます。
そしてProxyパターンはあるオブジェクトの代理となるオブジェクトを生成するパターンです。
この2つを組み合わせると、以下のようなシナリオが出来上がります。
- クライアントは重い生成処理のインスタンスを要求
- ファクトリは別スレッドで処理してFutureオブジェクトを生成
- Futureオブジェクトから取得できるインスタンスの型に対するプロキシを生成する
- そのプロキシのインスタンスを返す
というシナリオで、クライアントがインスタンスを利用した場合、以下のようなシナリオになります。
- クライアントはインスタンスを利用
- プロキシはFutureオブジェクトからインスタンスが返されるのを待つ
- インスタンスが返って来たら、そのインスタンスへ委譲する
という感じです。DIコンテナのように微妙に生成に時間が必要かつ、直ぐには参照しないオブジェクトでは非常に有効なパターンです。以下にコードを掲載します。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
public abstract class FutureProxy<T> {
private final class CallableImpl implements Callable<T> {
public T call() throws Exception {
return FutureProxy.this.createInstance();
}
}
private static class InvocationHandlerImpl<T> implements InvocationHandler {
private Future<T> future;
private volatile T instance;
InvocationHandlerImpl(Future<T> future){
this.future = future;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
synchronized(this){
if(this.future.isDone()){
this.instance = this.future.get();
}else{
while(!this.future.isDone()){
try{
this.instance = this.future.get();
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
return method.invoke(this.instance, args);
}
}
}
private static final class ThreadFactoryImpl implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
}
private static ExecutorService service = Executors
.newCachedThreadPool(new ThreadFactoryImpl());
protected abstract T createInstance();
protected abstract Class<? extends T> getInterface();
@SuppressWarnings("unchecked")
public final T getProxyInstance() {
Class<? extends T> interfaceClass = this.getInterface();
if (interfaceClass == null || !interfaceClass.isInterface()) {
throw new IllegalStateException();
}
Callable<T> task = new CallableImpl();
Future<T> future = FutureProxy.service.submit(task);
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[] { interfaceClass }, new InvocationHandlerImpl(future));
}
}
これは抽象クラスなので、具象クラスを以下のように書きます。お試しで生成に3秒かかるオブジェクトを3つほど作ってみます。
public interface Foo{
String getFoo();
}
public class FooImpl implements Foo{
public FooImpl(){
try{
Thread.sleep(3000);
}catch(InterruptedException e){
}
}
public String getFoo() {
return "foo";
}
}
public class FooFactory extends FutureProxy<Foo>{
@Override
protected Foo createInstance() {
return new FooImpl();
}
@Override
protected Class<? extends Foo> getInterface() {
return Foo.class;
}
}
public class Test{
public void main(String[] args) {
FooFactory factory = new FooFactory();
Foo foo1 = factory.getProxyInstance();
Foo foo2 = factory.getProxyInstance();
Foo foo3 = factory.getProxyInstance();
System.out.println(foo1.getFoo());
System.out.println(foo2.getFoo());
System.out.println(foo3.getFoo());
}
}
これを実行すると、3秒きっちりで終了するのが分かると思います。Tomcatと各種フレームワークを組み合わせて、再起動のたびに待たされている人には有用な場合があると思います。