РЕГИСТРАЦИЯ |
EMAIL
ПАРОЛЬ

Vladimir Ovchinnikov
© Fusionsoft 2007

 

Введение

Кэширование - мощный инструмент оптимизации доступа к данным, используемый для решения самых различных задач: в процессорах - для ускорения доступа к данным оперативной памяти; в операционных системах - для ускорения доступа к информации, хранимой на жестких дисках; в прокси-серверах - для ускорения доступа к документам, запрашиваемым из Интернета; и во многих других случаях.

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

 

Пример кэширования свойств объектов

Приведем простейший пример, в котором кэширование было бы актуально:

public class CachePattern {
	  private Integer area=null;
	  
	  public int getWidth(){
		...
	  }
	  
	  public int getHeight(){
		...
	  }
	  
	  public int getArea(){
		  if (area == null)
			  area = new Integer(getAreaCalculated());
		  return area;
	  }

	  private int getAreaCalculated(){
		  return getWidth()*getHeight();
	  }

	  public void clearArea (){
		  area = null;
	  }
...

Если допустить, что методы getWidth и getHeight могут требовать значительных ресурсов, например, обращаться к внешнему хранилищу данных, то в данном примере кэширование в методе getArea выглядит достаточно разумным. При этом необходимо предусмотреть метод очистки кэша clearArea, который должен вызываться всякий раз, когда изменяется ширина или высота в примере выше, например, следующим образом:

public void	setWidth(int width){
		  ...
		  clearArea();
}
	  
public void	setHeight(int height){
		  ...
          clearArea();
}

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

 

Кэширование свойств объектов с помощью библиотеки обновляемых объектов

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

Для того, чтобы в вышеприведенном примере избавить программиста от необходимости отслеживать изменения исходных свойств, достаточно добавить аннотацию @Refreshable для класса CachePattern

@Refreshable
public class CachePattern {

и создавать объекты этого класса с помощью WrapManager.newInstance данной библиотеки:

public static CachePattern newCachePattern (){
			return WrapManager.newInstance(CachePattern.class);
		}

Объекты, созданные таким образом, оказываются под управлением библиотеки обновляемых объектов, которая берет на себя функции управления кэшированием. Причем уровень управления кэшированием может различаться. В простейшем случае, данная библиотека может обеспечить вызов соответствующих clear-методов. Например, в вышеописанном случае метод clearArea будет вызван автоматически после завершения работы любого из методов setWidth или setHeight.

Почему? Как библиотека обновляемых объектов определила, что необходимо вызвать именно clearArea и именно после завершения работы setWidth или setHeight? Следующим образом. Во-первых, setWidth и setHeight являются сеттерами свойств, для которых геттерами являются соответственно getWidth и getHeight. Во-вторых, в теле геттера getArea, вычисляющего значение нашего свойства, присутствует вызов обоих геттеров getWidth и getHeight, следовательно, наше вычислимое свойство зависит от этих двух. А значит, при изменении любого из этих свойств должен быть очищен кэш для свойства площади (area). Далее, механизм обновляемых объектов ищет метод, который назывался бы clear<имя свойства>, в нашем случае clearArea, и запускает его после завершения работы любого из сеттеров setWidth и setHeight.

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

 

Декларативное кэширование свойств объектов

Библиотека обновляемых объектов позволяет избавить программиста и от необходимости объявления кэширующей переменной, и от программирования доступа к ней. Для этого достаточно на геттерах, требующих кэширования, указать аннотацию @Cached. В этом случае вся процедура кэширования и очистки кэша осуществляется на уровне механизма обновляемых объектов, а вышеприведенный пример примет следующий вид:

@Refreshable
public class CachePattern {
	  public int getWidth(){
		...
	  }
	  
	  public int getHeight(){
		...
	  }
	  
	  @Cached
	  public int getArea(){
		  return getWidth()*getHeight();
	  }

	  public void	setWidth(int width){
		  ...
	  }
	  
	  public void	setHeight(int height){
		  ...
	  }
...

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

Основным вопросом, возникающим у разработчиков, использующих библиотеку обновляемых объектов, является вопрос, каким образом определяется, что свойство объекта, которое может вычисляться из других свойств, изменило свое значение. Для отслеживания такого момента анализируется последовательность run-time вызовов при вычислении данного свойства. Если значение свойства объекта изменилось, то все свойства, использующие его напрямую или опосредованно тоже должны быть пересчитаны. Такие зависимости отслеживаются в run-time на уровне объектов, а не классов, так что нет необходимости в анализе java кода. Это означает, что различные объекты одного класса имеют независимые последовательности анализируемых вызовов при определении зависимых свойств объектов.

Сохранение свойств объектов (кэширование) в библиотеке имеет важную особенность. И основной особенностью кэширования является его перманентная актуальность. Вы не получите устаревших данных. Другие реализации кэширования свойств объектов могут возвращать устаревшие данные или требуют усилий со стороны программиста, чтобы избежать этого. В библиотеке обновляемых объектов кэш всегда актуален, что не требует никаких вмешательств со стороны разработчика. Это делает использование библиотеки абсолютно прозрачным: вы создаете неоптимизированные геттеры, выполняете в них циклы и другие ресурсоемкие операции, а затем оптимизируете данные геттеры простым добавлением аннотации @Cached. Никаких изменений логики не требуется, данные, возвращаемые кэшированными функциями всегда актуальны. Такого пока нет в других библиотеках, например: http://www.tek271.com/free/memoizer/tek271.memoizer.intro.html, http://dev2dev.bea.com/pub/a/2006/05/declarative-caching.html.

 

Заключение

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

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

Более детальное описание механизма обновляемых объектов приведено здесь http://fusionsoft-online.com/refreshableobject.php. Мы будем рады услышать Ваши отзывы и мнения, отправляйте их по адресу: info@fusionsoft-online.com.