Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



Одно из особенностей, благодаря которой работа с языком программирования Java доставляет такое удовольствие, является его безопасность. Это означает, что в отсутствие машинно-зависимых методов (native method) он неуязвим по отношению

 

 

114

 

к переполнению буферов и массивов, к неконтролируемым указателям, а таКже к другим ошибкам, связанным с разрушением памяти, которые мешают при работе с такими небезопасными языками, как С и С++. При использовании безопасного языка можно писать класс и не сомневаться, что его инварианты будут оставаться правильными, что бы ни произошло с остальными частями системы. В языках, где память трактуется как один гигантский массив, такое невозможно.

Но даже в безопасном языке вы не будете изолированы от других классов, если не приложите со своей стороны не которого усилия. Вы должны писать программы с защитой, исходя из предположения, что клиенты вашего класса будут пред­принимать все возможное для того, чтобы разрушить его инварианты. Это дей­ствительно так, когда кто-то пытается взломать систему безопасности. Однако скорее всего вашему классу придется иметь дело с непредвиденным поведением других клас­сов, которое обусловлено простыми ошибками программиста, пользующегося вашим API. В любом случае имеет смысл потратить время и написать классы, которые будут устойчивы при неправильном поведении клиентов.

Хотя другой класс не сможет поменять внутреннее состояние объекта без какой­-либо поддержки со стороны последнего, оказать такое содействие, не желая того, на удивление просто. Например, рассмотрим класс, задачей которого является представ­ление неизменного периода времени:

 

// Неправильный класс "неизменяемого"

public final class Period {

private final Date start;

private final Date end;

/**

* @рагат start - начало периода.

* @рагат end - конец периода; не должен предшествовать началу.

* @throws IllegalArgumentException. если start позже , чем end.

* @throws"NullPointerException, если start или end равен null.

*/

public Period(Date start, Date end) {

if (start.compareTo(end) > 0)

throw new IllegalArgumentException(start + “ after “ + еnd);

this.start = start;

this.end = end;

}

public Date start() {

return start;   }

 

 

 

115

 

public Date end() {

return end;   }

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

}

На первый взгляд может показаться, что это неизменяемый класс, который успешно выполняет условие, заключающееся в том, что началу периода не предшест­вует его же конец. Однако, воспользовавшись изменяемостью объекта Date, можно с легкостью нарушить этот инвариант:

// Атака на содержимое экземпляра Period

 Date start = new Date();

Date end = new Date();

Period р = new Period(start, end);

     end.setYear(78); // Изменяет содержимое объекта р!

Для того чтобы защитить содержимое экземпляра Реriod от нападений такого типа, для каждого изменяемого параметра конструктор должен создавать резерв­ную копию (defensive сору) и использовать именно эти копии, а не оригинал, как составные части экземпляра Period:

 

// Исправленный конструктор:

// создает резервные копии для параметров

public Period(Date start, Date end) {

this.start = new Date(start.getTime());

           this.end = new Date(end.getTime());

if (this.start.compareTo(this.end) > 0)

throw new IllegalArgumentException(start +" after"+ end);  }

 С новым конструктором описанная ранее атака уже не может воздействовать на экземпляр Period. Заметим, что резервные копии создаются до проверки правиль­ности параметров (статья 23), так что сама проверка выполняется уже не для оригинала, а для его копии. Такой порядок может показаться искусственным, но он необходим, поскольку защищает класс от подмены параметров, которая выполняется из параллельного потока в пределах "окна уязвимости" (window of vulnerability): с момента, когда параметры проверены, и до того момента, когда для них созданы копии.

Заметим также, что для создания резервных копий мы не пользовались методом clone из класса Date. Поскольку Date не является окончательным классом, нет гаран­тии, что метод clone возвратит объект именно класса jаvа.util.Date - он может вернуть экземпляр ненадежного подкласса, созданного специально для нанесения ущерба. Например, такой подкласс может записывать в закрытый статический список

 

