Библиотека отражений не работает при использовании в плагине Eclipse
Я разработал приложение, использующее библиотеку Reflections, для запросов ко всем классам с определенной аннотацией. Все работало как шарм, пока я не решил создать плагин Eclipse из моего приложения. Тогда размышления перестают работать.
Учитывая, что мое приложение работает нормально, когда не входит в плагин Eclipse, я думаю, что это должно быть проблемой загрузчика классов. Поэтому я добавил в свой Reflections
класс загрузчиков классов класса активатора модуля, загрузчика классов контекста и всех других загрузчиков классов, которые я мог себе представить, без какого-либо успеха. Это упрощенная версия моего кода:
ConfigurationBuilder config = new ConfigurationBuilder();
config.addClassLoaders(thePluginActivatorClassLoader);
config.addClassLoaders(ClasspathHelper.getContextClassLoader());
config.addClassLoaders("all the classloaders I could imagine");
config.filterInputsBy(new FilterBuilder().include("package I want to analyze"));
Reflections reflections = new Reflections(config);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(MyAnnotation.class); //this Set is empty
Я также попытался добавить URL-адреса классов, которые я хочу загрузить в ConfigurationBuilder
класс, но это не помогло.
Может ли кто-нибудь сказать мне, если есть способ сделать Reflections
работать как часть плагина Eclipse?, или мне лучше поискать другую альтернативу? Большое спасибо, я действительно озадачен этим.
3 ответа
Я предполагаю, что вы уже знаете, как создавать пакеты (в противном случае, проверьте это).
После некоторой отладки и исследования API Reflections я понял, что проблема в том, что Reflections просто не может прочитать OSGi URL (bundleresource://...), что приводит к исключению:
org.reflections.ReflectionsException: could not create Vfs.Dir from url,
no matching UrlType was found [bundleresource://1009.fwk651584550/]
и это предложение:
either use fromURL(final URL url, final List<UrlType> urlTypes)
or use the static setDefaultURLTypes(final List<UrlType> urlTypes)
or addDefaultURLTypes(UrlType urlType) with your specialized UrlType.
Поэтому я считаю, что реализация UrlType для OSGi (например, class BundleUrlType implements UrlType {...}
) и регистрируем это так:
Vfs.addDefaultURLTypes(new BundleUrlType());
должен сделать API Reflections пригодным для использования внутри пакета. Отражения зависимостей должны быть добавлены в проект Eclipse Plugin, как описано здесь.
Вот как выглядел мой пример MANIFEST.MF после добавления необходимых jar-файлов:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: ReflectivePlugin
Bundle-SymbolicName: ReflectivePlugin
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: reflectiveplugin.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: javax.annotation;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.service.log;version="1.3",
org.osgi.util.tracker;version="1.3.1"
Bundle-ClassPath: .,
lib/dom4j-1.6.1.jar,
lib/guava-r08.jar,
lib/javassist-3.12.1.GA.jar,
lib/reflections-0.9.5.jar,
lib/slf4j-api-1.6.1.jar,
lib/xml-apis-1.0.b2.jar
Export-Package: reflectiveplugin,
reflectiveplugin.data
Примечание: Используемые отражения v. 0.9.5
Вот пример реализации UrlType:
package reflectiveplugin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import org.osgi.framework.Bundle;
import org.reflections.vfs.Vfs;
import org.reflections.vfs.Vfs.Dir;
import org.reflections.vfs.Vfs.File;
import org.reflections.vfs.Vfs.UrlType;
import com.google.common.collect.AbstractIterator;
public class BundleUrlType implements UrlType {
public static final String BUNDLE_PROTOCOL = "bundleresource";
private final Bundle bundle;
public BundleUrlType(Bundle bundle) {
this.bundle = bundle;
}
@Override
public boolean matches(URL url) {
return BUNDLE_PROTOCOL.equals(url.getProtocol());
}
@Override
public Dir createDir(URL url) {
return new BundleDir(bundle, url);
}
public class BundleDir implements Dir {
private String path;
private final Bundle bundle;
public BundleDir(Bundle bundle, URL url) {
this(bundle, url.getPath());
}
public BundleDir(Bundle bundle, String p) {
this.bundle = bundle;
this.path = p;
if (path.startsWith(BUNDLE_PROTOCOL + ":")) {
path = path.substring((BUNDLE_PROTOCOL + ":").length());
}
}
@Override
public String getPath() {
return path;
}
@Override
public Iterable<File> getFiles() {
return new Iterable<Vfs.File>() {
public Iterator<Vfs.File> iterator() {
return new AbstractIterator<Vfs.File>() {
final Enumeration<URL> entries = bundle.findEntries(path, "*.class", true);
protected Vfs.File computeNext() {
return entries.hasMoreElements() ? new BundleFile(BundleDir.this, entries.nextElement()) : endOfData();
}
};
}
};
}
@Override
public void close() { }
}
public class BundleFile implements File {
private final BundleDir dir;
private final String name;
private final URL url;
public BundleFile(BundleDir dir, URL url) {
this.dir = dir;
this.url = url;
String path = url.getFile();
this.name = path.substring(path.lastIndexOf("/") + 1);
}
@Override
public String getName() {
return name;
}
@Override
public String getRelativePath() {
return getFullPath().substring(dir.getPath().length());
}
@Override
public String getFullPath() {
return url.getFile();
}
@Override
public InputStream openInputStream() throws IOException {
return url.openStream();
}
}
}
И вот как я создаю отражения в классе Activator:
private Reflections createReflections(Bundle bundle) {
Vfs.addDefaultURLTypes(new BundleUrlType(bundle));
Reflections reflections = new Reflections(new Object[] { "reflectiveplugin.data" });
return reflections;
}
Последнее немного сбивает с толку, но все же важно: если вы запускаете свой плагин внутри Eclipse (Run As / OSGi Framework), вы должны также добавить выходной каталог ваших классов в шаблоны пути Reflections (то есть "bin" или "target/classes"). "). Хотя для выпущенного плагина это не нужно (для создания плагина / комплекта выполните "Экспорт"->"Развертываемые плагины и фрагменты").
Просто для записей на случай, если у кого-то есть такая же проблема. Вот небольшая модификация ответа Влада, чтобы избежать необходимости добавлять каталог вывода в шаблоны пути Reflections. Разница только в классе BundleDir. Похоже, что работает хорошо во всех моих тестах:
public class BundleUrlType implements UrlType {
public static final String BUNDLE_PROTOCOL = "bundleresource";
private final Bundle bundle;
public BundleUrlType(Bundle bundle) {
this.bundle = bundle;
}
@Override
public Dir createDir(URL url) {
return new BundleDir(bundle, url);
}
@Override
public boolean matches(URL url) {
return BUNDLE_PROTOCOL.equals(url.getProtocol());
}
public static class BundleDir implements Dir {
private String path;
private final Bundle bundle;
private static String urlPath(Bundle bundle, URL url) {
try {
URL resolvedURL = FileLocator.resolve(url);
String resolvedURLAsfile = resolvedURL.getFile();
URL bundleRootURL = bundle.getEntry("/");
URL resolvedBundleRootURL = FileLocator.resolve(bundleRootURL);
String resolvedBundleRootURLAsfile = resolvedBundleRootURL.getFile();
return("/"+resolvedURLAsfile.substring(resolvedBundleRootURLAsfile.length()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public BundleDir(Bundle bundle, URL url) {
//this(bundle, url.getPath());
this(bundle, urlPath(bundle,url));
}
public BundleDir(Bundle bundle, String p) {
this.bundle = bundle;
this.path = p;
if (path.startsWith(BUNDLE_PROTOCOL + ":")) {
path = path.substring((BUNDLE_PROTOCOL + ":").length());
}
}
@Override
public String getPath() {
return path;
}
@Override
public Iterable<File> getFiles() {
return new Iterable<Vfs.File>() {
public Iterator<Vfs.File> iterator() {
return new AbstractIterator<Vfs.File>() {
final Enumeration<URL> entries = bundle.findEntries(path, "*.class", true);
protected Vfs.File computeNext() {
return entries.hasMoreElements() ? new BundleFile(BundleDir.this, entries.nextElement()) : endOfData();
}
};
}
};
}
@Override
public void close() { }
}
public static class BundleFile implements File {
private final BundleDir dir;
private final String name;
private final URL url;
public BundleFile(BundleDir dir, URL url) {
this.dir = dir;
this.url = url;
String path = url.getFile();
this.name = path.substring(path.lastIndexOf("/") + 1);
}
@Override
public String getName() {
return name;
}
@Override
public String getRelativePath() {
return getFullPath().substring(dir.getPath().length());
}
@Override
public String getFullPath() {
return url.getFile();
}
@Override
public InputStream openInputStream() throws IOException {
return url.openStream();
}
}
}
Eclipse построен на основе OSGi, и вы столкнулись с загрузкой классов OSGi... и победить не так просто.
Взгляните на эту статью Нила Бартлетта: Готовность OSGi - Загрузка классов. Также вы можете погуглить на "OSGi buddy policy".