如何正确清理错误初始化的对象?

时间:2019-05-28 04:40:53

标签: java oop class-design

在一次采访中有人问我这个问题。

有一个类,可以称之为A。它具有默认的构造函数,并在其构造函数中初始化2个不同的连接。

方法initDBinitSocket创建一个DB连接和一个套接字连接,并将它们保存在实例字段中。 它们只是一个例子。它们也可以打开文件或其他任何方式。

让我们说一个客户实例化了这个类的一个实例。现在initDB成功执行了,但是initSocket引发了异常。因此对象创建被中止了。但是在异常发生之前,数据库连接没有关闭。这导致资源泄漏。我该如何解决这种资源泄漏?

例如

class A {
    public A(){
        this.dbConnection = initDB();
        this.socketConnection = initSocket(); // throws exception
    }
}

我最初的回答是,我不会在构造函数中初始化它们,而是在单独的init()中初始化它们。他反驳说这可能是一个遗留类,我被要求保持这种状态。在这种情况下,我需要以某种方式清除资源泄漏。我将如何解决这个问题?

我很沮丧,因为实例创建引发了异常,我失去了对这些连接字段的任何引用。因此,我无法致电close()。但是它们仍然会出现在操作系统级别(这是我的猜测)。

注释1

如Interviewer所述,我无法更改已编写的构造函数的行为。我可以对其进行扩展或执行某些操作,但是不能更改代码。

注释2

我认为面试官没有明确地寻找可以处理这种情况的任何代码。有什么JMX东西有帮助吗?我对此很赞,然后我们继续前进。对于那些认为这是一个很好的问题的人,我认为采访者知道这不是一种普遍的做法,并且可能无法回答。

2 个答案:

答案 0 :(得分:0)

我们在这里有一些选择...

  1. 将东西放在其他地方。这显然是有问题的遗留代码。也许这个具有访问控制问题的“斑点”可以移到其他一些可以通过RPC与系统其余部分通信的进程中。如果系统严重损坏,则最好这样做。您可以通过其他方式扩展它,例如合成。但是,如果它是如此封闭,您将无法获得它,那您就筋疲力尽了

  2. 使用字节码修改。您可以做到这一点,并且可以获得足够的杠杆来获得所需的东西。 ByteBuddy可以派上用场。我不会亲自这样做,但是,嘿,有时候绝望的措施需要绝望的解决方案...

  3. 如果可以影响initDB,则可以用其他东西修饰返回值。例如,假设它来自我们 did 控制的某个基类或我们控制的其他方法,那么我们也许可以做类似的事情

Connection initDb() {
  try {
    this.wrappedProvider.initDb();
  } catch(Exception e) {
     // .. destroy the connection...
  }
}

...,然后由于可以影响它,因此可以更改其有效语义。

  1. 您可以影响“ A”的“连接”吗?如何获得“ A”?如果它是从某个DI容器中获得的,或者您可以对其进行 影响的,那么您可以将该类的实现分包出来,以实现在某些给定时间内未进行交谈或初始化的“超时”操作。 hacky,当然,但是如果没有更多信息,那将是我们最好的...

答案 1 :(得分:0)

解决方案1:

提供:

  1. 您可以扩展A类,然后使用B类的实例,
  2. 方法initSocket是可重写的(不是最终的也不是私有的)
  3. 字段dbConnection可从B类(非私有)访问

在出现异常的情况下,您可以覆盖方法initSocket以关闭dbConnection

@Override
protected Socket initSocket() {
    boolean ok = false;
    try {
        Socket result = super.initSocket();
        ok = true;
        return result;
    } finally {
        if (!ok) {
            dbConnection.close();
        }
    }
}

解决方案2:

提供:

  1. 您可以扩展A类
  2. 方法initDb是可重写的(不是最终的也不是私有的)

您可以将A对象包装在另一个类中,并保存连接,以便在发生异常时可以将其关闭:

class B {
    private static ThreadLocal<Connection> CONNECTION = new ThreadLocal<>();
    private final A delegate;

    public B() {
        boolean ok = false;
        try {
            delegate = new A() {
                @Override
                protected Connection initDb() {
                    Connection result = super.initDb();
                    CONNECTION.set(result);
                    return result;
                }
            };
            ok = true;
        } finally {
            if (!ok) {
                Connection cnt = CONNECTION.get();
                if (cnt != null) {
                    cnt.close();
                }
            }
            CONNECTION.set(null);
        }
    }
}