Статьи

Простое внедрение зависимостей с помощью ServiceLoader в JDK 6


Существует множество инфраструктур внедрения зависимостей / IOC.
Возможно, вы не знаете, что в JDK встроен очень простой, но полезный. И это безопасно для типов.

JDK 6 вводит ServiceLoader. ServiceLoader загружает вещи на основе плоских файлов в каталоге META-INF/servicesиз пути к классам Java. Механизм использования META-INF/servicesсуществует с JDK 1.3 под названием Java Extension Mechanism. ServiceLoaderделает его действительно простым в использовании.

Для тех, кто знаком с NetBeans Lookup API , ServiceLoaderэто как сильно урезанная версия этого (особенно поиск по умолчанию ). Но что делает ServiceLoader, он делает адекватно. Смотрите ниже детали для различий.

Использовать ServiceLoaderэто просто — как и шаблон возможностей , он основан на запросе типа с Classобъектом и получении нуля или более экземпляров (которые могут быть подклассами) этого типа.

Вот игрушечный пример использования ServiceLoaderдля инъекций:

package serviceloaderdemo;
import java.util.ServiceLoader;
public abstract class HelloProvider {

public static HelloProvider getDefault() {
ServiceLoader<HelloProvider> ldr = ServiceLoader.load(HelloProvider.class);
for (HelloProvider provider : ldr) {
//We are only expecting one
return provider;
}
throw new Error ("No HelloProvider registered");
}

public abstract String getMessage();

public static void main(String[] ignored) {
HelloProvider provider = HelloProvider.getDefault();
System.out.println(provider.getMessage());
}
}

package serviceloaderdemo;
public class HelloImpl extends HelloProvider {
@Override
public String getMessage() {
return "Hello World";
}
}

Чтобы зарегистрировать HelloImpl как внедренную реализацию
HelloProvider, мы просто создаем папку с именем
META-INF/servicesв нашем исходном каталоге, поэтому она заканчивается в нашем JAR-файле. В этом каталоге мы создаем текстовый файл с именем
serviceloaderdemo.HelloProvider. Этот файл содержит одну строку текста — название фактической реализации
HelloProvider:

serviceloaderdemo.HelloImpl

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

Давайте посмотрим на более реальный пример. Несколько блогов назад я показал очищенное вверх конструкцию для что — то вроде SwingWorker. Он имеет интерфейс, который вызывается TaskStatusс помощью набора сеттеров, которые фоновая задача может использовать для обновления информации о состоянии, предположительно в некотором пользовательском интерфейсе. Было бы неплохо, если бы кто-то мог предоставить пользовательский интерфейс для отображения хода выполнения фоновых задач, просто поместив JAR в путь к классам, выбрав компонент и добавив его в свое окно. Так что это идеальный случай для внедрения зависимостей — кто-то должен иметь возможность написать такую ​​реализацию пользовательского интерфейса, а затем просто поместить ее в путь к классам, и приложение может взять его и использовать без изменения строки кода, и это может быть многократно используется во многих приложениях без добавления кода в эти приложения.

Нам понадобится фабрика для TaskStatusобъектов, потому что одновременно может выполняться несколько фоновых процессов, а пользовательский интерфейс должен отображать все запущенные фоновые задачи. Итак, мы создадим два класса, используя шаблон зеркального класса , который обсуждался в моем предыдущем блоге — мы разделим API и SPI. Пользовательский интерфейс получит ProgressMonitorFactoryи получит компонент пользовательского интерфейса для добавления в свою строку состояния путем вызова getCapability(Component.class). Все, ProgressMonitorFactoryчто нужно сделать, — это проверить правильность аргументов и состояние потока, а затем делегировать реализацию, которая ищется с помощью ServiceLoader.

Но здесь есть дополнительная складка: нам нужно по крайней мере два варианта пользовательского интерфейса состояния: один, который отображается только в строке состояния, и другой, блокирующий пользовательский интерфейс с помощью модального диалога.

Ничего страшного: ServiceLoaderможем предоставить нам более одной реализации — мы просто создаем несколько реализаций и добавляем больше строк в этот файл META-INF/services.

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

@Retention (value=RUNTIME)
public @interface MonitorKind {
public static final String DEFAULT = "default";
public static final String BLOCKING = "blocking";
String kind();
}

