Лично мне не нравится ServiceLoader
ни при каких обстоятельствах.Он медленный и неоправданно расточительный, и вы мало что можете сделать, чтобы оптимизировать его.
Я также считаю его немного ограниченным - вам действительно нужно уйти с дороги, если вы хотите сделать больше, чем поиск по типув одиночку.
ResourceFinder xbean-finder
- ResourceFinder представляет собой автономный файл Java, способный заменить использование ServiceLoader.Копирование / вставка повторного использования не проблема.Это один файл java, лицензированный ASL 2.0 и доступный от Apache.
Прежде чем наше внимание станет слишком коротким, вот как он может заменить ServiceLoader
ResourceFinder finder = new ResourceFinder("META-INF/services/");
List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);
Это найдетвсе реализации META-INF/services/org.acme.Plugin
в вашем пути к классам.
Обратите внимание, что на самом деле не создаются все экземпляры.Выберите те, которые вам нужны, и вы на один newInstance()
вызов откажетесь от наличия экземпляра.
Почему это мило?
- Насколько сложно позвонить
newInstance()
с правильной обработкой исключений?Не сложно. - Приятно иметь возможность создавать только те экземпляры, которые вам нужны.
- Теперь вы можете поддерживать аргументы конструктора!
Сужение области поиска
Если вы хотите просто проверить определенные URL-адреса, вы можете сделать это легко:
URL url = new File("some.jar").toURI().toURL();
ResourceFinder finder = new ResourceFinder("META-INF/services/", url);
Здесь при любом использовании этого экземпляра ResourceFinder будет выполняться поиск только файла «some.jar».
Существует также удобный класс, называемый UrlSet
, который позволяет очень легко выбирать URL-адреса из пути к классам.
ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader();
UrlSet urlSet = new UrlSet(webAppClassLoader);
urlSet = urlSet.exclude(webAppClassLoader.getParent());
urlSet = urlSet.matching(".*acme-.*.jar");
List<URL> urls = urlSet.getUrls();
Альтернативные стили "service"
Скажем, вы хотите применить ServiceLoader
введите концепцию, чтобы изменить дизайн обработки URL и найти / загрузить java.net.URLStreamHandler
для определенного протокола.
Вот как вы можете расположить сервисы в вашем classpath:
META-INF/java.net.URLStreamHandler/foo
META-INF/java.net.URLStreamHandler/bar
META-INF/java.net.URLStreamHandler/baz
Где foo
- это простой текстовый файл, который содержит имя реализации службы, как и прежде.Теперь скажите, что кто-то создает foo://...
URL.Мы можем быстро найти реализацию для этого с помощью:
ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");
Альтернативных стилей "обслуживания" 2
Допустим, вы хотите поместить некоторую информацию о конфигурации в файл службы, чтобы она содержала болеепросто имя класса.Вот альтернативный стиль, который разрешает службы для файлов свойств.По соглашению один ключ будет именами классов, а другие ключи - свойствами, которые можно будет ввести.
Итак, red
- это файл свойств
META-INF/org.acme.Plugin/red
META-INF/org.acme.Plugin/blue
META-INF/org.acme.Plugin/green
Вы можете искать вещи так же, как и раньше.
ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName());
Properties redDefinition = plugins.get("red");
Вот как вы можете использовать эти свойства с xbean-reflect
, еще одна маленькая библиотека, которая может дать вам IoC без фреймворка.Вы просто даете ему имя класса и несколько пар «имя-значение», и он будет создавать и вставлять.
ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString());
recipe.setAllProperties(redDefinition);
Plugin red = (Plugin) recipe.create();
red.start();
Вот как это может выглядеть «прописано» в длинной форме:
ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin");
recipe.setProperty("myDateField","2011-08-29");
recipe.setProperty("myIntField","100");
recipe.setProperty("myBooleanField","true");
recipe.setProperty("myUrlField","http://www.stackoverflow.com");
Plugin red = (Plugin) recipe.create();
red.start();
Библиотека xbean-reflect
- это шаг за пределы встроенного API JavaBeans, но немного лучше, не требуя от вас полного перехода к полнофункциональной среде IoC, такой как Guice или Spring.Он поддерживает фабричные методы и аргументы конструктора и внедрение сеттера / поля.
Почему ServiceLoader так ограничен?
Устаревший код в JVM повреждает сам язык Java.Многие вещи обрезаются до костей перед добавлением в JVM, потому что вы не можете обрезать их после.ServiceLoader
является ярким примером этого.API ограничен, и реализация OpenJDK занимает где-то около 500 строк, включая javadoc.
Там нет ничего особенного, и заменить его легко.Если это не работает для вас, не используйте его.
Область пути к классам
* * * * *
API, в чистом виде сужение области поиска URL-адресов является истинным решением этой проблемы.Серверы приложений имеют довольно много URL-адресов сами по себе, не считая фляги в вашем приложении.Например, Tomcat 7 в OSX имеет около 40 ~ URL-адресов только в StandardClassLoader (это родитель для всех загрузчиков классов веб-приложений).
Чем больше ваш сервер приложений, тем дольше даже простой поиск.
Кэширование не поможет, если вы собираетесь искать более одной записи.Кроме того, это может добавить некоторые плохие утечки.Может быть реальным проигрышным сценарием.
Сузьте URL-адреса до 5 или 12, которые вас действительно волнуют, и вы можете выполнять все виды загрузки услуг и никогда не замечать попадания.