Java:在范围退出时自动调用函数(如 c++ 析构函数)

Posted

技术标签:

【中文标题】Java:在范围退出时自动调用函数(如 c++ 析构函数)【英文标题】:Java: call a function automatically at scope exit (like c++ destructors) 【发布时间】:2011-10-27 14:26:28 【问题描述】:

对于 mysql 连接,我有一个连接对象并使用事务机制 connection.startTransaction()connection.commitTransaction()connection.rollbackTransaction()

对于每个startTransaction(),必须始终调用commitTransaction()rollbackTransaction()。错过这样一个电话或同时拨打这两个电话会破坏我的交易系统。

所以我按以下方式使用它们:

boolean i_am_in_a_transaction=true;
try 
    connection.startTransaction();
    ...
    i_am_in_a_transaction=false;
    connection.commitTransaction();
 finally 
    if(i_am_in_a_transaction) 
        connection.rollbackTransaction();
    

这确保了声明的调用顺序,但它很费力,因为我必须在使用事务的任何地方都写这行。

在 C++ 中,如果调用了 commit() 函数,我将使用一个事务对象检查其析构函数,否则调用 rollback()

class Transaction 
    public:
        Transaction()
            :am_in_transaction(false) 
        

        ~Transaction() 
            if(_am_in_transaction) 
                rollback();
            
        

        void startTransaction() 
            _am_in_transaction=true;
            ...start Transaction...
        

        void commit() 
            _am_in_transaction=false;
            ...commit Transaction...
        

        void rollback() 
            _am_in_transaction=false;
            ...rollback Transaction...
        

    private:
        bool _am_in_transaction;

这样我就可以在一个地方实现逻辑并且可以非常简单地使用它:

Transaction my_transaction;
my_transaction.startTransaction;
...
my_transaction.commit();

这段代码比上面带有 try/finally 块的 java 代码要简单得多。

有没有办法在 java 中实现这种行为,而无需将逻辑专用于调用者并让他实现 try/finally 块?

在范围退出时自动调用函数之类的方法会对我有所帮助。

【问题讨论】:

Does java have a using clause? 的可能重复项 @sgusc Java 7 会自动关闭资源但不会自动调用未知函数 @JohnVint 是的,好点。我忘记了using 的语义。我把它等同于我脑海中 Python 的with 语句。感谢您的澄清! Java 7 Automatic Resource Management JDBC的可能重复 【参考方案1】:

与 C++ 中的析构函数类似(非常重要)的是方法finalize()。不同之处在于无法保证垃圾收集器何时真正调用它,因此不建议依赖它。

否则,你能做的最好的就是try/finally 块。

【讨论】:

更多,最多调用一次。所以即使你下次救出对象也不会执行finalize。 finalize() 不是析构函数。 JVM负责调用它。如果需要,它会在需要时调用它。因此,强烈建议不要将任何逻辑基于 finalize()。它只能用于后备。【参考方案2】:

Java 没有析构函数。 但是有几个“标准”解决方案可以帮助您。

1.使用名为“模板方法”的模式。

abstract class Base 
    public query() 
         openTransaction();
         doQuery();
         closeTransaction();
    
    protected abstract doQuery();

现在您应该在您创建的每个子类中实现doQuery(),并通过从基类调用“query()”来使用它。

2.使用面向方面的编程

3.使用装饰器(包装器)模式。

4. 使用一种流行的 ORM 框架(Hibernate、iBatis 等)为您解决所有这些问题,并且完全不处理低级 JDBC 的东西。

【讨论】:

我会添加 #5,使用 Spring 的内置机制,它是 #2、#3 和 #4 的组合。 @Dave Newton,我同意你的观点,但我认为它是#4 的一部分 有点,但它们可以在 Spring 之外使用,因此有区别:)【参考方案3】:

想到了几个解决方案...

正如@Dark Falcon 指出的那样,这对于try with resources 调用将是一个很好的用例,它将在尝试结束时自动清理您的资源。不幸的是,这仅在 Java 7 中可用。