Затем мы определяем класс SPI, который создает экземпляры ProgressImpl, которые могут перемещать индикатор выполнения в пользовательском интерфейсе:

public abstract class ProgressImplFactory {
public abstract ProgressImpl create (Task task);

public <T> T getCapability (Class<T> type) {
return null;
}
}

Последнее, что нам нужно, это
finalзеркальный API-класс для нашего класса SPI. Важный метод поиска с помощью аннотаций
getInstance(String):

public final class ProgressMonitorFactory {
private static final Set<String> notFoundKinds = new HashSet<String>();
private final ProgressImplFactory delegate;

private ProgressMonitorFactory(ProgressImplFactory p) {
this.delegate = p;
}

public ProgressMonitor create (Task task) {
return new ProgressMonitor (delegate.create(task));
}

public static ProgressMonitorFactory getInstance(String kind) {
if (kind == null) {
throw new NullPointerException ("Kind null");
}
for (ProgressImplFactory p : ServiceLoader.load(ProgressImplFactory.class)) {
//Would be nicer if ServiceLoader let you get the type
//without creating an instance, ala NetBeans Lookup
Class type = p.getClass();
MonitorKind mk = (MonitorKind) type.getAnnotation(MonitorKind.class);
if (mk != null && kind.equals(mk.kind())) {
return new ProgressMonitorFactory(p);
}
}
if (!notFoundKinds.contains(kind)) {
notFoundKinds.add (kind);
Logger.getLogger(ProgressMonitor.class.getName()).log (Level.FINE,
"Requested ProgressMonitor kind '" + kind + "', but it is" +
" not registered on the classpath.",
new IllegalArgumentException());
}
return null;
}

//implementation omitted...
}

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

ProgressMonitorFactory factory = ProgressMonitorFactory.getDefault();
Component statusComp = factory.getCapability (Component.class);
if (statusComp != null) {
statusBar.add (statusComp);
}

Затем все, что нужно сделать автору приложения, это реализовать Task и отправить его для запуска в фоновом режиме. Пользовательский интерфейс будет работать правильно и прозрачно.

Суть в том, что JDK имеет полезный встроенный механизм внедрения зависимостей , и что вы можете создавать API и их реализации, которые удобно использовать и легко обновлять с помощью него. Хотя он не настолько богат, как некоторые из существующих структур внедрения зависимостей, он имеет преимущество в том, что он безопасен для типов и более чем достаточен для многих целей.

Почему вы можете использовать поиск NetBeans вместо ServiceLoader

Различия между NetBeans LookupJDK и JDK ServiceLoaderследующие:

  • Вы не можете создать экземпляр ServiceLoader самостоятельно; Вы можете использовать Lookup для связи между объектами, а не только для загрузки служб из пути к классам, и это очень полезно для этого
  • Вы можете прослушивать изменения в Lookup — действительно, подписаться на изменения наличия / отсутствия / идентичности определенного типа в этом Lookup. Это означает, что если путь к классам изменяется динамически, вы действительно сможете выгружать и перезагружать объекты. Это позволяет перезагрузить JAR без перезагрузки приложения.
  • Вы можете выяснить, содержит ли Lookup экземпляр экземпляра типа, фактически не создавая экземпляр объекта этого типа. С помощью ServiceLoaderвы можете только запросить, фактически создав объекты.
  • Вы можете использовать Lookups.forPath(String)для регистрации экземпляров в других подкаталогах META-INF. Это делает возможными некоторые интересные вещи, такие как использование информации о пути в качестве метаданных. Например, если вы хотите зарегистрировать разные реализации одного и того же типа для разных типов MIME, вы можете просто вызвать
                Lookup lkp = Lookups.forPath ("text/plain");
    SomeObject thingy = lkp.lookup (SomeObject.class);
  • Вы можете составить несколько поисков вместе с ними ProxyLookupи даже изменить набор поисков, передаваемых по прокси на лету, и получать соответствующие события.

Если вы хотите использовать Lookup и у вас есть копия NetBeans, у вас уже есть копия Lookup — смотрите $NB_HOME/platform9/modules/org-openide-util.jar. Javadoc можно найти здесь . Поиск не привязан ни к другим API-интерфейсам NetBeans, ни к приложениям с графическим интерфейсом. Вы можете использовать его в любом месте, где хотите внедрить этот вид зависимости.

С http://weblogs.java.net/blog/timboudreau/