プロキシモード-静的プロキシ、動的プロキシ(JDKプロキシおよびCGLibプロキシ)

概要概要

いくつかの理由で、オブジェクトへのアクセスを制御するために、オブジェクトのプロキシを提供する必要があります

現時点では、アクセスオブジェクトは適切でないか、ターゲットオブジェクトを直接参照できず、プロキシオブジェクトはアクセスオブジェクトとターゲットオブジェクトの間の仲介役として機能します

Javaのプロキシは、プロキシクラスの生成時間の違いに応じて、静的プロキシと動的プロキシに分けられます

静的プロキシプロキシクラスはコンパイル時に生成されますが、動的プロキシプロキシクラスはJavaランタイム中に動的に生成されます。

JDKエージェントとCGLibエージェントの2つの動的エージェントがあります。

構造

プロキシモードは3つの役割に分けられます。

  • 抽象サブジェクト(サブジェクト)クラス:インターフェイスまたは抽象クラスを介して、実際のサブジェクトとプロキシオブジェクトによって実装されるビジネスメソッドを宣言します。
  • Real Subjectクラス:抽象サブジェクトで具体的なビジネスを実装します。これは、プロキシオブジェクトと最終的に参照されるオブジェクトによって表される実際のオブジェクトです。
  • プロキシクラス:実際のテーマと同じインターフェイスを提供します。実際のテーマへの参照が含まれています。実際のテーマの機能にアクセス、制御、または拡張できます。

静的プロキシ

ケースを通して静的プロキシの感触をつかみましょう。

【例】駅で切符を売る

電車の切符を購入したい場合は、駅に行って切符を購入し、電車に乗って駅まで行き、列に並んで一連の操作を待つ必要がありますが、これは明らかに面倒です。また、駅には多くの場所に委託ポイントがあるので、委託ポイントに行ってチケットを購入する方がはるかに便利です。

この例は、実際には典型的な代理店モデルです。駅がターゲットで、販売代理店が代理店です。

クラス図は次のとおりです。

SellTickets.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: SellTickets
 * @Description: 卖火车票的接口
 * @Author: dym
 */
public interface SellTickets {

    void sell();
}

TrainStation.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyPoint.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: ProxyPoint
 * @Description: 代售点类
 * @Author: dym
 */
public class ProxyPoint implements SellTickets {

    //声明火车站类对象
    private TrainStation trainStation  = new TrainStation();

    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }

}

Client.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //创建代售点类对象
        ProxyPoint proxyPoint = new ProxyPoint();
        //调用方法进行买票
        proxyPoint.sell();
    }
}

上記のコードから、テストクラスがProxyPointクラスオブジェクトに直接アクセスしていることがわかります。

つまり、ProxyPointは、アクセスオブジェクトとターゲットオブジェクトの間の仲介役として機能します。

同時に、販売方法が強化されました(代理店は一部のサービス料金を請求します)。



JDK動的プロキシ

次に、動的プロキシを使用して上記のケースを実装します。最初に、JDKによって提供される動的プロキシについて説明します。

Javaは、動的プロキシクラスProxyを提供します。Proxyは、前述のプロキシオブジェクトのクラスではありません。

代わりに、プロキシオブジェクト作成してプロキシオブジェクトを取得する静的メソッド(newProxyInstanceメソッド)を提供します。

コードは次のように表示されます。

SellTickets.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: SellTickets
 * @Description: 卖火车票的接口
 * @Author: dym
 */
public interface SellTickets {

    void sell();
}

TrainStation.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory.java

package com.itheima.pattern.proxy.jdk_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @version v1.0
 * @ClassName: ProxyFactory
 * @Description: 获取代理对象的工厂类
 *      代理类也实现了对应的接口
 * @Author: dym
 */
public class ProxyFactory {

    //声明目标对象
    private TrainStation station = new TrainStation();