116

 

 

ссылку на экземпляр в момент создания последнего, а затем предоставить злоумыш­леннику доступ к этому списку. В результате злоумышленник получит полный конт­роль над всеми этими экземплярами. Чтобы предотвратить атаки такого рода, не используйте метод clone для создания резервной копии параметра, который имеет тип, позволяющий ненадежным партнерам создавать подклассы.

Обновленный конструктор успешно защищает от вышеописанной атаки, ,однако все равно остается возможность модификации экземпляра Period, поскольку его мето­ды предоставляют доступ к его внутренним частям, которые можно поменять:

// Вторая атака на содержимое экземпляра Period

Date start = new Date();

Date end = new Date();

Period р = new Period(start, end);

p.end().setYear(78); // Изменяет внутренние данные р!

для защиты от второй атаки модифицируйте методы доступа таким образом, чтобы возвращались резервные копии изменяемых внутренних полей.

// Исправленные методы доступа:

// создаются резервные копии внутрених полей

public Date start() {

return (Date) start.clone(); }

public Date end() {

return (Date) end.clone();  }

Получив новый конструктор и новые методы доступа, класс Period стал действи­тельно неизменяемым. Теперь некомпетентному программисту или злоумышленнику не удастся нарушить инвариант, гласящий, что начало периода предшествует его концу. Это так, поскольку, за исключением самого класса Period, никакой другой класс не имеет возможности получить доступ хоть к какому-нибудь изменяемому полю экземпляра Period. Указанные поля действительно инкапсулированы в этом объекте.

Заметим, что новые методы доступа, в отличие от нового конструктора, для со­здания резервных копий используют метод clone. Такое решение приемлемо (хотя и необязательно), поскольку мы точно знаем, что внутренние объекты Date в классе Реriod относятся к классу jаvа.util.Date, а не какому-то потенциально ненадежному подклассу.

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

 

117

 

предоставленный клиентом, вы предполагаете использовать как элемент во внутреннем экземпляре Set или как ключ во внутреннем экземпляре Мар, следует учитывать, что инварианты этого набора или схемы могут быть нарушены, если после добавления в них объект вдруг поменяется.

То же самое справедливо и в отношении резервного копирования внутренних компонентов перед возвращением их клиенту. Вы должны дважды подумать, является ли ваш класс изменяемым или нет, прежде чем передавать клиенту ссылку на внутрен­ний компонент, который можно изменить. Возможно, вам все же следует возвращать резервную копию. Крайне важно также помнить о том, что массивы ненулевой длины всегда являются изменяемыми. Поэтому для внутреннего массива вы всегда должны делать резервную копию, прежде чем возвращать его клиенту. Как альтернатива, вы можете возвращать пользователю неизменяемое представление этого массива. Оба приема показаны в статье 12.

Таким образом, урок, который можно извлечь из всего сказанного, заключается в том, что в качестве составных частей объектов вы должны по возможности использовать неизменяемые объекты, чтобы не пришлось беспокоиться о резервном копи­ровании (статья 13). В случае же с примером Period стоит отметить, что опытные программисты для внутреннего представления времени часто применяют не ссылку на объект Date, а простой тип long, возвращаемый методом Date, getTime(). И поступа­ют они так в первую очередь потому, что Date является изменяемым.

Не всегда можно создать резервную копию изменяемого параметра перед его включением в объект. Существуют такие методы и конструкторы, чей вызов означает явную передачу объекта, на который указывает параметр-ссылка. Вызывая такой ме­тод, клиент дает обещание, что он не будет напрямую менять этот объект. Для метода или конструктора, предполагающего, что ему будет полностью передано управление изменяемым объектом, предоставленным клиентом, это обстоятельство должно быть четко оговорено в документации.

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


Поделиться:



Последнее изменение этой страницы: 2019-04-11; Просмотров: 204; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.024 с.)
Главная | Случайная страница | Обратная связь