Как правильно очистить неправильно инициализированный объект? - PullRequest
0 голосов
/ 28 мая 2019

Мне задали этот вопрос в интервью.

Есть класс, давайте назовем его A.Он имеет конструктор по умолчанию и инициализирует в своем конструкторе 2 разных соединения.

Методы initDB & initSocket создает соединение с БД и соединение с сокетом и сохраняет их в поле экземпляра. Это всего лишь пример.Они также могут быть открыты, или что-то еще.

Допустим, клиент создает экземпляр этого класса.Теперь initDB выполнен успешно, но initSocket сгенерировал исключение.Таким образом, создание объекта было прервано.Но соединение с БД не было закрыто до исключения.Это привело к утечке ресурсов.Как бы я справился с такой утечкой ресурсов?

Например:

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

Мой первоначальный ответ состоял в том, что я бы не инициализировал их в конструкторе, а в отдельном init().Он возразил, предположив, что это может быть унаследованный класс, и меня попросили поддержать это.В этом случае мне нужно как-то устранить утечку ресурсов.Как бы я подошел к этой проблеме?

Я был озадачен, потому что создание экземпляра вызвало исключение. Я потерял какую-либо ссылку на эти поля подключения.Поэтому я не могу назвать close() на них.Но они все равно будут присутствовать на уровне ОС (это мое предположение).

Примечание 1

Как заявил интервьюер, Я не могу изменить поведение уже написанного конструктора. Я могу расширять или что-то делать с этим, но не могу изменить код.

Примечание 2

Я думаю, что интервьюер не искал явно никакого кода, который бы обрабатывал этот сценарий.Поможет ли какой-нибудь материал JMX?Я поверил в это, и затем мы пошли дальше.Для тех, кто думает, что это очень хороший вопрос, я думаю, что интервьюер знал, что это не обычная практика и, вероятно, не сможет ответить.

Ответы [ 2 ]

0 голосов
/ 28 мая 2019

Решение 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);
        }
    }
}
0 голосов
/ 28 мая 2019

У нас есть несколько вариантов здесь ...

  1. Уберите вещи из другого места.Это явно проблематичный устаревший код.Возможно, этот «блоб», имеющий проблемы с контролем доступа, можно перенести в какой-то другой процесс, который может связываться с остальной системой через RPC.Вам лучше делать это, если система ужасно сломана.Вы можете расширить его другими способами, такими как композиция;но если он так запечатан, вы не сможете его достать, тогда у вас кость

  2. Используйте модификацию байтового кода.Вы можете сделать это, и вы можете получить достаточно рычагов, чтобы получить то, что вам нужно.ByteBuddy пригодится для этого.Я бы не стал делать это лично, но эй, иногда отчаянные меры требуют отчаянных решений ...

  3. Если вы можете повлиять на initDB, тогда вы можете украсить возвращаемое значение чем-то другим,Например, предположим, что это был некоторый из некоторого базового класса, который мы сделали контролировали, или какой-то другой метод, которым мы управляли, тогда мы могли бы возможно сделать что-то вроде этого

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

... и затем, поскольку вы можете влиять на это, вы можете изменить эффективную семантику этого.

Можете ли вы повлиять на «соединение» «А»?Как это получить "А"?Если он получает его из какого-либо DI-контейнера или чего-то, на что вы можете влиять, то вы можете заменить реализацию для этого класса на что-то, что "истекло", если с ним не разговаривали или не инициализировали в какое-то время.Хаки, конечно, но без дополнительной информации это лучшее, что мы получим ...
...