今日、偶然、JDK の動的プロキシについて少し調べてみたくなり、その使い方をテストしたかっただけなので、次のインターフェイスとクラスを簡単に作成しました。
インターフェースクラス: UserService.java
次のようにコードをコピーします。
パッケージcom.yixi.proxy;
パブリックインターフェース UserService {
public int save();
public void update(int id);
}
実装クラス: UserServiceImpl.java
次のようにコードをコピーします。
パッケージcom.yixi.proxy;
パブリック クラス UserServiceImpl は UserService を実装します {
@オーバーライド
public int save() {
System.out.println("ユーザー保存....");
1 を返します。
}
@オーバーライド
public void update(int id) {
System.out.println("ユーザーの更新 " + id);
}
}
それから、私は心配そうに、欲しかった InvocationHandler を書きました。これの機能は非常に単純で、メソッドの実行の開始時刻と終了時刻を記録することです。
TimeInvocationHandler.java
次のようにコードをコピーします。
パッケージcom.yixi.proxy;
インポート java.lang.reflect.InvocationHandler;
java.lang.reflect.Methodをインポートします。
パブリック クラス TimeInvocationHandler は InvocationHandler を実装します {
@オーバーライド
public Object invoke(オブジェクトプロキシ、メソッドメソッド、Object[] args)
スロー可能 {
System.out.println("startTime : " +System.currentTimeMillis());
オブジェクト obj = メソッド.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
オブジェクトを返します。
}
}
すべての準備が完了したら、もちろんテストの作成を開始します。
テスト.java
次のようにコードをコピーします。
パッケージcom.yixi.proxy;
java.lang.reflect.Proxyをインポートします。
パブリック クラス テスト {
public static void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
問題なく実行されましたが、結果は画面いっぱいに表示される例外でした。
次のようにコードをコピーします。
開始時間: 1352877835040
開始時間: 1352877835040
開始時間: 1352877835040
スレッド「メイン」での例外 java.lang.reflect.UndeclaredThrowableException
$Proxy0.update (ソース不明)
com.yixi.proxy.Test.main(Test.java:11) で
原因: java.lang.reflect.InvocationTargetException
sun.reflect.NativeMethodAccessorImpl.invoke0(ネイティブメソッド)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) で
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) で
java.lang.reflect.Method.invoke(Method.java:597) で
com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) で
...あと 2 件
com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12) 例外は、TimeInvocationHandle の 12 行目の問題を明確に示しています。
次のようにコードをコピーします。
public Object invoke(オブジェクトプロキシ、メソッドメソッド、Object[] args)
スロー可能 {
System.out.println("startTime : " +System.currentTimeMillis());
オブジェクト obj = メソッド.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
オブジェクトを返します。
}
方法としては間違ってないですよ! invoke() メソッドは、method.invoke(Object, Object[]) で必要なパラメータをすべて提供しているように見えるので、当然のこととしてそれを使用します。本当にそう考えている場合は、JDK をだましていることになります。最初に正しい書き方を見てみましょう。以下を読む気がない生徒もいるかもしれないので、少なくとも正しい解決策を教えてください。
TimeInvocationHandler.javaを変更する
次のようにコードをコピーします。
パッケージcom.yixi.proxy;
インポート java.lang.reflect.InvocationHandler;
java.lang.reflect.Methodをインポートします。
パブリック クラス TimeInvocationHandler は InvocationHandler を実装します {
プライベートオブジェクト o;
public TimeInvocationHandler(Object o){
これ.o = o;
}
@オーバーライド
public Object invoke(オブジェクトプロキシ、メソッドメソッド、Object[] args)
スロー可能 {
System.out.println("startTime : " +System.currentTimeMillis());
オブジェクト obj = メソッド.invoke(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
オブジェクトを返します。
}
}
Test.javaを変更する
次のようにコードをコピーします。
パッケージcom.yixi.proxy;
java.lang.reflect.Proxyをインポートします。
パブリック クラス テスト {
public static void main(String[] args) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
正しい出力は次のとおりです。
次のようにコードをコピーします。
開始時間: 1352879531334
ユーザー 2 を更新する
終了時刻: 1352879531334
開始時間: 1352879531334
ユーザー保存....
終了時刻: 1352879531335
コードを減らしたい場合は、匿名クラスを直接記述できます。
パッケージcom.yixi.proxy;
インポート java.lang.reflect.InvocationHandler;
java.lang.reflect.Methodをインポートします。
java.lang.reflect.Proxyをインポートします。
パブリック クラス テスト {
public static void main(String[] args) {
最終 UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader()、
usi.getClass().getInterfaces()、
new InvocationHandler() {
@オーバーライド
public Object invoke(オブジェクトプロキシ、メソッドメソッド、Object[] args)
スロー可能 {
System.out.println("startTime : " +System.currentTimeMillis());
オブジェクト obj = メソッド.invoke(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
オブジェクトを返します。
}
});
u.update(2);
u.save();
}
}
Method.invoke(target,args); の最初のパラメータはターゲット オブジェクトなので、invocationHandler の Invoke メソッドに Object プロキシ パラメータが必要なのはなぜですか? 下を見てみよう!
(私の個人的な意見では) 最も重要な呼び出しメソッドについて、JDK の内容を見てみましょう。
次のようにコードをコピーします。
呼び出す
オブジェクト呼び出し(オブジェクトプロキシ、
メソッドメソッド、
オブジェクト[] 引数)
throws Throwable はプロキシ インスタンスのメソッド呼び出しを処理し、結果を返します。このメソッドは、関連付けられているプロキシ インスタンス上でメソッドが呼び出されるときに、呼び出しハンドラー上で呼び出されます。
パラメータ:
proxy - メソッドが呼び出されるプロキシ インスタンス
Method - プロキシ インスタンスで呼び出されるインターフェイス メソッドに対応する Method インスタンス。 Method オブジェクトの宣言クラスは、メソッドが宣言されるインターフェイスになります。これは、プロキシ クラスがメソッドを継承するプロキシ インターフェイスのスーパーインターフェイスである場合があります。
args - プロキシ インスタンスのメソッド呼び出しに渡される引数値を含むオブジェクトの配列、またはインターフェイス メソッドが引数を取らない場合は null。基本型のパラメータは、適切な基本ラッパー クラス (java.lang.Integer や java.lang.Boolean など) のインスタンスにラップされます。
proxy - メソッドが呼び出されるプロキシ インスタンス? この文はどういう意味ですか? 演技?メソッドはプロキシメソッドですか? それでは、プロキシを実行するメソッドは Object obj = method.invoke(proxy, args); であるべきではないでしょうか? その時は振り向かず、ディスカッション グループに行って Google に行きましたが、何もインスピレーションが得られませんでした。ソース コードを見てみたら何か分かるかもしれないと思いました。
Proxy クラスのソース コードを開いて、コンストラクターが何であるかを確認します。
次のようにコードをコピーします。
protectedInvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
InvocationHandler を Proxy のコンストラクターのパラメーターとして使用します。では、InvocationHandler は何に使用されるのでしょうか。 InvocationHandler の invoke() メソッドとの関係はありますか?
最初に考えたのは、プロキシが内部で次のステートメントを呼び出すのではないかということです。
次のようにコードをコピーします。
h.invoke(this,メソッド名,args);
対応するメソッドを実行するには invoke メソッドを呼び出す必要があるためです。
まずはこれを見てみましょう
ここで、意味がありそうなことがわかります。u.update(2) のとき、Proxy は handler.invoke(proxyClass,update,2) を呼び出します。これは、proxyClass.update(2) を意味します。
u.save(); の場合、Proxy は handler.invoke(proxyClass,save,null) を呼び出します。つまり、proxyClass.save();
Test.java を次のように変更すると、
次のようにコードをコピーします。
パブリック クラス テスト {
public static void main(String[] args) {
最終 UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader()、
usi.getClass().getInterfaces()、
new InvocationHandler() {
@オーバーライド
public Object invoke(オブジェクトプロキシ、メソッドメソッド、Object[] args)
スロー可能 {
null を返します。
}
});
u.update(2);
u.save();
}
}
このとき、匿名クラスのメソッドは null を返すことに注意してください。実行すると、次のようになります。
次のようにコードをコピーします。
スレッド「メイン」での例外 java.lang.NullPointerException
$Proxy0.save(ソース不明)
com.yixi.proxy.Test.main(Test.java:17) で
17 行目に null ポインタがあります。つまり、ここの u.save() メソッドには null 要素が含まれている可能性があります。 u が null の場合、u.update(2) はそこで null ポインタ例外を報告します。17 行目をコメントアウトすると、例外は消え、u.update() が正常に実行できることがわかります。では、なぜそうなるのでしょうか?
実際、これが invoke メソッドが null を返す理由です。
UserService クラスの 2 つのメソッドに注目してください。
次のようにコードをコピーします。
パブリックインターフェース UserService {
public int save();
public void update(int id);
}
Save() メソッドは int 型を返し、update メソッドは void 型を返します。上記の推測によると、handler.invoke() は proxyClass.update(2); を実装しており、invoke メソッドの return メソッドは対応しています。プロキシメソッドの値、
そのため、invoke メソッドが null を返すと、エージェントの update メソッドは戻り値として null を受け取り、元々は void を返すため、例外は報告されず、エージェントの保存はここで返す値は依然として null です。 JVM は null を変換できないため、int 型に変換されるため、この説明は明確に説明でき、以前の推測を相対的に証明することもできます。
InvocationHandler の呼び出しメソッドの最初のパラメーター proxy は、proxyClass が完了する必要があるビジネスを完了するために独自の InvocationHandler オブジェクトの参照を使用してメソッドを呼び出すときに、Proxy クラスがプロキシ オブジェクト proxyClass の参照を渡すことを許可するだけのようです。 。
文才はダメだ!能力には限界がある!皆さんが私を修正してくれることを願っています...