org.reflections包下ClasspathHelper.forPackage方法的使用误区

来源:互联网 发布:淘宝网农村怎么加盟 编辑:程序博客网 时间:2024/06/11 10:43

今天在项目需求中遇到要扫描某一特定路径下class的问题,在代码看到如下使用:

private void scanAnnotations() {        Reflections packageReflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(pkg))                .setScanners(new FieldAnnotationsScanner()));          //1.对静态字段的注解初始化        scanStaticStringAnnotation(packageReflections);        //2.对枚举类型字段的注解初始化        scanEnumAnnotation(packageReflections);    }
其中ClasspathHelper.forPackage方法的源码如下:
 /**     * Returns a distinct collection of URLs based on a package name.     * <p>     * This searches for the package name as a resource, using {@link ClassLoader#getResources(String)}.     * For example, {@code forPackage(org.reflections)} effectively returns URLs from the     * classpath containing packages starting with {@code org.reflections}.     * <p>     * If the optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()}     * and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}.     * <p>     * The returned URLs retainsthe order of the given {@code classLoaders}.     *      * @return the collection of URLs, not null     */    public static Collection<URL> forPackage(String name, ClassLoader... classLoaders) {        return forResource(resourceName(name), classLoaders);    }
注意标红的句子,是不是很容易让人理解成返回的结果就是你要传入的路径?其实它的返回结果是你所传入路径的上一级路径,也就是包含你所传入路径的那个路径。继续点进去可以看到:
public static Collection<URL> forResource(String resourceName, ClassLoader... classLoaders) {        final List<URL> result = new ArrayList<URL>();        final ClassLoader[] loaders = classLoaders(classLoaders);        for (ClassLoader classLoader : loaders) {            try {                final Enumeration<URL> urls = classLoader.getResources(resourceName);                while (urls.hasMoreElements()) {                    final URL url = urls.nextElement();                    int index = url.toExternalForm().lastIndexOf(resourceName);                    if (index != -1) {                        result.add(new URL(url.toExternalForm().substring(0, index)));                    } else {                        result.add(url); //whatever                    }                }            } catch (IOException e) {                if (Reflections.log != null) {                    Reflections.log.error("error getting resources for " + resourceName, e);                }            }        }        return distinctUrls(result);    }
这里就是真正处理并得到路径的地方,关键就在于这个substring的用处,是不是很坑..

这是Github上有人遇到的同样问题:

Collection<URL> urls = ClasspathHelper.forPackage("foo.reflections.v1"); System.out.println("URLS="+urls); config.addUrls(urls);Output is:URLS=[file:/tmp/Code/sandbox/build/classes/main/]which is the root directory and when scan by Reflections is done, everything is scanned from root.The issue seems to be with ClasspatHelper:107result.add(new URL(url, url.toExternalForm().substring(0, index)));The workaround is to do:Reflections reflections = new Reflections("foo.reflections.v1")and bypass ClasspathHelper.forPackage call, which the documentation states as the correct way to do it.This also affects using ConfigurationBuilder.forPackages, as any valid package name is ignored and scanning starts at root.At least one other 3rd party library is affected by this, Swagger which winds up scanning everything on startup and significantly 
impacts the startup time of api servers that use it. At the moment I have not found a way to force Swagger to use the other 
Reflections constructor nor to access the internal ConfigurationBuilder.The ultimate issue is that ClasspathHelper.forPackage should be returning the URL for that package and not root.