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

eit*_*ama 2 java

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

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

public class ExternalClass {
  public method1() {...}
  public method2() {...}
  ...
  ...
  public metod30(...) {...}
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我正在寻找一种方法来扩展/包装/复合材料这个类,这样,当有人拨打任一方法,或者,这将弥补原来的方法的调用,以便它们将被推迟,直到合适的时机特殊方法谈到.

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

  • 30次覆盖
  • 30个清单
  • 30班

很多代码,不是很聪明.

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

Tom*_*icz 9

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

interface ExternalClassFacade {
    method1();
    method2();
            //...
    method30();
}
Run Code Online (Sandbox Code Playgroud)

别担心,你不必实现它!只需从中复制所有方法签名即可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();
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这个神奇而晦涩的代码创建了一些实现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);
    }

}
Run Code Online (Sandbox Code Playgroud)

这段代码本身并没有做任何有用的事情 - 当你调用一个方法时,ExternalClassFacade它会ExternalClass使用相同的参数将调用转发给相同的命名方法.所以我们还没有取得任何成就.顺便说一下,我使用MethodUtilsApache Commons 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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


当然,如果您可以使用AspectJ/Spring AOP,您将避免使用整个代理基础结构代码.但基本的想法将只是API将更加愉快.