Java 类确实定义了一个 finalize() 方法,当对象被垃圾回收时可以调用该方法,但重写此方法几乎从来都不是正确的做法。

如果你迷上了“在函数返回时执行代码”这个想法,我认为你唯一的选择就是使用Aspect Oriented Programming。如果您阅读了诸如AspectJ 之类的一些包或查看使用AOP with Spring,您可以做一些配置魔术,通过拦截调用来获取函数返回时执行的代码。这是 an example 在函数返回时使用 Spring AOP 执行其他代码。

【讨论】:

【参考方案4】:

如果可以选择更新到 java 7,则新的 try with resources 将在 Closable 实现中执行方法 close

【讨论】:

【参考方案5】:

我只有一种方法,而且只做一次。

public static void update(Connection connection, String updateSQL) 
    PreparedStatement update = null;
    try 
      try 
        connection.startTransaction();
        update = connection.prepareStatement(updateString);
        update.executeUpdate();
       finally 
        connection.rollbackTransaction();
      
    connection.commitTransaction();
     finally 
        if(update != null) update.close();
    

稍后

update(connection, updateSQL1);
update(connection, updateSQL2);
// etc.

【讨论】:

我不太了解你的代码(最后总是被执行——即使没有抛出异常),但我想我明白了你的想法。这在事务中只有一个 sql 语句时有效。但一个事务通常由多个语句组成。 在这种情况下,您可以传递一个 List 或String... 的 SQL 语句。 除了它们相互依赖 不确定更新如何相互依赖。有些是根据其他是否失败有条件地执行,但你不能用 SQL 写这个?但是如果你需要这个,你可以重复调用 update()。 你是对的 - 当只有更新语句时,这种方法有效。【参考方案6】:

我知道这是一个老问题,但为了他人的利益:

您可以使用实现接口的匿名内部类来完成这项工作,类似于 Java 比较器和列表排序机制的工作方式。这允许您将内部类视为执行范围。

例如修改你原来的例子:

class Transaction

   boolean i_am_in_a_transaction=false;

   interface AutoRollback
   
       void runQueries() throws Throwable;
   

    void startTransaction() 
        i_am_in_a_transaction=true;
        ...start Transaction...
    

    void commit() 
        i_am_in_a_transaction=false;
        ...commit Transaction...
    

    void rollback()             
        i_am_in_a_transaction=false;
        ...rollback Transaction...
    

   public void execute(AutoRollback work)
   
      try 
          work.runQueries();
       catch ( Throwable t ) 
          rollback();
          throw t;
      
   

然后是如何使用它的示例:

void test() throws WhateverException

    Transaction my_transaction;
    my_transaction.startTransaction();

    my_transaction.execute( new AutoRollback()  public void runQueries() throws Throwable 

     ... perform your queries: can be more than one, complex code, etc. ...
     ... local variables from the enclosing scope can be used as long as they are final... 

    );

    my_transaction.commit();

如果您有 Java 8,使用 lambdas 会变得更漂亮,因为它保存了 new AutoRollback 语法。

如果您没有 Java 8 并且过多的标点符号仍然困扰着您,您应该能够使用注解处理器和代码注入使其读起来更漂亮。 编译时注解设置为 LOCAL_VARIABLE 的目标是您想要的,然后将其应用于my_transaction

...假设您的工作场所允许使用 apt 或预处理器等注释处理器,并且您希望为语法糖做那么多工作。

【讨论】:

以上是关于Java:在范围退出时自动调用函数(如 c++ 析构函数)的主要内容,如果未能解决你的问题,请参考以下文章

C ++:在超出范围之前调用析构函数?

c++ delete 调用析构函数问题。

C++如何自动调用析构函数?

C++ 析构函数何时被调用?

C++ 析构函数何时被调用?

静态对象成员会在所属类的析构函数被调用时自动析构吗?