不太经常,但有时我们被迫使用java.lang.reflect.Proxy
编写自定义动态代理类 。 这种机制的确没有魔力,而且即使您永远不会真正使用它,也值得知道-因为Java代理在各种框架和库中无处不在。
这个想法很简单:动态创建一个实现一个或多个接口的对象,但是每次调用这些接口的任何方法时,都会调用我们的自定义回调处理程序。 该处理程序接收到一个被称为( java.lang.reflect.Method
实例)方法的句柄,并且可以以任何方式自由运行。 代理通常用于实现无缝的模拟,缓存,事务和安全性,即它们是AOP的基础。
在我从标题解释com.google.common.reflect.AbstractInvocationHandler
的目的之前,让我们从一个简单的示例开始。 假设我们要在线程池中透明地异步运行给定接口的方法。 诸如Spring(请参阅: 27.4.3 The @Async Annotation
)和Java EE(请参阅: Asynchronous Method Invocation
)之类的流行堆栈已经使用相同的技术来支持此功能。
假设我们提供以下服务:
public interface MailServer {void send(String msg);int unreadCount();
}
我们的目标是异步运行send()
以便几个后续调用不会阻塞而是排队,并在外部线程池中同时执行,而不是在调用线程中执行。 首先,我们需要将创建代理实例的工厂代码:
public class AsyncProxy {public static <T> T wrap(T underlying, ExecutorService pool) {final ClassLoader classLoader = underlying.getClass().getClassLoader();final Class<T> intf = (Class<T>) underlying.getClass().getInterfaces()[0];return (T)Proxy.newProxyInstance(classLoader,new Class<?>[] {intf},new AsyncHandler<T>(underlying, pool));}
}
上面的代码很少做出大胆的假设,例如,一个underlying
对象(我们正在代理的实际实例)恰好实现了一个接口。 在现实生活中,一门课程当然可以实现多个接口,代理也可以实现多个接口,但是出于教育目的,我们对此进行了一些简化。 现在,对于初学者,我们将创建无操作代理,该代理将委托给基础对象而没有任何附加值:
class AsyncHandler<T> implements InvocationHandler {private static final Logger log = LoggerFactory.getLogger(AsyncHandler.class);private final T underlying;private final ExecutorService pool;AsyncHandler1(T underlying, ExecutorService pool) {this.underlying = underlying;this.pool = pool;}@Overridepublic Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {return method.invoke(underlying, args);}}
ExecutorService pool
将在以后使用。 最后一行至关重要–我们在具有相同args
underlying
实例上调用method
。 在这一点上,我们可以:
- 是否调用
underlying
(例如,如果给定的呼叫被缓存/存储) - 更改参数(即出于安全目的)
- 在异常之前/之后/周围/上运行代码
- 通过返回不同的值来改变结果(它必须与
method.getReturnType()
的类型匹配) - …以及更多
在我们的例子中,我们将method.invoke()
与Callable
一起Callable
并异步运行:
class AsyncHandler<T> implements InvocationHandler {private final T underlying;private final ExecutorService pool;AsyncHandler(T underlying, ExecutorService pool) {this.underlying = underlying;this.pool = pool;}@Overridepublic Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {final Future<Object> future = pool.submit(new Callable<Object>() {@Overridepublic Object call() throws Exception {return method.invoke(underlying, args);}});return handleResult(method, future);}private Object handleResult(Method method, Future<Object> future) throws Throwable {if (method.getReturnType() == void.class)return null;try {return future.get();} catch (ExecutionException e) {throw e.getCause();}}
}
提取了额外的handleResult()
方法以正确处理非void
方法。 使用这样的代理很简单:
final MailServer mailServer = new RealMailServer();final ExecutorService pool = Executors.newFixedThreadPool(10);
final MailServer asyncMailServer = AsyncProxy.wrap(mailServer, pool);
现在,即使RealMailServer.send()
花费一秒钟完成,通过asyncMailServer.send()
调用两次也asyncMailServer.send()
花费时间,因为这两个调用都是在后台异步运行的。
损坏的
一些开发人员不了解默认InvocationHandler
实现的潜在问题。 引用官方文件 :
如上所述,将对代理实例上
java.lang.Object
声明的hashCode
,equals
或toString
方法的调用进行编码,并分派给调用处理程序的invoke
方法,就像对接口方法调用进行编码和分派一样。
在我们的案例中,这意味着例如toString()
与MailServer
其他方法在同一线程池中执行,这非常令人惊讶。 现在,假设您有一个本地代理,其中每个方法调用都会触发远程调用。 通过网络调度equals()
, hashCode()
和toString()
绝对不是我们想要的。
用
Guava的AbstractInvocationHandler
是一个简单的抽象类,可以正确处理上述问题。 默认情况下,它将equals()
, hashCode()
和toString()
调度到Object
类,而不是将其传递给调用处理程序。 从直接的InvocationHandler
重构为AbstractInvocationHandler
非常简单:
import com.google.common.reflect.AbstractInvocationHandler;class AsyncHandler<T> extends AbstractInvocationHandler {//...@Overrideprotected Object handleInvocation(Object proxy, final Method method, final Object[] args) throws Throwable {//...}@Overridepublic String toString() {return "Proxy of " + underlying;}
}
而已! 我决定重写toString()
来帮助调试。 equals()
和hashCode()
都是从Object
继承而来的,一开始就很好。 现在,请查看您的代码库并搜索自定义代理。 如果到目前为止您还没有使用AbstractInvocationHandler
或类似的东西,很可能会引入一些细微的错误。
翻译自: https://www.javacodegeeks.com/2013/12/proxies-done-right-with-guavas-abstractinvocationhandler.html