【问题标题】:How to properly cleanup incorrectly initialized object?如何正确清理错误初始化的对象?
【发布时间】:2019-05-28 04:40:52
【问题描述】:

我在一次采访中被问到这个问题。

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

方法initDB & initSocket 创建一个数据库连接和一个套接字连接并将它们保存在实例字段中。 他们只是一个例子。它们也可以是文件打开或其他任何东西。

假设客户端实例化了此类的一个实例。现在initDB 执行成功,但initSocket 抛出异常。所以对象创建被中止。但是数据库连接在异常之前没有关闭。这导致了资源泄漏。我将如何处理这种资源泄漏?

例如

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

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

我被难住了,因为实例创建引发了异常,我丢失了对这些连接字段的任何引用。所以我不能给他们打电话close()。但它们仍然会出现在操作系统级别(这是我的猜测)。

注 1

正如面试官所说,我无法更改已经编写好的构造函数的行为。我可以扩展或围绕它做一些事情,但不能更改代码。

注 2

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

【问题讨论】:

  • 如果它抛出,你可以抓住它并在catch内处理它
  • @VaughanHilts,不。我确实明确要求过,但他直接拒绝了。基本上现在已经编写了构造函数。我无法修改它。我可以扩展类或做其他事情,但不能修改构造函数。
  • 为什么不像try { this.socketConnection = initSocket(); }catch(Exception e) { this.socketConnection = null;}那样使用try catch
  • @SudhirOjha,因为我无法修改构造函数中的代码。请参阅上面我对 VaughanHilts 的回复。
  • 您不能尝试将super 包装起来。

标签: java oop class-design


【解决方案1】:

我们有几个选择...

  1. 把东西装在别处。这显然是有问题的遗留代码。也许这个具有访问控制问题的“blob”可以移动到可以通过 RPC 与系统其余部分通信的其他进程中。如果系统严重损坏,你最好这样做。您可以通过其他方式扩展它,例如组合;但是,如果它被密封得那么严密,你拿不到,那你就完蛋了

  2. 使用字节码修改。你可以这样做,你可以获得足够的筹码来获得你需要的东西。 ByteBuddy 会为此派上用场。我不会亲自这样做,但嘿,有时绝望的措施需要绝望的解决方案......

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

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

...然后既然你可以影响它,你就可以改变它的有效语义。

  1. 你能影响“A”的“连接”吗?它是如何得到“A”的?如果它是从某个 DI 容器或您可以影响的东西中获取的,那么您可以将该类的实现替换为如果在某个给定时间内未与之交谈或初始化的“超时”的东西。哈基,当然,但没有更多信息,这是我们将得到的最好的……

【讨论】:

    【解决方案2】:

    解决方案 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);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-02
      • 1970-01-01
      • 1970-01-01
      • 2018-05-18
      相关资源
      最近更新 更多