简体   繁体   English

自动关闭的Java代理(Jedis资源)

[英]Java proxy for Autocloseable (Jedis resources)

I am trying to find out whether it is possible to create Java dynamic proxy to automatically close Autocloseable resources without having to remember of embedding such resources with try-resources block. 我试图找出是否有可能创建Java动态代理来自动关闭Autocloseable资源,而不必记住用try-resources块嵌入此类资源。

For example I have a JedisPool that has a getResource method which can be used like that: 例如,我有一个JedisPool,它具有一个getResource方法,可以这样使用:

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

For now I did something like that: 现在,我做了这样的事情:

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;
        }
    }
}

Now each time when I call method on Jedis (JedisCommands) this method is passed to proxy which gets a new client from the pool, executes method and returns this resource to the pool. 现在,每次我在Jedis(JedisCommands)上调用方法时,此方法都会传递给代理,该代理从池中获取新客户端,执行该方法并将此资源返回到池中。

It works fine, but when I want to execute multiple methods on client, then for each method resource is taken from pool and returned again (it might be time consuming). 它工作正常,但是当我想在客户端上执行多个方法时,则对于每个方法,资源都从池中获取并再次返回(这可能很耗时)。 Do you have any idea how to improve that? 你有什么想法要改进吗?

You would end up with your own "transaction manager" in which you normally would return the object to the pool immediately, but if you had started a "transaction" the object wouldn't be returned to the pool until you've "committed" the "transaction". 您最终将拥有自己的“事务管理器”,在该事务管理器中,通常会立即将对象返回到池中,但是如果您开始了“事务”,则在您“提交”之前,对象不会返回到池中。交易”。

Suddenly your problem with using try-with-resources turns into an actual problem due to the use of a hand-crafted custom mechanism. 由于使用了手工定制的机制,因此突然出现的使用try-with-resources的问题变成了实际问题。

Using try with resources pros: 尝试使用资源专家:

  • Language built-in feature 语言内置功能
  • Allows you to attach a catch block, and the resources are still released 允许您附加catch块,并且资源仍被释放
  • Simple, consistent syntax, so that even if a developer weren't familiar with it, he would see all the Jedis code surrounded by it and (hopefully) think "So this must be the correct way to use this" 简单,一致的语法,因此即使开发人员不熟悉它,他也会看到所有Jedis代码被其包围,并且(希望)认为“因此,这必须是使用此代码的正确方法”

Cons: 缺点:

  • You need to remember to use it 您需要记住使用它

Your suggestion pros (You can tell me if I forget anything): 您的建议专家(您可以告诉我是否忘记了什么):

  • Automatic closing even if the developer doesn't close the resource, preventing a resource leak 即使开发人员不关闭资源也自动关闭,从而防止资源泄漏

Cons: 缺点:

  • Extra code always means extra places to find bugs in 额外的代码始终意味着在其中查找错误的额外地方
  • If you don't create a "transaction" mechanism, you may suffer from a performance hit (I'm not familiar with [jr]edis or your project, so I can't say whether it's really an issue or not) 如果您不创建“事务”机制,那么您可能会遭受性能[jr]edis (我对[jr]edis或您的项目不熟悉,所以我不能说这是否真的是个问题)
  • If you do create it, you'll have even more extra code which is prone to bugs 如果您确实创建了它,那么您将拥有更多易于出错的额外代码。
  • Syntax is no longer simple, and will be confusing to anyone coming to the project 语法不再简单,它将使任何参加该项目的人感到困惑
  • Exception handling becomes more complicated 异常处理变得更加复杂
  • You'll be making all your proxy-calls through reflection (a minor issue, but hey, it's my list ;) 您将通过反思进行所有代理呼叫(一个小问题,但是,这是我的列表;)
  • Possibly more, depending on what the final implementation will be 可能更多,取决于最终的实现方式

If you think I'm not making valid points, please tell me. 如果您认为我没有提出正确的观点,请告诉我。 Otherwise my assertion will remain "you have a 'solution' looking for a problem". 否则,我的主张将仍然是“您有一个寻找问题的'解决方案'”。

I don't think that this is going into the right direction. 我认为这没有朝正确的方向发展。 After all, developers should get used to handle resources correctly and IDEs/compilers are able to issue warnings when autoclosable resources aren't handled using try(…){} 毕竟,开发人员应该习惯于正确处理资源,并且在未使用try(…){}来处理可自动关闭的资源时,IDE /编译器能够发出警告。

However, the task of creating a proxy for decorating all invocations and the addition of a way to decorate a batch of multiple action as a whole, is of a general nature, therefore, it has a general solution: 但是,创建用于装饰所有调用的代理以及添加一种整体装饰一批多个动作的方法的任务具有一般性,因此,它具有一般性的解决方案:

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);
        }
    }
}

Note that the type conversion of a Pool<Jedis> to a JedisPool looked suspicious to me but I didn't change anything in that code as I don't have these classes to verify it. 请注意,从Pool<Jedis>JedisPool的类型转换对我来说JedisPool很可疑,但是我没有更改该代码中的任何内容,因为我没有这些类来进行验证。

Now you can use it like 现在您可以像

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

The batch execution requires the instance to be a proxy, otherwise an exception is thrown as it's clear that this method cannot guarantee a particular behavior for an unknown instance with an unknown life cycle. 批处理执行需要实例作为代理,否则会引发异常,因为很明显,此方法不能保证生命周期未知的未知实例的特定行为。

Also, developers now have to be aware of the proxy and the batch execution feature just like they have to be aware of resources and the try(…){} statement when not using a proxy. 而且,开发人员现在必须了解代理和批处理执行功能,就像他们在不使用代理时必须了解资源和try(…){}语句一样。 On the other hand, if they aren't, they lose performance when invoking multiple methods on a proxy without using the batch method, whereas they let resources leak when invoking multiple methods without try(…){} on an actual, non-proxy resource… 另一方面,如果不是这样,则在不使用批处理方法的情况下调用代理上的多个方法时,它们会失去性能,而在实际的非代理中,如果不使用try(…){}来调用多个方法,则会使资源泄漏try(…){}资源...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM