Статьи

Гобелен 5 IoC: переплет и строительные услуги

Гобелен 5 включает в себя собственный внутренний контейнер Inversion of Control. Это часто вызывает споры … почему бы просто не использовать
Spring или (в более поздних разговорах)
Guice ?

Это сложный вопрос; Проще говоря, у Tapestry есть требования в качестве основы, для которой другие контейнеры не предлагают решений.

Эта публикация представляет собой простое введение в основы Tapestry 5 IoC. В последующих публикациях мы более подробно расскажем о расширенных функциях контейнера IoC Tapestry, которые действительно удаляют его от Spring и Guice.

Гобелен использует термин «сервис» для основных объектов, которыми он управляет для вас. Весна использует термин «боб». Служба обычно является интерфейсом и классом, который реализует интерфейс. В наиболее типичном случае только один сервис реализует интерфейс, но T5 IoC полностью способен обрабатывать случай, когда один сервисный интерфейс имеет несколько различных сервисов; даже случай, когда один класс создается с другой конфигурацией.

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

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

T5 IoC использует классы модулей, чтобы определить, какие услуги доступны. Класс модуля — это класс POJO со специальным методом, названным методом bind(). Приложение Tapestry будет состоять из нескольких модулей: некоторые модули предоставляются самим Tapestry, некоторые — сторонними библиотеками или расширениями, а некоторые — самим приложением. Гобелен смешивает и сопоставляет всю эту информацию, все сервисы, определенные каждым из модулей, в единый реестр сервисов.

Это может звучать сложнее, чем на самом деле. Реальность такова, что в bind()методе мы просто сопоставляем интерфейсы служб с соответствующими реализациями:

public final class TapestryModule
{
public static void bind(ServiceBinder binder)
{
binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
binder.bind(PersistentLocale.class, PersistentLocaleImpl.class);
binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class);
// ... and so on
}
}

ServiceBinder использует универсальные средства, чтобы гарантировать, что указанный класс реализует интерфейс службы. API — это свободный интерфейс: вы можете связать несколько дополнительных вызовов методов с bind () для переопределения значений по умолчанию, например:

      binder.bind(ObjectProvider.class, AssetObjectProvider.class).withId("AssetObjectProvider");

TapestryModule на самом деле определяет довольно много дополнительных сервисов.

Давайте посмотрим на пример:

public interface PersistentLocale
{
void set(Locale locale);

Locale get();

boolean isSet();
}

Я убрал комментарии, чтобы сэкономить место … но этот сервис управляет локалью пользователя; это ключевая часть поддержки локализации в Tapestry 5. Реализация, которую мы вскоре увидим, работает с использованием куки-файлов HTTP, но это не важно для кода, использующего PersistentLocale.

public class PersistentLocaleImpl implements PersistentLocale
{
private static final String LOCALE_COOKIE_NAME = "org.apache.tapestry5.locale";

private final Cookies cookieSource;

public PersistentLocaleImpl(Cookies cookieSource)
{
this.cookieSource = cookieSource;
}

public void set(Locale locale)
{
cookieSource.writeCookieValue(LOCALE_COOKIE_NAME, locale.toString());
}

public Locale get()
{
String localeCookieValue = getCookieValue();

return localeCookieValue != null ? LocaleUtils.toLocale(localeCookieValue) : null;
}

private String getCookieValue()
{
return cookieSource.readCookieValue(LOCALE_COOKIE_NAME);
}

public boolean isSet()
{
return getCookieValue() != null;
}
}

T5 IoC делает все инъекции через конструктор. Это сделано для того, чтобы побудить вас записать свои зависимости в конечные поля, что является потокобезопасным. Как правило, ваши услуги будут неизменными объектами: все поля являются окончательными.

PersistentLocaleImpl зависит от другого сервиса, Cookies. А что такое Cookies? Это еще один сервисный интерфейс. Обратите внимание, что нам не нужно делать никаких дополнительных настроек здесь … так как есть один и только один сервис, который реализует интерфейс Cookies, это вся информация, необходимая Tapestry для объединения вещей.

Другие реализации сервисов внутри Tapestry имеют всего лишь нулевые зависимости и целых восемь. Нет теоретического предела, просто наличие нескольких зависимостей — это запах дизайна … что вы можете разбить вещи на более мелкие части.

Одним из отличительных признаков кодирования с использованием контейнера IoC является этот уровень краткости , также известный как преодоление блага . Учитывая, что PersistentLocaleImpl касается файлов cookie HTTP, можно подумать, что он каким-то образом завладеет объектом HttpServletRequest и начнет вызывать его getCookies()и использовать addCookie()для него … но вместо этого все детали взаимодействия с Servlet API и довольно неудобным API для HTTP-куки скрыт в реализации сервиса Cookies.

Это здорово … это делает реализацию PersistentLocaleImpl (как и любой другой код, который заботится о HTTP-куки) намного проще и проще для тестирования.

Сервисный цикл

Услуги гобеленов имеют определенный жизненный цикл:

  • определенный

Идентифицирован через ServiceBinder, но еще не указан
  • виртуальный

Прокси-сервер существует, который был введен как зависимость какой-либо другой службы, но методы прокси не были вызваны
  • понял

Служба была создана с зависимостями

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

Опять же, привлекательность контейнера IoC заключается в том, что вы можете разбить ваше приложение на крошечные, легко проверяемые фрагменты, а контейнер IoC отвечает за соединение всего вместе во время выполнения. Это действительно приводит к новому способу кодирования и размышлениям о кодировании.

Методы построения сервисов

Иногда просто создание экземпляра класса недостаточно; может потребоваться дополнительная конфигурация как часть создания экземпляра класса. Tapestry 5 Предшественник IoC, HiveMind , достиг таких целей с помощью комплексных сервисных сервисов. В итоге получилось много XML.

T5 IoC выполняет то же, и более, используя методы построения сервисов ; методы модуля, которые создают сервис. Типичный случай, когда реализация службы должна прослушивать события от какой-либо другой службы:

public static TranslatorSource buildTranslatorSource(ComponentInstantiatorSource componentInstantiatorSource, 
ServiceResources resources)
{
TranslatorSourceImpl service = resources.autobuild(TranslatorSourceImpl.class);

componentInstantiatorSource.addInvalidationListener(service);

return service;
}

Методы модуля с префиксом «build» являются методами построения сервисов. Интерфейс службы определяется из возвращаемого значения (TranslatorSource). Идентификатор сервиса — это явно «TranslatorSource» (то есть все после «build» в имени метода).

Здесь Tapestry внедряется в метод построения сервисов . ComponentInstantiatorSource — это служба, которая запускает события. ServiceResources — это нечто иное: это набор ресурсов, связанных с создаваемой службой … включая возможность создания объекта, включая зависимости . Здесь замечательно то, buildTranslatorSource()что не нужно знать, каковы зависимости TranslatorSourceImpl, он может создать экземпляр класса с зависимостями, используя autobuild()метод. Затем построитель службы добавляет новую службу в качестве прослушивателя ComponentInstantiatorSource, а затем возвращает его.

Это большое разделение задач : у нас есть конструктивная проблема (являющаяся слушателем событий), которая отличается от эксплуатационных проблем TranslatorSource. И они хранятся отдельно.

Вывод

Tapestry IoC имеет простой и лаконичный API для определения сервисов и, в большинстве случаев, автоматически обрабатывает зависимости. Конечным результатом является то, что становится детской игрой «разделяй и властвуй»: конвертируйте старый, монолитный, трудно поддерживаемый код в небольшие, легко тестируемые, легко понимаемые сервисы.

В будущих публикациях я более подробно расскажу о более продвинутых функциях Tapestry: сферах обслуживания, настройках и оформлении сервисов.

С http://tapestryjava.blogspot.com/