Javaダイナミックエージェントの簡単な使用と理解
14410 ワード
Javaでは、ダイナミックエージェントはよく使われる機能で、一般的には自分で直接使う必要はありませんが、何が起こっているのかを知る必要があります.
このブログの主な内容はJDKダイナミックエージェントとCGLIBダイナミックエージェントの簡単な使用と理解です.
JDKダイナミックエージェント
JDKダイナミックエージェントは、インタフェースに依存してエージェントを必要とするメソッドを決定します.使用する場合は、次のロールに分けることができます.
TargetInterfaces
-エージェントを必要とするターゲットインタフェース、JDKダイナミックエージェントは、これらのインタフェースのメソッドの作成エージェントTargetObject
-ターゲットインタフェースを実現したオブジェクトInvocationHandler
-メソッド呼び出しプロセッサ、JDKダイナミックエージェントは、InvocationHandler
オブジェクトを介してターゲットメソッドの呼び出しを内部で処理java.lang.reflect.Proxy
-アセンブリInvocationHandler
およびTargetObject
は、プロキシオブジェクトを作成します.作成されたプロキシオブジェクトは、そのサブクラスのインスタンスTargetInterfaces
およびTargetObject
は比較的容易に理解できるが、いくつかのインタフェースおよびこれらのインタフェースを実現したオブジェクト、例えば、以下のようなものである.interface TargetInterfaceA {
void targetMethodA();
}
interface TargetInterfaceB {
void targetMethodB();
}
class TargetClass implements TargetInterfaceA, TargetInterfaceB {
@Override
public void targetMethodA() {
System.out.println("Target method A...");
}
@Override
public void targetMethodB() {
System.out.println("Target method B...");
}
}
上記の例では、ターゲットインタフェースは
[TargetInterfaceA, TargetInterfaceB]
である、ターゲットオブジェクトはTargetClass
の例である.次に、
TargetClass
実装インタフェースのメソッドの呼び出しをブロックするには、InvocationHandler
によってエージェントロジックを定義する必要があります.class SimpleInvocationHandler implements InvocationHandler {
private Object target;
public SimpleInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("Before invocation method %s", method.getName()));
Object result = method.invoke(target, args);
System.out.println(String.format("After invocation method %s", method.getName()));
return result;
}
}
InvocationHandler
このインタフェースは、次のパラメータを持つメソッドinvoke
のみを定義します.proxy
-エージェント・オブジェクト・インスタンスです.ただし、TargetObject
ではなく、Proxy
サブクラスのインスタンスです.したがって、InvocationHandler
インスタンスの内部にTargetObject
method
-呼び出すメソッドargs
-メソッド呼び出しパラメータInvocationHandler
とTargetClass
があれば、TargetObject
を作成し、Proxy
を組み立ててエージェントオブジェクトを作成することができます.主にnewProxyInstance
の方法で完了します.TargetClass targetObject = new TargetClass();
Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), new SimpleInvocationHandler(targetObject););
メソッド
Proxy.newProxyInstance
のパラメータは、次のとおりです.ClassLoader
-一つClassLoader
,簡単なら直接targetObject
のClassLoader
を使えばいいClass>[]
-エージェントするインタフェース配列も,同様に,targetObject
実装されたすべてのインタフェースInvocationHandler
-メソッド呼び出し処理ロジックを定義したInvocationHandler
TargetObject
を作成する必要があります.また、TargetObject
をInvocationHandler
に手動で渡す必要があります.面倒です.完全なコードとテスト:
interface TargetInterfaceA {
void targetMethodA();
}
interface TargetInterfaceB {
void targetMethodB();
}
class TargetClass implements TargetInterfaceA, TargetInterfaceB {
@Override
public void targetMethodA() {
System.out.println("Target method A...");
}
@Override
public void targetMethodB() {
System.out.println("Target method B...");
}
}
class SimpleInvocationHandler implements InvocationHandler {
private Object target;
public SimpleInvocationHandler(Object target) {
this.target = target;
}
public static Object bind(Object targetObject) {
SimpleInvocationHandler handler = new SimpleInvocationHandler(targetObject);
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), handler);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("Before invocation method %s", method.getName()));
Object result = method.invoke(target, args);
System.out.println(String.format("After invocation method %s", method.getName()));
return result;
}
}
public class ProxyTest {
public static void main(String[] args) {
Object proxy = SimpleInvocationHandler.bind(new TargetClass());
((TargetInterfaceA) proxy).targetMethodA();
((TargetInterfaceB) proxy).targetMethodB();
}
}
出力:
Before invocation method targetMethodA
Target method A...
After invocation method targetMethodA
Before invocation method targetMethodB
Target method B...
After invocation method targetMethodB
エージェントクラス
コードを実行するときに、次の行のコードを一番前に置いて、
Proxy
動的に生成されたエージェントクラスがどのようになっているかを確認できます.System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
前のコードで生成されたプロキシクラスは次のとおりです.
final class $Proxy0 extends Proxy implements TargetInterfaceA, TargetInterfaceB {
private static Method m0;
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
//
m4 = Class.forName("classload.TargetInterfaceB").getMethod("targetMethodB");
m3 = Class.forName("classload.TargetInterfaceA").getMethod("targetMethodA");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
// InvocationHandler
public final void targetMethodA() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// InvocationHandler
public final void targetMethodB() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
エージェントクラスのコードを読むと、次のことがわかります.
Proxy
を継承し、ターゲットインタフェースInvocationHandler
によってターゲットメソッドInvocationHandler
渡された最初のパラメータはエージェントオブジェクトであり、TargetObject
1また、エージェントクラスは、
Object
のhashCode、equals、toStringメソッドを取得しました.これらの呼び出しロジックは、InvocationHandler
オブジェクトに対応するメソッドを直接呼び出すことです.たとえば、次のようになります.public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
したがって、ターゲットオブジェクトのこれらのメソッドをエージェントすることもできます.
CGLIBダイナミックエージェント
CGLIBダイナミックエージェントはJDKダイナミックエージェントと似ているが、CGLIBダイナミックエージェントはクラスベースであり、インタフェースを実現する必要はなく、簡単に使用すると
MethodInterceptor
を定義するだけでよい、JDKダイナミックエージェントにおけるInvocationHandler
に相当する.class SimpleMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(String.format("Before invocation method %s", method.getName()));
Object result = proxy.invokeSuper(obj, args);
System.out.println(String.format("After invocation method %s", method.getName()));
return result;
}
}
MethodInterceptor
があれば、エージェントオブジェクトを作成できます.class ProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//
enhancer.setSuperclass(TargetClass.class);
// MethodInterceptor
enhancer.setCallback(new SimpleMethodInterceptor());
//
TargetClass proxyObject = (TargetClass) enhancer.create();
//
proxyObject.targetMethodA();
proxyObject.targetMethodB();
}
}
class TargetClass {
public void targetMethodA() {
System.out.println("Target method A...");
}
public void targetMethodB() {
System.out.println("Target method B...");
}
}
実行出力:
Before invocation method targetMethodA
Target method A...
After invocation method targetMethodA
Before invocation method targetMethodB
Target method B...
After invocation method targetMethodB
エージェントクラス
CGLIB
では、生成されたプロキシクラス2を保存するためにDebuggingClassWriter.DEBUG_LOCATION_PROPERTY
属性の値を設定できます.public class TargetClass$$EnhancerByCGLIB$$eb42b691 extends TargetClass implements Factory {
private MethodInterceptor CGLIB$CALLBACK_0;
static void CGLIB$STATICHOOK1() {
//
var10000 = ReflectUtils.findMethods(new String[]{"targetMethodA", "()V", "targetMethodB", "()V"}, (var1 = Class.forName("TargetClass")).getDeclaredMethods());
CGLIB$targetMethodA$0$Method = var10000[0];
CGLIB$targetMethodA$0$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodA", "CGLIB$targetMethodA$0");
CGLIB$targetMethodB$1$Method = var10000[1];
CGLIB$targetMethodB$1$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodB", "CGLIB$targetMethodB$1");
}
//
final void CGLIB$targetMethodA$0() {
super.targetMethodA();
}
public final void targetMethodA() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
// MethodInterceptor MethodInterceptor
if (var10000 != null) {
var10000.intercept(this, CGLIB$targetMethodA$0$Method, CGLIB$emptyArgs, CGLIB$targetMethodA$0$Proxy);
} else {
super.targetMethodA();
}
}
//
final void CGLIB$targetMethodB$1() {
super.targetMethodB();
}
public final void targetMethodB() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
// MethodInterceptor MethodInterceptor
if (var10000 != null) {
var10000.intercept(this, CGLIB$targetMethodB$1$Method, CGLIB$emptyArgs, CGLIB$targetMethodB$1$Proxy);
} else {
super.targetMethodB();
}
}
final int CGLIB$hashCode$5() {
return super.hashCode();
}
// Object
public final int hashCode() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}
// ...
}
見える
MethodInterceptor
が存在するかどうかを判断して呼び出しTargetClass
を継承し、JDKダイナミックエージェントでProxy
を継承する方法とは異なりますMethodInterceptor
を設定すると、CGLIBはMethodInterceptor
でターゲットメソッドを呼び出すことができ、またMethodInterceptor.intercept
メソッドを呼び出すときに渡される最初のパラメータはエージェントクラスインスタンスであるため、エージェントされるメソッドを実行する必要がある場合はMethodProxy.invokeSuper
で完了し、Method.invoke
を使用すると無限再帰呼び出しを招く.Spring @Configuration
Springを使用する場合、Beanを次のように定義できます.
@Configuration
@ComponentScan(basePackageClasses = Company.class)
public class Config {
@Bean
public Address getAddress() {
return new Address("High Street", 1000);
}
@Bean
public Person getPerson() {
return new Person(getAddress());
}
}
当初、このような方式に対する困惑は、Springがどのように
getAddress
メソッドの呼び出しをブロックしたのかということであった.私の印象ではJDKダイナミックエージェントにはできないということだったが、SpringはCGLIBを通じてConfig
エージェントオブジェクトを作成し、getAddress
メソッドの呼び出しをブロックし、Beanの単例性を保証することに気づいた.Javaでは先のオブジェクトの実際のタイプに応じてメソッドが検索されないため、親に検索されますが、CGLIBで作成されたエージェントオブジェクトは親のメソッドを上書きしているため、エージェントクラスでは
MethodInterceptor
ブロックメソッドの呼び出しでBeanの重複作成を回避できます.これはSpringにおける対応する
MethodInterceptor
がConfigurationClassEnhancer.BeanMethodInterceptor
である.小結
ここでは、JDKダイナミックエージェントとCGLIBダイナミックエージェントのエージェント方式をまとめます.
Proxy
を継承し、TargetInterfaces
を実現したエージェントクラスを作成することでエージェントを完了し、TargetInterfaces
を呼び出すメソッドを呼び出すと、エージェントクラスはメソッド呼び出しをInvocationHandler
完了TargetClass
を継承したエージェントクラスを作成することによりエージェントを完了し、TargetClass
のメソッドを呼び出す場合、MethodInterceptor
が空でなければ、メソッド呼び出しをMethodInterceptor
完了動的エージェントを実装する方法は2つとも近いが,1つはインタフェースを介し,1つはサブクラスを介している.
締めくくり
最初にダイナミックエージェントという概念に触れたのは『Javaコア技術巻』という本を読んだときで、当時Javaを習い始めて間もないのに、これを見た後の考えは、こんな不便なものは、誰が大丈夫なのかということでした.→
結局、広く使われていました(´ゝ`)
似たようなソース注記もありますが、これらの操作は面倒だと言わざるを得ませんが、機能的で強力な特性で、いつも誰かが模様を完成しています(̄▽̄)∞
Footnotes
1最初に
InvocationHandler
このインタフェースを見たとき、最初のパラメータはTargetObject
だと思っていました.2読みやすいように他の不要な内容を省略している