3

我试图找出是否可以创建 Java 动态代理来自动关闭 Autocloseable 资源,而不必记住使用 try-resources 块嵌入此类资源。

例如,我有一个 JedisPool,它有一个 getResource 方法,可以像这样使用:

try(Jedis jedis = jedisPool.getResource() {
   // use jedis client
}

现在我做了这样的事情:

class JedisProxy implements InvocationHandler {

    private final JedisPool pool;

    public JedisProxy(JedisPool pool) {
        this.pool = pool;
    }

    public static JedisCommands newInstance(Pool<Jedis> pool) {
        return (JedisCommands) java.lang.reflect.Proxy.newProxyInstance(
            JedisCommands.class.getClassLoader(),
            new Class[] { JedisCommands.class },
            new JedisProxy(pool));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try (Jedis client = pool.getResource()) {
            return method.invoke(client, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
            throw e;
        }
    }
}

现在,每次我在 Jedis (JedisCommands) 上调用方法时,此方法都会传递给代理,该代理从池中获取新客户端,执行方法并将此资源返回到池中。

它工作正常,但是当我想在客户端上执行多个方法时,每个方法的资源都从池中取出并再次返回(这可能很耗时)。你知道如何改进吗?

4

2 回答 2

3

你最终会得到你自己的“事务管理器”,在这个管理器中你通常会立即将对象返回到池中,但是如果你已经开始了一个“事务”,那么在你“提交”之前对象不会被返回到池中交易”。

由于使用了手工制作的自定义机制,您的使用问题突然try-with-resources变成了实际问题。

将 try 与资源专家一起使用:

  • 语言内置功能
  • 允许您附加一个catch块,并且资源仍然被释放
  • 简单、一致的语法,因此即使开发人员不熟悉它,他也会看到Jedis它周围的所有代码并(希望)认为“所以这一定是使用它的正确方法”

缺点:

  • 你需要记住使用它

您的建议专家(如果我忘记了什么,您可以告诉我):

  • 即使开发者不关闭资源也会自动关闭,防止资源泄漏

缺点:

  • 额外的代码总是意味着有额外的地方可以找到错误
  • 如果您不创建“事务”机制,您可能会受到性能影响(我不熟悉[jr]edis或您的项目,所以我不能说这是否真的是一个问题)
  • 如果您确实创建它,您将拥有更多容易出现错误的额外代码
  • 语法不再简单,任何参与项目的人都会感到困惑
  • 异常处理变得更加复杂
  • 您将通过反射进行所有代理调用(一个小问题,但嘿,这是我的清单;)
  • 可能更多,取决于最终的实施方式

如果您认为我没有提出有效的观点,请告诉我。否则我的断言将仍然是“你有一个寻找问题的'解决方案'”。

于 2015-10-28T20:04:08.217 回答
2

我不认为这是朝着正确的方向发展。毕竟,开发人员应该习惯于正确处理资源,并且 IDE/编译器能够在未使用...处理可自动关闭的资源时发出警告try(…){}。</p>

但是,创建一个代理来装饰所有调用以及添加一种方式来装饰一批多个动作作为一个整体的任务是通用的,因此,它有一个通用的解决方案:

class JedisProxy implements InvocationHandler {

    private final JedisPool pool;

    public JedisProxy(JedisPool pool) {
        this.pool = pool;
    }

    public static JedisCommands newInstance(Pool<Jedis> pool) {
        return (JedisCommands) java.lang.reflect.Proxy.newProxyInstance(
            JedisCommands.class.getClassLoader(),
            new Class[] { JedisCommands.class },
            new JedisProxy(pool));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try (Jedis client = pool.getResource()) {
            return method.invoke(client, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
    public static void executeBatch(JedisCommands c, Consumer<JedisCommands> action) {
        InvocationHandler ih = Proxy.getInvocationHandler(c);
        if(!(ih instanceof JedisProxy))
            throw new IllegalArgumentException();
        try(JedisCommands actual=((JedisProxy)ih).pool.getResource()) {
            action.accept(actual);
        }
    }
    public static <R> R executeBatch(JedisCommands c, Function<JedisCommands,R> action){
        InvocationHandler ih = Proxy.getInvocationHandler(c);
        if(!(ih instanceof JedisProxy))
            throw new IllegalArgumentException();
        try(JedisCommands actual=((JedisProxy)ih).pool.getResource()) {
            return action.apply(actual);
        }
    }
}

请注意,aPool<Jedis>到 a的类型转换JedisPool对我来说看起来很可疑,但我没有更改该代码中的任何内容,因为我没有这些类来验证它。

现在你可以像这样使用它了

JedisCommands c=JedisProxy.newInstance(pool);

c.someAction();// aquire-someaction-close

JedisProxy.executeBatch(c, jedi-> {
    jedi.someAction();
    jedi.anotherAction();
}); // aquire-someaction-anotherAction-close

ResultType foo = JedisProxy.executeBatch(c, jedi-> {
    jedi.someAction();
    return jedi.someActionReturningValue(…);
}); // aquire-someaction-someActionReturningValue-close-return the value

批处理执行需要实例是代理,否则会抛出异常,因为很明显该方法不能保证具有未知生命周期的未知实例的特定行为。

此外,开发人员现在必须了解代理和批处理执行功能,就像他们try(…){}在不使用代理时必须了解资源和语句一样。另一方面,如果不是这样,它们会在不使用批处理方法的情况下在代理上调用多个方法时失去性能,而在不try(…){}使用实际的非代理资源的情况下调用多个方法时会导致资源泄漏……</p>

于 2015-10-29T11:44:56.803 回答