如何拦截方法调用以延迟执行,将所有调用组合在一起并在java中执行?

时间:2011-08-18 20:28:49

标签: java

我一直试图解决我的问题大约一天,但似乎无法到达任何地方。问题:

我有一个java类,ExternalClass,里面有30个方法。 我还有一个ExternalClassFacade接口。

public class ExternalClass {
  public method1() {...}
  public method2() {...}
  ...
  ...
  public metod30(...) {...}
}

这个类是一个外部库,我无法修改它的代码。 该类运行良好,但我有一种情况,我需要将未定义的时间跨度上的多个调用组合到所有30个方法,延迟执行,并在某个时刻执行所有(串行或并行,我不关心)。 / p>

例如,超过10分钟,方法1到30将被随机调用500次,我希望它们在被调用的时刻什么都不做,但是在10分钟之后我想要调用所有500个调用,因为它们最初被调用

大多数方法都需要我需要记住的参数,我将调用这些方法。

我正在寻找一种方法来扩展/包装/复合这个类,这样当有人调用这些方法中的任何一个时,或者,一个特殊的方法将桥接对原始方法的调用,以便它们被延迟到正确的时刻来了。

我正在考虑扩展类并覆盖所有方法,并管理30个Struct-Like类来保存有关调用的信息,但这需要:

  • 30次覆盖
  • 30列表
  • 30班

很多代码,不是很聪明。

我正在寻找一种更好的方法来实现这一点,我正在考虑捕获调用并保持指向原始方法调用的指针,但这是java,所以这是不可能的。

3 个答案:

答案 0 :(得分:9)

确实非常有趣的问题。第一个问题:ExternalClass实现了一些接口吗?如果是这样,它会简化很多东西,但如果没有,你可以创建一个:

interface ExternalClassFacade {
    method1();
    method2();
            //...
    method30();
}

别担心,您不必实施它!只需复制ExternalClass中的所有方法签名即可。你知道java.lang.Proxy吗?像你这样的问题的奇妙工具:

ExternalClass ext = //obtain target ExternalClass somehow
ExternalClassFacade extFacade = (ExternalClassFacade) Proxy.newProxyInstance(
    ExternalClass.class.getClassLoader(), 
    new Class<?>[]{ExternalClassFacade.class},
    new BatchInvocationHandler(ext));
extFacade.method1();

正如您所看到的,这个神奇而晦涩的代码创建了一些实现ExternalClassFacade的代码,并允许您运行与ExternalClass相同的方法。这是一个缺失的难题:

public class BatchInvocationHandler implements InvocationHandler {

    private final ExternalClass ext;

    public BatchInvocationHandler(ExternalClass ext) {
        this.ext = ext;
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        return MethodUtils.invokeMethod(ext, method.getName(), args);
    }

}

此代码本身没有做任何有用的事情 - 当您在ExternalClassFacade上调用方法时,它会使用相同的参数将调用转发到ExternalClass上的相同命名方法。所以我们还没有取得任何成就。顺便说一下,我正在使用Apache Commons MethodUtils中的Lang来简化反射代码。有可能你已经在CLASSPATH上有了这个库,如果没有,它只是几行额外的代码。

现在看看这个改进的版本:

private static class BatchInvocationHandler implements InvocationHandler {

    private final ExternalClass ext;

    private Queue<Callable<Object>> delayedInvocations = new ConcurrentLinkedQueue<Callable<Object>>();

    public BatchInvocationHandler(ExternalClass ext) {
        this.ext = ext;
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        delayedInvocations.add(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return MethodUtils.invokeMethod(ext, method.getName(), args);
            }
        });
        return null;
    }

}

现在我们到了某个地方:我们不是调用方法,而是将调用包装在Callable内并将其添加到delayedInvocations队列中。当然,因为我们不再调用实际方法,所以返回值只是一个占位符。如果ExternalClass方法的返回类型不同于void,则必须非常小心。

我想你现在看到了光明。您需要的一切就是创建一个线程,该线程将获取队列中收集的所有Callable并批量运行它们。您可以通过各种方式实现,但基本构建块在那里。您也可以选择数据结构,如map或set而不是队列。例如,我可以想象由于某种原因按名称分组方法。


当然,如果您可以使用AspectJ / Spring AOP,您将避免使用整个代理基础架构代码。但基本的想法只会与API更加一致。

答案 1 :(得分:2)

使用AspectJ,您可以introduce an interface到该类,然后编码到该接口。之后,您可以自由添加所需的界面背后的任何行为。或者,只需使用AspectJ编织您正在寻找的收集/执行行为。

CglibJavassist也可以让你通过动态子类化基本代理类(假设它不是最​​终的)来更干净地完成它。

有很多选择。这是我发生的三个第三方。其中一些方法的一个优点是它们可以为您提供对象形式的方法调用的一些表示,您可以在以后轻松地收集和运行它。

答案 2 :(得分:0)

您可以使用方面拦截所有调用以执行外部lib方法并暂停Thread,将Thread的ID写入同步Set。该Set是由另一个Thread监视的单例。

当您的业务规则被触发时,让单例观察者迭代Set并通知每个线程继续处理。该方面将继续并执行每个最初请求的外部方法。