#関連するおすすめ: 「違い: 静的プロキシはプログラマによって作成されるか、ツールがプロキシ クラスのソース コードを生成してからプロキシ クラスをコンパイルします。プロキシ クラスのバイトコード ファイルはプログラムの実行前にすでに存在します。プロキシ クラスとデリゲート クラスの関係は、実行前に決定されます。動的プロキシクラスのソースコードは、プログラム実行中のリフレクションなどの仕組みに基づいてJVMにより動的に生成されるため、プロキシクラス用のバイトコードファイルは存在しません。
プログラミング動画コース 」
# 1. エージェンシーのコンセプトオブジェクトへのアクセスを制御するために、オブジェクトにプロキシを提供します。プロキシ クラスとデリゲート クラスには共通の親クラスまたは親インターフェイスがあるため、デリゲート クラス オブジェクトが使用される場所であればどこでもプロキシ オブジェクトを使用できます。プロキシ クラスは、リクエストの前処理、フィルタリング、処理のためのデリゲート クラスへのリクエストの割り当て、およびデリゲート クラスがリクエストを完了した後の後続の処理を担当します。
関連する推奨事項: 「Java ビデオ チュートリアル 」
図 1: プロキシ モード
#図からわかるように、プロキシ インターフェイス (Subject)、プロキシ クラス (ProxySubject)、および委譲クラス (RealSubject) が「Pin」構造を形成しています。
エージェント クラスの生成時間に応じて、エージェントは静的エージェントと動的エージェントの 2 つのタイプに分類できます。
静的エージェントと動的エージェントを説明するためのシミュレーション要件は次のとおりです。デリゲート クラスは長時間のタスクを処理する必要があり、クライアント クラスはそれにかかる時間を出力する必要があります。タスクを実行します。この問題を解決するには、タスクの実行前と実行後の時間を記録する必要があり、その差がタスクの実行にかかる時間となります。
2. 静的プロキシ
プロキシ クラスのソース コードを生成するためにプログラマまたはツールによって作成され、次に、プロキシ クラスをコンパイルします。いわゆる静的とは、プロキシ クラスのバイトコード ファイルがプログラムの実行前にすでに存在し、プロキシ クラスとデリゲート クラスの関係が実行前に決定されることを意味します。
リスト 1: プロキシ インターフェイス
/** * 代理接口。处理给定名字的任务。 */ public interface Subject { /** * 执行给定名字的任务。 * @param taskName 任务名 */ public void dealTask(String taskName); }
/** * 真正执行任务的类,实现了代理接口。 */ public class RealSubject implements Subject { /** * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间 * @param taskName */ @Override public void dealTask(String taskName) { System.out.println("正在执行任务:"+taskName); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }
/** * 代理类,实现了代理接口。 */ public class ProxySubject implements Subject { //代理类持有一个委托类的对象引用 private Subject delegate; public ProxySubject(Subject delegate) { this.delegate = delegate; } /** * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 * * @param taskName */ @Override public void dealTask(String taskName) { long stime = System.currentTimeMillis(); //将请求分派给委托类处理 delegate.dealTask(taskName); long ftime = System.currentTimeMillis(); System.out.println("执行任务耗时"+(ftime - stime)+"毫秒"); } }
public class SubjectStaticFactory { //客户类调用此工厂方法获得代理对象。 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。 public static Subject getInstance(){ return new ProxySubject(new RealSubject()); } }
清单5:客户类
public class Client1 { public static void main(String[] args) { Subject proxy = SubjectStaticFactory.getInstance(); proxy.dealTask("DBQueryTask"); } }
静态代理类优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
三、动态代理
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
1、先看看与动态代理紧密关联的Java API。
1)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
清单6:Proxy类的静态方法
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:该方法用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl) // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
2)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
清单7:InvocationHandler的核心方法
// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象 // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 Object invoke(Object proxy, Method method, Object[] args)
3)java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象
2、动态代理实现步骤
具体步骤是:
a. 实现InvocationHandler接口创建自己的调用处理器
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
清单8:分步骤实现动态代理
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用 InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通过反射从生成的类对象获得构造函数对象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数对象创建动态代理类实例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。
清单9:简化后的动态代理实现
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 直接创建动态代理类实例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
3、动态代理实现示例
清单10:创建自己的调用处理器
/** * 动态代理类对应的调用处理程序类 */ public class SubjectInvocationHandler implements InvocationHandler { //代理类持有一个委托类的对象引用 private Object delegate; public SubjectInvocationHandler(Object delegate) { this.delegate = delegate; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long stime = System.currentTimeMillis(); //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。 //因为示例程序没有返回值,所以这里忽略了返回值处理 method.invoke(delegate, args); long ftime = System.currentTimeMillis(); System.out.println("执行任务耗时"+(ftime - stime)+"毫秒"); return null; } }
清单11:生成动态代理对象的工厂,工厂方法列出了如何生成动态代理类对象的步骤。
/** * 生成动态代理对象的工厂. */ public class DynProxyFactory { //客户类调用此工厂方法获得代理对象。 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。 public static Subject getInstance(){ Subject delegate = new RealSubject(); InvocationHandler handler = new SubjectInvocationHandler(delegate); Subject proxy = null; proxy = (Subject)Proxy.newProxyInstance( delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), handler); return proxy; } }
清单12:动态代理客户类
public class Client { public static void main(String[] args) { Subject proxy = DynProxyFactory.getInstance(); proxy.dealTask("DBQueryTask"); } }
4、动态代理机制特点
首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:
图2:动态代理类的继承关系
図からわかるように、Proxy クラスはその親クラスであり、このルールは Proxy によって作成されるすべての動的プロキシ クラスに適用されます。また、このクラスは、プロキシするインターフェイスのセットも実装します。これが、プロキシするインターフェイスに安全に型キャストできる基本的な理由です。
次に、プロキシ クラス インスタンスのいくつかの特性を見てみましょう。各インスタンスは呼び出しハンドラー オブジェクトに関連付けられており、Proxy が提供する静的メソッド getInvocationHandler を通じてプロキシ クラス インスタンスの呼び出しハンドラー オブジェクトを取得できます。プロキシのインターフェースで宣言されたメソッドがプロキシ クラス インスタンスで呼び出される場合、これらのメソッドは最終的に呼び出し側プロセッサの invoke メソッドによって実行されます。さらに、ルート クラス java には 3 つのメソッドがあることに注意してください。プロキシ クラスの lang.Object。メソッドは、実行のために呼び出し側プロセッサの invoke メソッドにもディスパッチされます。hashCode、equals、および toString です。考えられる理由は次のとおりです。まず、これらのメソッドは public で非 Final 型であるためです。また、プロキシ クラスによってオーバーライドできること、第 2 に、これらのメソッドは、多くの場合、クラスの特定の特性属性を示し、ある程度の区別があるためです。したがって、プロキシ クラスとデリゲート クラスの外部一貫性を確保するために、これらのメソッドは、 3 つのメソッドも実行のためにデリゲート クラスに割り当てる必要があります。プロキシのインターフェイスのセットに繰り返し宣言されたメソッドがあり、そのメソッドが呼び出される場合、プロキシ クラスのインスタンスがそのインターフェイスを使用しているかどうかに関係なく、プロキシ クラスは常に最前面のインターフェイスからメソッド オブジェクトを取得し、呼び出し元のハンドラーにディスパッチします。 . (またはこのインターフェイスから継承されたサブインターフェイス) は、現在参照されている型をプロキシ クラス内で区別できないため、外部参照されます。
次に、プロキシされるインターフェイスのグループの特性を見てみましょう。まず、動的プロキシ クラス コードを生成するときにコンパイル エラーを避けるために、インターフェイスが重複しないように注意してください。次に、これらのインターフェイスはクラス ローダーから見える必要があります。そうしないと、クラス ローダーはそれらをリンクできず、クラス定義が失敗します。第三に、プロキシ化する必要があるすべての非パブリック インターフェイスは同じパッケージ内に存在する必要があります。そうしないと、プロキシ クラスの生成も失敗します。最後に、インターフェイスの数は、JVM によって設定された制限である 65535 を超えることはできません。
最後に、例外処理の特徴を見てみましょう。呼び出し元のプロセッサ インターフェイスによって宣言されたメソッドから、すべての例外は Throwable インターフェイスから継承されるため、理論上はあらゆるタイプの例外をスローできることがわかりますが、これは事実でしょうか?答えは「いいえ」です。継承の原則に従う必要があるためです。つまり、サブクラスが親クラスのメソッドをオーバーライドするか、親インターフェイスを実装する場合、スローされる例外は、元のメソッドでサポートされている例外リスト内になければなりません。したがって、ハンドラーの呼び出しは理論的には可能ですが、実際には、親インターフェイスのメソッドが Throwable 例外のスローをサポートしない限り、制限されることがよくあります。では、インターフェイス メソッド宣言でサポートされていない例外が呼び出しメソッドで発生した場合はどうなるのでしょうか?心配しないでください。Java 動的プロキシ クラスはすでにソリューションを設計しています。UndeclaredThrowableException 例外をスローします。この例外は RuntimeException 型であるため、コンパイル エラーは発生しません。例外の getCause メソッドを通じて、サポートされていない元の例外オブジェクトを取得して、エラー診断を容易にすることもできます。
5. 動的プロキシの長所と短所
利点:
静的プロキシと比較した場合、動的プロキシの最大の利点は、インターフェイスで宣言されたすべてのメソッドが呼び出しプロセッサ (InvocationHandler.invoke) の集中メソッドに転送されることです。これにより、インターフェースのメソッドが多数ある場合でも、静的プロキシのようにメソッドごとに転送する必要がなく、柔軟に対応することができます。この例では、特定の周辺サービスが invoke メソッド本体に埋め込まれているため (タスク処理の前後の時間を記録し、時間差を計算する) ため見えませんが、実際には Spring AOP と同様に周辺サービスを構成できます。
軟膏の欠陥:
Proxy が非常に美しくデザインされているのは事実ですが、少し残念な点がまだあります。その設計はこの残念な運命にあるため、インターフェイス プロキシのみをサポートするという束縛を取り除くことができませんでした。動的に生成されたプロキシ クラスの継承図を思い出してください。それらは、Proxy と呼ばれる共通の親クラスを持つことになります。 Java の継承メカニズムでは、Java では多重継承が本質的に機能しないため、これらの動的プロキシ クラスはクラスの動的プロキシを実装できないという運命にあります。
クラス プロキシの必要性を否定できる理由はたくさんありますが、クラス動的プロキシをサポートする方が良いと考える理由もいくつかあります。インターフェースとクラスの区別はそもそもそれほど明確ではありませんが、これほど詳細になるのは Java だけです。メソッドの宣言とそれが定義されているかどうかだけを考慮すると、その 2 つが混在しており、その名前は抽象クラスです。私は、抽象クラスの動的プロキシの実装にも固有の価値があると信じています。さらに、履歴から残っているクラスがいくつかありますが、これらはインターフェイスを実装していないため、動的エージェントに関連付けられることはありません。これだけのことが起こっているので、それは小さな後悔であると言わざるを得ません。
関連記事をさらに読みたい場合は、PHP 中国語 Web サイト にアクセスしてください。 !
以上が静的プロキシと動的プロキシの違いは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。