ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## [动态代理](https://lingcoder.gitee.io/onjava8/#/book/19-Type-Information?id=%e5%8a%a8%e6%80%81%e4%bb%a3%e7%90%86) *代理*是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构: ~~~ // typeinfo/SimpleProxyDemo.java interface Interface { void doSomething(); void somethingElse(String arg); } class RealObject implements Interface { @Override public void doSomething() { System.out.println("doSomething"); } @Override public void somethingElse(String arg) { System.out.println("somethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; SimpleProxy(Interface proxied) { this.proxied = proxied; } @Override public void doSomething() { System.out.println("SimpleProxy doSomething"); proxied.doSomething(); } @Override public void somethingElse(String arg) { System.out.println( "SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); } } class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } } ~~~ 输出结果: ~~~ doSomething somethingElse bonobo SimpleProxy doSomething doSomething SimpleProxy somethingElse bonobo somethingElse bonobo ~~~ 因为`consumer()`接受`Interface`,所以它不知道获得的是`RealObject`还是`SimpleProxy`,因为两者都实现了`Interface`。 但是,在客户端和`RealObject`之间插入的`SimpleProxy`执行操作,然后在`RealObject`上调用相同的方法。 当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更---所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对`RealObject`中方法的调用,或衡量此类调用的开销,该怎么办?你不想这部分代码耦合到你的程序中,而代理能使你可以很轻松地添加或删除它。 Java 的*动态代理*更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个*调用处理程序*,该处理程序负责发现调用的内容并决定如何处理。这是`SimpleProxyDemo.java`使用动态代理重写的例子: ~~~ // typeinfo/SimpleDynamicProxy.java import java.lang.reflect.*; class DynamicProxyHandler implements InvocationHandler { private Object proxied; DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); if (args != null) for (Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); // Insert a proxy and call again: Interface proxy = (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real)); consumer(proxy); } } ~~~ 输出结果: ~~~ doSomething somethingElse bonobo **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null doSomething **** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@6bc7c054 bonobo somethingElse bonobo ~~~ 可以通过调用静态方法`Proxy.newProxyInstance()`来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口`InvocationHandler`的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。 `invoke()`方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在`invoke()`内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。 通常执行代理操作,然后使用`Method.invoke()`将请求转发给被代理对象,并携带必要的参数。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用: ~~~ // typeinfo/SelectingMethods.java // Looking for particular methods in a dynamic proxy import java.lang.reflect.*; class MethodSelector implements InvocationHandler { private Object proxied; MethodSelector(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("interesting")) System.out.println( "Proxy detected the interesting method"); return method.invoke(proxied, args); } } interface SomeMethods { void boring1(); void boring2(); void interesting(String arg); void boring3(); } class Implementation implements SomeMethods { @Override public void boring1() { System.out.println("boring1"); } @Override public void boring2() { System.out.println("boring2"); } @Override public void interesting(String arg) { System.out.println("interesting " + arg); } @Override public void boring3() { System.out.println("boring3"); } } class SelectingMethods { public static void main(String[] args) { SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance( SomeMethods.class.getClassLoader(), new Class[]{ SomeMethods.class }, new MethodSelector(new Implementation())); proxy.boring1(); proxy.boring2(); proxy.interesting("bonobo"); proxy.boring3(); } } ~~~ 输出结果: ~~~ boring1 boring2 Proxy detected the interesting method interesting bonobo boring3 ~~~ 在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。 动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在 Erich Gamma 等人的*设计模式*中了解有关*代理*和其他设计模式的更多信息。 (Addison-Wesley,1995年),以及[设计模式](https://lingcoder.gitee.io/onjava8/#/./25-Patterns)一章。