在 Eclipse 插件中使用时 Reflections 库不工作

发布于 2024-12-19 05:50:54 字数 990 浏览 3 评论 0原文

我使用 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 插件的一部分工作?或者我应该更好地寻找另一种替代方案?非常感谢,我真的很困惑。

I have developed an application using the Reflections library for querying all the classes having a particular annotation. Everything was working like a charm until I decided to create an Eclipse plug-in from my application. Then Reflections stop working.

Given that my application is working fine when not part of an Eclipse plug-in, I think it should be a class-loader problem.
So I added to my Reflections class the classloaders of the plug-in activator class, the context class loader, and all other class loaders I could imagine, without any success. This is a simplified version of my code:

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

I also tried adding URLs of the classes I want to load to the ConfigurationBuilder class, but it did not help.

Could someone tell me if there is a way to make Reflections work as part of an Eclipse plug-in ?, or should I better look for another alternative ?. Thanks a lot, I am really puzzled about it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

病毒体 2024-12-26 05:50:54

我假设您已经知道如何创建捆绑包(否则,请检查这个)。

经过对 Reflections API 的一些调试和探索后,我意识到问题在于 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.

所以我相信为 OSGi 实现一个 UrlType (例如 class BundleUrlType Implements UrlType {...}) 并像这样注册它:

Vfs.addDefaultURLTypes(new BundleUrlType());

应该使 Reflections API 可从包内部使用。反射依赖项应添加到 Eclipse 插件项目中,如下所述 此处

这是我的示例 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

注意:使用了 Reflections 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 中运行插件(运行为/OSGi 框架),您还必须将类输出目录添加到反射路径模式(即“bin”或“target/classes”)。尽管如此,发布的插件不需要它(要构建插件/捆绑包,请执行“导出”->“可部署插件和片段”)。

I assume you already know how to create bundles (otherwise, check this).

After some debuging and exploration of Reflections API I have realised that the problem is that Reflections simply fails to read OSGi URLs (bundleresource://...) resulting in an exception:

org.reflections.ReflectionsException: could not create Vfs.Dir from url, 
no matching UrlType was found [bundleresource://1009.fwk651584550/]

and this suggestion:

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.

So I believe implementing a UrlType for OSGi (e.g. class BundleUrlType implements UrlType {...}) and registering it like this:

Vfs.addDefaultURLTypes(new BundleUrlType());

should make Reflections API usable from inside a bundle. Reflections dependencies should be added to the Eclipse Plugin project as described here.

This is how my sample MANIFEST.MF looked like after adding needed jars:

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

Note: Used Reflections v. 0.9.5

Here's a sample UrlType implementation:

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();
        }
    }
}

And this is how I create reflections in the Activator class:

private Reflections createReflections(Bundle bundle) {
    Vfs.addDefaultURLTypes(new BundleUrlType(bundle));
    Reflections reflections = new Reflections(new Object[] { "reflectiveplugin.data" });
    return reflections;
}

The last bit is very confusing, but still important: if you run your plugin inside of Eclipse (Run As / OSGi Framework) you have to add also your classes output directory to the Reflections path patterns (i.e. "bin" or "target/classes"). Although, it's not needed for a released plugin (to build a plugin/bundle do "Export"->"Deployable plug-ins and fragments").

七秒鱼° 2024-12-26 05:50:54

仅供记录,以防其他人遇到同样的问题。
这里对 Vlad 的答案进行了一个小修改,以避免必须将输出目录添加到 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();
    }
}
}

Just for the records in case someone else has the same problem.
Here a small modification to the answer of Vlad in order to avoid having to add the output directory to the Reflections path patterns.
The difference is only in the BundleDir class. It seems to work fine in all my tests:

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();
    }
}
}
牵强ㄟ 2024-12-26 05:50:54

Eclipse 是构建在 OSGi 之上的,而您面临着 OSGi 类加载……这不是一场容易获胜的战斗。

看看 Neil Bartlett 的这篇文章: OSGi 准备 — 加载类。您也可以通过 google 搜索“OSGi 好友策略”。

Eclipse is build on top of OSGi and you are up against OSGi class loading... and that is not an easy battle to win.

Have a look at this article of Neil Bartlett: OSGi Readiness — Loading Classes. Also you can google for "OSGi buddy policy".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文