guava的正确引入方式_使用Guava的AbstractInvocationHandler正确完成代理

guava的正确引入方式

不太经常,但有时我们被迫使用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;
    }
 
    @Override
    public 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包装在一起,并异步运行它:

class AsyncHandler<T> implements InvocationHandler {
 
    private final T underlying;
    private final ExecutorService pool;
 
    AsyncHandler(T underlying, ExecutorService pool) {
        this.underlying = underlying;
        this.pool = pool;
    }
 
    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        final Future<Object> future = pool.submit(new Callable<Object>() {
            @Override
            public 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声明的hashCodeequalstoString方法的调用进行编码,并将其分派到调用处理程序的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 {
 
    //...
 
    @Override
    protected Object handleInvocation(Object proxy, final Method method, final Object[] args) throws Throwable {
        //...
    }
 
    @Override
    public String toString() {
        return "Proxy of " + underlying;
    }
}

而已! 我决定重写toString()来帮助调试。 equals()hashCode()都是从Object继承而来的,一开始就很好。 现在,请查看您的代码库并搜索自定义代理。 如果到目前为止您尚未使用AbstractInvocationHandler或类似的程序,则可能会引入一些细微的错误。


翻译自: https://www.javacodegeeks.com/2013/12/proxies-done-right-with-guavas-abstractinvocationhandler.html

guava的正确引入方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值