web-dev-qa-db-ja.com

動的プロキシクラスとは何ですか。なぜ使用するのですか?

動的プロキシを使用するユースケースとは何ですか?

バイトコードの生成とリフレクションにどのように関連していますか?

推奨読書はありますか?

67
cwash

これを強くお勧めします resource

まず、プロキシパターンのユースケースを理解する必要があります。プロキシの主な目的は、ターゲットオブジェクトの機能を強化することではなく、ターゲットオブジェクトへのアクセスを制御することです。アクセス制御には、同期、認証、リモートアクセス(RPC)、遅延インスタンス化(Hibernate、Mybatis)、AOP(トランザクション)が含まれます。

静的プロキシとは対照的に、動的プロキシは、実行時にJavaリフレクションを必要とするバイトコードを生成します。動的アプローチでは、プロキシクラスを作成する必要がないため、利便性が向上します。

25
xiaojieaa

動的プロキシクラスは、クラスのインスタンスのインターフェイスの1つを介したメソッド呼び出しが実行されるように、実行時に指定されたインターフェイスのリストを実装するクラスですエンコードされ、統一されたインターフェイスを介して別のオブジェクトにディスパッチされます。プロキシクラスの事前生成を必要とせずに、インターフェイスのリストのタイプセーフプロキシオブジェクトを作成するために使用できます。動的プロキシクラスは、インターフェイスAPIを提供するオブジェクトの呼び出しのタイプセーフリフレクションディスパッチを提供する必要があるアプリケーションまたはライブラリに役立ちます。

動的プロキシクラス

動的プロキシの興味深い使用法を思いつきました。

別の依存サービスと結合されている重要ではないサービスに問題があり、その依存サービスが利用できなくなったときにフォールトトレラントになる方法を模索したいと考えていました。

そこで、私はLoadSheddingProxyを作成しました。これは2つのデリゲートを取ります-1つは「JNDIルックアップ後の」「通常の」サービスのリモート実装です。もう1つのオブジェクトは、「ダミー」の負荷制限実装です。タイムアウトをキャッチし、再試行する前に一定の時間ダミーに迂回する各メソッド呼び出しを囲む単純なロジックがあります。使用方法は次のとおりです。

// This is part of your ServiceLocator class
public static MyServiceInterface getMyService() throws Exception
{
    MyServiceInterface loadShedder = new MyServiceInterface() {
        public Thingy[] getThingys(Stuff[] whatever) throws Exception {
            return new Thingy[0];
        }
        //... etc - basically a dummy version of your service goes here
    }           
    Context ctx = JndiUtil.getJNDIContext(MY_CLUSTER);
    try {
        MyServiceInterface impl = ((MyServiceHome) PortableRemoteObject.narrow(
                ctx.lookup(MyServiceHome.JNDI_NAME), 
                MyServiceHome.class)).create();
        // Here's where the proxy comes in
        return (MyService) Proxy.newProxyInstance(
            MyServiceHome.class.getClassLoader(),
        new Class[] { MyServiceInterface.class },
        new LoadSheddingProxy(MyServiceHome.JNDI_NAME, impl, loadShedder, 60000));  // 10 minute retry
    } catch (RemoteException e) {    // If we can't even look up the service we can fail by shedding load too
        logger.warn("Shedding load");
        return loadShedder;
    } finally {
        if (ctx != null) {
        ctx.close();
        }
    }
}

プロキシは次のとおりです。

public class LoadSheddingProxy implements InvocationHandler {

static final Logger logger = ApplicationLogger.getLogger(LoadSheddingProxy.class);

Object primaryImpl, loadDumpingImpl;
long retry;
String serviceName;
// map is static because we may have many instances of a proxy around repeatedly looked-up remote objects
static final Map<String, Long> servicesLastTimedOut = new HashMap<String, Long>();

public LoadSheddingProxy(String serviceName, Object primaryImpl, Object loadDumpingImpl, long retry)
{
    this.serviceName = serviceName;
    this.primaryImpl = primaryImpl;
    this.loadDumpingImpl = loadDumpingImpl;
    this.retry = retry;
}

public Object invoke(Object obj, Method m, Object[] args) throws Throwable
{
    try
    {
        if (!servicesLastTimedOut.containsKey(serviceName) || timeToRetry()) {
            Object ret = m.invoke(primaryImpl, args);
            servicesLastTimedOut.remove(serviceName);
            return ret;
        } 
        return m.invoke(loadDumpingImpl, args);
    }
    catch (InvocationTargetException e)
    {
        Throwable targetException = e.getTargetException();

        // DETECT TIMEOUT HERE SOMEHOW - not sure this is the way to do it???
        if (targetException instanceof RemoteException) {
            servicesLastTimedOut.put(serviceName, Long.valueOf(System.currentTimeMillis()));
        }
        throw targetException;
    }                    
}

private boolean timeToRetry() {
    long lastFailedAt = servicesLastTimedOut.get(serviceName).longValue();
    return (System.currentTimeMillis() - lastFailedAt) > retry;
}
}
16
Jim P

クラス - Java.lang.reflect.Proxy では、 InvocationHandler でメソッド呼び出しを処理することにより、インターフェイスを動的に実装できます。 Javaのリフレクション機能の一部と見なされますが、バイトコード生成とは関係ありません。

Sunには、プロキシクラスの使用について チュートリアル があります。 Googleも役立ちます。

8

使用例の1つは休止状態です。モデルクラスインターフェイスを実装するオブジェクトを提供しますが、ゲッターとセッターの下にはdb関連のコードがあります。つまり単純なPOJOのように使用しますが、実際には多くのことが隠れています。

たとえば、遅延ロードされたプロパティのgetterを呼び出すだけですが、実際にはプロパティ(おそらく大きなオブジェクト構造全体)がデータベースから取得されます。

詳細については、 cglib libraryを確認してください。

5
miceuz