    //获取代理对象的方法
    public SellTickets getProxyObject() {
        //返回代理对象
        /*
            ClassLoader loader : 类加载器,用于加载代理类。可以通过目标对象获取类加载器
            Class<?>[] interfaces : 代理类实现的接口的字节码对象
            InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {

                    /*
                        Object proxy : 代理对象。和proxyObject对象是同一个对象,在invoke方法中基本不用
                        Method method : 对接口中的方法进行封装的method对象
                        Object[] args : 调用方法的实际参数

                        返回值: 方法的返回值。
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //System.out.println("invoke方法执行了");
                        System.out.println("代售点收取一定的服务费用(jdk动态代理)");
                        //执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

Client.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        //1,创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //2,使用factory对象的方法获取代理对象
        SellTickets proxyObject = factory.getProxyObject();
        //3,调用卖调用的方法
        proxyObject.sell();

        System.out.println(proxyObject.getClass());


    }
}



動的エージェントを使用して、次の質問について考えます。

  • ProxyFactoryはプロキシクラスですか?

    ProxyFactoryは、プロキシモードで言及されているプロキシクラスではありません。

  • プロキシクラスは、プログラムの実行中にメモリ内で動的に生成されるクラスです。

  • アリババのオープンソースJava診断ツール(Arthas [Alsace])を使用して、プロキシクラスの構造を確認します。

package com.sun.proxy;
​
import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
​
public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
​
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
​
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }
​
    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
​
    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
​
    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
​
    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}
  • 上記のクラスから、次の情報を確認できます。

    • プロキシクラス($ Proxy0)はSellTicketsを実装しますこれは、前に述べた実際のクラスとプロキシクラスが同じインターフェイスを実装していることも確認します
    • プロキシクラス($ Proxy0)、提供した匿名の内部クラスオブジェクト親クラスに渡します
  • 動的エージェントの実行プロセスは何ですか?

    抽出されたキーコードは次のとおりです。

//程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m3;
​
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
​
    static {
        m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
    }
​
    public final void sell() {
        this.h.invoke(this, m3, null);
    }
}
​
//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
     
    protected Proxy(InvocationHandler h) {
        this.h = h;
    }
}
​
//代理工厂类
public class ProxyFactory {
​
    private TrainStation station = new TrainStation();
​
    public SellTickets getProxyObject() {
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
​
                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}
​
​
//测试访问类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();
        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

実行プロセスは次のとおりです。

  1. テストクラスのプロキシオブジェクトを介してsell()メソッドを呼び出します
  2. ポリモーフィズムの特性に応じて、プロキシクラス($ Proxy0)のsell()メソッドが実行されます
  3. プロキシクラス($ Proxy0)のsell()メソッドは、InvocationHandlerインターフェイスのサブ実装クラスオブジェクトのinvokeメソッドを呼び出します
  4. invokeメソッドは、リフレクションを介して、実オブジェクトが属するクラス(TrainStation)でsell()メソッドを実行します。


CGLIB動的エージェント

上記の場合も同じですが、ここでもCGLIBプロキシ実装を使用します。

SellTicketsインターフェースが定義されていない場合、TrainStation(鉄道駅クラス)のみが定義されます。

明らかに、JDK動的プロキシでは、インターフェイスをプロキシするためにインターフェイスを定義する必要があるため、JDKプロキシは使用できません

CGLIBは、強力で高性能なコード生成パッケージです。

これは、インターフェースを実装しないクラスのプロキシーを提供し、JDKの動的プロキシーの優れた補足を提供します。

CGLIBはサードパーティによって提供されるパッケージであるため、jarパッケージの座標を導入する必要があります。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

TrainStation.java

package com.itheima.pattern.proxy.cglib_proxy;


/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory.java

package com.itheima.pattern.proxy.cglib_proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @version v1.0
 * @ClassName: ProxyFactory
 * @Description: 代理对象工厂,用来获取代理对象
 * @Author: dym
 */
public class ProxyFactory implements MethodInterceptor {

    //声明火车站对象
    private TrainStation station = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象。指定父类
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //System.out.println("方法执行了");
        System.out.println("代售点收取一定的服务费用(CGLib代理)");
        //要调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj;
    }
}

Client.java

package com.itheima.pattern.proxy.cglib_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();
        //调用代理对象中的sell方法卖票
        proxyObject.sell();
    }
}



3つのエージェントの比較

  • jdkエージェントとCGLIBエージェント

    CGLibを使用して動的プロキシを実装します。CGLibの最下層はASMバイトコード生成フレームワークとバイトコードテクノロジを使用してプロキシクラスを生成します。JDK1.6より前は、Javaリフレクションを使用するよりも効率的です。

注意すべき唯一のことは、CGLibの原則はプロキシクラスのサブクラスを動的に生成することであるため、CGLibはfinalとして宣言されたクラスまたはメソッドをプロキシできないことです。

JDK1.6、JDK1.7、およびJDK1.8が徐々にJDK動的プロキシを最適化した後、呼び出し数が少ない場合、JDKのプロキシ効率はCGLibよりも高くなります。呼び出し数が多い場合のみ。 、JDK1.6およびJDK1.7は、CGLibプロキシよりも少し効率が低くなります。

しかし、JDK1.8の時点では、JDKプロキシの効率はCGLibプロキシの効率よりも高くなっています。したがって、インターフェースがある場合はJDK動的プロキシーを使用し、インターフェースがない場合はCGLIBプロキシーを使用します。

  • 動的プロキシと静的プロキシ

    静的プロキシと比較して動的プロキシの最大の利点は、インターフェイスで宣言されたすべてのメソッドが、呼び出しハンドラー(InvocationHandler.invoke)の一元化されたメソッドに転送されることです。

このように、インターフェースメソッドの数が比較的多い場合は、柔軟に対応できます。

静的プロキシのようにすべてのメソッドを転送する必要はありません。インターフェイスがメソッドを追加する場合、静的プロキシモードはすべての実装クラスに加えてこのメソッドを実装する必要があり、すべてのプロキシクラスもこのメソッドを実装する必要があります。コードメンテナンスの複雑さが増しました。

動的プロキシにはこの問題はありません

 

長所と短所

利点:

  • プロキシモードは、クライアントとターゲットオブジェクトの間の仲介役を果たし、ターゲットオブジェクトを保護します。
  • プロキシオブジェクトは、ターゲットオブジェクトの機能を拡張できます。
  • プロキシモードでは、クライアントをターゲットオブジェクトから分離できます。これにより、システムの結合がある程度減少します。

短所:

  • システムの複雑さを増します。

使用するシーン

  • リモート(リモート)プロキシ

    ローカルサービスは、ネットワークを介してリモートサービスを要求します。ローカルからリモートへの通信を実現するためには、起こりうる異常に対処するためのネットワーク通信を実装する必要があります。コードの設計と保守性を高めるために、ネットワーク通信部分を非表示にし、ローカルサービスへのインターフェイスのみを公開します。これにより、通信部分の詳細にあまり注意を払うことなく、リモートサービスによって提供される機能にアクセスできます。

  • ファイアウォールプロキシ

    プロキシ機能を使用するようにブラウザを設定すると、ファイアウォールはブラウザの要求をインターネットに転送します。インターネットが応答を返すと、プロキシサーバーはそれをブラウザに転送します。

  • 保護(保護またはアクセス)エージェント

    オブジェクトへのアクセスを制御し、必要に応じて、さまざまなユーザーにさまざまなレベルの使用権を提供します。

おすすめ

転載: blog.csdn.net/qq_39368007/article/details/113948288