Существует множество инфраструктур внедрения зависимостей / 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 Lookup
JDK и 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/