Мои Конспекты
Главная | Обратная связь


Автомобили
Астрономия
Биология
География
Дом и сад
Другие языки
Другое
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Металлургия
Механика
Образование
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Туризм
Физика
Философия
Финансы
Химия
Черчение
Экология
Экономика
Электроника

При необходимости создавайте метод readResolue



 

В статье 2 описывается шаблон Singleton и приводится следующий пример класса. синглтона. Этот класс ограничивает доступ к конструктору с тем, чтобы гарантировать создание только одного экземпляра:

 

public class Elvis {

public static final Elvis INSTANCE = new Elvis();

private Elvis() { }

// Остальное опущено

}

 

Как отмечалось в статье 2, этот класс перестает быть синглтоном, если в его декларацию добавляются слова "implements Serializable". Не имеет значения, использует этот класс сериализованную форму, предлагаемую по умолчанию, или спе­циальную форму (статья 55). Не имеет также значения, предоставляется ли пользова­телю в этом классе явный метод readObject (статья 56). Любой метод readObject, явный или применяемый по умолчанию, возвращает вновь созданный экземпляр, а не тот, что был сформирован в момент инициализации класса. До выхода версии 1.2 написать сериализуемый класс-синглтон было невозможно.

В версии 1.2 механизм сериализации пополнился функцией readResolve [Serialization, 3.6]. Если класс десериализуемого объекта имеет метод readResolve с соответствующей декларацией, то по завершении десериализации этот метод будет вызван для вновь созданного объекта. Вместо ссылки на вновь созданный объект клиенту передается ссылка, возвращаемая этим методом. В большинстве случаев ис­пользования этого механизма ссылка на новый объект не сохраняется, а сам объект фактически является мертворожденным и немедленно становится объектом для сборки мусора.

Если класс Elvis создан так, чтобы реализовать интерфейс Serializable, то для обеспечения свойства синглтона достаточно будет создать следующий метод readResolve:

 

private Object readResolve() throws ObjectStreamException {

// Возвращает только истинный экземпляр Elvis и дает возможность

// сборщику мусора позаботиться об Еlvis - самозванце

return INSTANCE; }

 

 

 

Метод игнорирует десериализованный объект, просто возвращая уникальный эк­земпляр Elvis, который был создан во время инициализации класса. По этой причине необходимо, чтобы в сериализованной форме экземпляра Elvis не содержалось ника­ких реальных данных, а все поля экземпляра были помечены как transient. Это отно­сится не только к классу Elvis, но и К любым другим синглтонам.

Метод readResolve необходим не только для синглтонов, но и для всех остаЛЬНblХ классов, контролирующих свои экземпляры (instance-controlled), т. е. для тех классов, в которых сохранение некоего инварианта требует строгого контроля над процедурой создания экземпляров. Еще один пример класса, контролирующего свои экземпляры,- перечисление типов (статья 21), чей метод readResolve должен возвращать канонический экземпляр, соответствующий указанной константе в пере­числении. Главное правило таково, что если вы пишете сериализуемый класс, не имею­щий ни открытых, ни защищенных конструкторов, проверьте, нужен ли этому классу метод r~adResolve.

Второй вариант применения метода readResolve - в качестве безопасной альтернаТИВbI защищенному методу readObject (статья 56). В этом случае из ме­тода readObject изымаются все про верки параметров, а также процедуры создания резервных копий, и применяются проверки и резервное копирование, предоставляемые обычным конструктором. При использовании сериализованной формы, предлагаемой по умолчанию, метод readObject может быть полностью исключен. Как объясняется в статье 56, это дает возможность клиенту, имеющему злой умысел, создать экземп­ляр с испорченными инвариантами. Однако потенциально испорченный десериализо­ванный экземпляр никогда не будет передан в активное использование. Из него просто будут извлечены данные для открытого конструктора или статического метода генерации, после чего он будет отброшен.

Изящество этого подхода заключается в том, что фактически устраняются со­ставные части сериализации, выходящие за пределы языка, что делает невозможным нарушение каких-либо инвариантов класса, имевшихся до того, как этот класс был сделан сериализуемым. Чтобы продемонстрировать эту методику, в примере с клас­сом Реriod (статья 56) вместо защищенного метода readObject можно воспользо­ваться следующим методом readResolve:

 

// Идиома защищенного метода readResolve

private Object readResolve() throws ObjectStreamException {

return new Period(start, end); }

 

Этот метод останавливает обе атаки, описанные в статье 56. Идиома защищенного метода readResolve имеет несколько преимуществ перед защищенным readObject. Это чисто механический прием, позволяющий делать класс сериализуемым, не под­вергая риску его инварианты. Он требует небольшого объема кода, немного раз­мышлений и работает надежно. Наконец, он устраняет искусственные ограничения, накладываемые сериализацией на использование окончательных полей.

 

 

 

 

Хотя идиома защищенного метода readResolve не имеет широкого примене­ния, она заслуживает серьезного рассмотрения. Главный ее недостаток состоит в том, что она не годится для класса, который можно наследовать за пределами соответствующего пакета. Это не касается неизменяемых классов, поскольку они обычно являются окончательными (статья 13). Недостатком этой идиомы является и некоторое снижение скорости десериализации, поскольку она сопровождается созданием дополнительного объекта. На моей машине десериализация экземпляров Реriod замедлил ась примерно на один процент по сравнению с защищенным мето­дом readObj ect.

Большое значение имеет доступность метода readResolve. Если вы помещаете его в окончательный класс, например синглтон, он должен быть закрытым. Помещая метод readResolve в расширяемый класс, вы должны внимательно изучить его доступ­ность. Если он будет закрытым, его не удастся использовать ни в одном из подклас­сов. Если он будет доступен только в пакете, его применение ограничится подклассами из того же пакета. Защищенный или открытый метод будет доступен во всех под­классах, если только они его не переопределят. Если метод readResolve является защищенным или открытым и не переопределяется в подклассе, то десериализация сериализованного экземпляра подкласса создаст экземпляр суперкласса, а это, вероят­но, будет совсем не то, чего вы ожидали.

Предыдущее рассуждение наводит на мысль о причине, по которой в классах, допускающих наследование, метод readResolve не может служить заменой защищен­ному методу readObject. Если бы в суперклассе метод readResolve был помечен как final, это не позволило бы правильно десериализовать экземпляры подкласса. Если бы он был переопределяемым, то подкласс, написанный злоумышленником, мог бы его переопределить таким методом, который возвращает испорченный экземпляр.

Подведем итоги. Вы должны использовать метод readResolve для защиты "инвариантов контроля над экземплярами" в синглтонах и других классах, которые контролируют свои экземпляры. По существу, метод readResolve превращает метод readObject из де-факто открытого конструктора в де-факто открытый статический метод генерации. Метод readResolve также полезен в качестве простой альтернати­вы защищенному методу readObject в классах, для которых запрещено наследование за пределами соответствующего пакета.