`
yangdong
  • 浏览: 66603 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java 类路径扫描

    博客分类:
  • Java
 
阅读更多
Java 中缺乏内建的类路径扫描机制。但是这个机制对于做框架的人来说很常用。下面的工具类从类路径下面的文件或者 JAR 路径中扫描包,可以实现我们需要的功能。实现是基于 Spring 3.1.11.RELEASE 版的 PathMatchingResourcePatternResolver 修改的。去掉了原版中 ANT * 号匹配的功能。依赖 Google Guava, Apache Commons Collections 4, Apache Commons  Lang。

这个实现的大概思路是

1. 先根据包名构建一个路径("." 换成 "/")
2. 用类加载器去加载这个路径对应的资源,得到一个 URL
3. 如果 URL 对应的是 JAR,扫描 JAR(非递归扫描)
4. 如果 URL 是文件系统,扫描文件系统(递归扫描)

3. 4., 一个递归一个非递归,主要原因是 JAR 对应的 API 返回的文件列表是展开的,所以我们不需要递归展开了。

比如有意思的是 2. 这一步。如果路径是空字符串 "",那么

1. 在 maven 下或者 IDE 下直接运行。得到的 URL 在文件系统下是 classes、test-classes 这些
2. 用 java -jar xxx.jar 运行。得到的 URL 是空的。但如果不传空字符串,传 com.xxx.app 就可以加载到资源

所以这个工具类的使用需要注意一点。如果你想让这个工具类在打成 JAR 包之后还能正常用,那么参数 basePackages 这里至少添上一个 com.xxx.app 之类的东西。

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.collections4.EnumerationUtils;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 反射工具。实现是基于 Spring 3.1.11.RELEASE 版的 PathMatchingResourcePatternResolver 修改的。
 *
 * @author fanshen
 * @since 2015/7/7
 */
public class ReflectionUtils {
    private ReflectionUtils() {
    }

    /**
     * URL protocol for an entry from a jar file: "jar"
     */
    private static final String URL_PROTOCOL_JAR = "jar";

    /**
     * URL protocol for an entry from a zip file: "zip"
     */
    private static final String URL_PROTOCOL_ZIP = "zip";

    /**
     * URL protocol for an entry from a WebSphere jar file: "wsjar"
     */
    private static final String URL_PROTOCOL_WSJAR = "wsjar";

    /**
     * URL protocol for an entry from a JBoss jar file: "vfszip"
     */
    private static final String URL_PROTOCOL_VFSZIP = "vfszip";

    /**
     * URL protocol for an entry from an OC4J jar file: "code-source"
     */
    private static final String URL_PROTOCOL_CODE_SOURCE = "code-source";

    /**
     * Separator between JAR URL and file path within the JAR
     */
    private static final String JAR_URL_SEPARATOR = "!/";

    /**
     * URL prefix for loading from the file system: "file:"
     */
    private static final String FILE_URL_PREFIX = "file:";

    /**
     * 文件夹隔离符。
     */
    private static final String FOLDER_SEPARATOR = "/";

    /**
     * 递归地在类路径中以指定的类加载器获取 dirPath 指定的目录下面所有的资源。cl 可为空,为空时取系统类加载器。
     * 返回值一定不为 null。
     */
    public static ClassPathResource[] getClassPathResources(String dirPath, ClassLoader cl) throws IOException {
        if (cl == null) {
            cl = Thread.currentThread().getContextClassLoader();
        }
        URL[] roots = getRoots(dirPath, cl);
        Set<ClassPathResource> result = new LinkedHashSet<ClassPathResource>(16);
        for (URL root : roots) {
            if (isJarResource(root)) {
                result.addAll(doFindPathMatchingJarResources(root));
            } else {
                result.addAll(doFindPathMatchingFileResources(root, dirPath));
            }
        }
        return result.toArray(new ClassPathResource[result.size()]);
    }

    /**
     * 获取指定 basePackages 下面所有能用 classLoaders 加载的类。
     *
     * @param basePackages 这些包下面的类会被扫描
     * @param classLoaders 用来加载 basePackages 下面类的类加载器
     * @return 能够扫描到的类
     */
    public static Class<?>[] getAllClassPathClasses(String[] basePackages, ClassLoader... classLoaders)
        throws IOException {
        if (classLoaders.length <= 0) {
            classLoaders = new ClassLoader[] {Thread.currentThread().getContextClassLoader()};
        }
        List<Class<?>> classes = Lists.newArrayListWithCapacity(100);
        for (ClassLoader classLoader : classLoaders) {
            for (String basePackage : basePackages) {
                classes.addAll(Lists.newArrayList(ReflectionUtils.getClasses(basePackage, classLoader)));
            }
        }
        return classes.toArray(new Class[classes.size()]);
    }

    /**
     * 使用 cl 指定的类加载器递归加载 packageName 指定的包名下面的所有的类。不会返回 null。
     * cl 为空时使用系统类加载器。返回值一定不为 null。返回值中不包含类路径中的内部类。
     */
    public static Class<?>[] getClasses(String packageName, ClassLoader cl) throws IOException {
        if (cl == null) {
            cl = Thread.currentThread().getContextClassLoader();
        }
        ClassPathResource[] resources = getClassPathResources(StringUtils.replace(packageName, ".", "/"), cl);
        List<Class<?>> result = Lists.newArrayList();
        for (ClassPathResource resource : resources) {
            String urlPath = resource.getUrl().getPath();
            if (!urlPath.endsWith(".class") || urlPath.contains("$")) {
                continue;
            }
            Class<?> cls = resolveClass(cl, resource);
            if (cls != null) {
                result.add(cls);
            }
        }
        return result.toArray(new Class[result.size()]);
    }

    private static Class<?> resolveClass(ClassLoader cl, ClassPathResource resource) {
        String className = resolveClassName(resource);
        try {
            return cl.loadClass(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private static String resolveClassName(ClassPathResource resource) {
        String path = resource.getClassPathPath();
        String className = path.substring(0, path.length() - ".class".length());
        className = StringUtils.replace(className, "/", ".");
        return className;
    }

    private static URL[] getRoots(String dirPath, ClassLoader cl) throws IOException {
        Enumeration<URL> resources = cl.getResources(dirPath);
        List<URL> resourceUrls = EnumerationUtils.toList(resources);
        return resourceUrls.toArray(new URL[resourceUrls.size()]);
    }

    private static Collection<ClassPathResource> doFindPathMatchingFileResources(URL rootUrl, String dirPath)
        throws IOException {
        String filePath = rootUrl.getFile();
        File file = new File(filePath);
        File rootDir = file.getAbsoluteFile();
        return doFindMatchingFileSystemResources(rootDir, dirPath);
    }

    private static Collection<ClassPathResource> doFindMatchingFileSystemResources(File rootDir, String dirPath)
        throws IOException {
        Set<File> allFiles = Sets.newLinkedHashSet();
        retrieveAllFiles(rootDir, allFiles);
        String classPathRoot = parseClassPathRoot(rootDir, dirPath);
        Set<ClassPathResource> result = new LinkedHashSet<ClassPathResource>(allFiles.size());
        for (File file : allFiles) {
            String absolutePath = file.getAbsolutePath();
            URL url = new URL("file:///" + absolutePath);
            String classPathPath = absolutePath.substring(classPathRoot.length());
            classPathPath = StringUtils.replace(classPathPath, "\\", "/");
            result.add(new ClassPathResource(url, classPathPath));
        }
        return result;
    }

    private static String parseClassPathRoot(File rootDir, String dirPath) {
        String absolutePath = rootDir.getAbsolutePath();
        absolutePath = StringUtils.replace(absolutePath, "\\", "/");
        int lastIndex = absolutePath.lastIndexOf(dirPath);
        String result = absolutePath.substring(0, lastIndex);
        if (!result.endsWith("/")) {
            result = result + "/";
        }
        return result;
    }

    private static void retrieveAllFiles(File dir, Set<File> allFiles) {
        File[] subFiles = dir.listFiles();
        assert subFiles != null;
        allFiles.addAll(Arrays.asList(subFiles));

        for (File subFile : subFiles) {
            if (subFile.isDirectory()) {
                retrieveAllFiles(subFile, allFiles);
            }
        }
    }

    private static Collection<ClassPathResource> doFindPathMatchingJarResources(URL rootUrl) throws IOException {
        URLConnection con = rootUrl.openConnection();
        JarFile jarFile;
        String rootEntryPath;
        boolean newJarFile = false;

        if (con instanceof JarURLConnection) {
            // Should usually be the case for traditional JAR files.
            JarURLConnection jarCon = (JarURLConnection) con;
            jarCon.setUseCaches(true);
            jarFile = jarCon.getJarFile();
            JarEntry jarEntry = jarCon.getJarEntry();
            rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
        } else {
            // No JarURLConnection -> need to resort to URL file parsing.
            // We'll assume URLs of the format "jar:path!/entry", with the protocol
            // being arbitrary as long as following the entry format.
            // We'll also handle paths with and without leading "file:" prefix.
            String urlFile = rootUrl.getFile();
            int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
            if (separatorIndex != -1) {
                String jarFileUrl = urlFile.substring(0, separatorIndex);
                rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length());
                jarFile = getJarFile(jarFileUrl);
            } else {
                jarFile = new JarFile(urlFile);
                rootEntryPath = "";
            }
            newJarFile = true;
        }

        try {
            if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
                // Root entry path must end with slash to allow for proper matching.
                // The Sun JRE does not return a slash here, but BEA JRockit does.
                rootEntryPath = rootEntryPath + "/";
            }
            Set<ClassPathResource> result = new LinkedHashSet<ClassPathResource>(8);
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
                JarEntry entry = entries.nextElement();
                String entryPath = entry.getName();
                if (entryPath.startsWith(rootEntryPath)) {
                    String relativePath = entryPath.substring(rootEntryPath.length());
                    String rootPath = rootUrl.getPath();
                    rootPath = rootPath.endsWith("/") ? rootPath : rootPath + "/";
                    String newPath = applyRelativePath(rootPath, relativePath);
                    String classPathPath = applyRelativePath(rootEntryPath, relativePath);
                    result.add(new ClassPathResource(new URL(newPath), classPathPath));
                }
            }
            return result;
        } finally {
            // Close jar file, but only if freshly obtained -
            // not from JarURLConnection, which might cache the file reference.
            if (newJarFile) {
                jarFile.close();
            }
        }
    }

    /**
     * Apply the given relative path to the given path,
     * assuming standard Java folder separation (i.e. "/" separators).
     *
     * @param path         the path to start from (usually a full file path)
     * @param relativePath the relative path to apply
     *                     (relative to the full file path above)
     * @return the full file path that results from applying the relative path
     */
    private static String applyRelativePath(String path, String relativePath) {
        int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
        if (separatorIndex != -1) {
            String newPath = path.substring(0, separatorIndex);
            if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
                newPath += FOLDER_SEPARATOR;
            }
            return newPath + relativePath;
        } else {
            return relativePath;
        }
    }

    /**
     * Resolve the given jar file URL into a JarFile object.
     */
    private static JarFile getJarFile(String jarFileUrl) throws IOException {
        if (jarFileUrl.startsWith(FILE_URL_PREFIX)) {
            try {
                return new JarFile(toURI(jarFileUrl).getSchemeSpecificPart());
            } catch (URISyntaxException ex) {
                // Fallback for URLs that are not valid URIs (should hardly ever happen).
                return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length()));
            }
        } else {
            return new JarFile(jarFileUrl);
        }
    }

    /**
     * Create a URI instance for the given location String,
     * replacing spaces with "%20" URI encoding first.
     *
     * @param location the location String to convert into a URI instance
     * @return the URI instance
     * @throws URISyntaxException if the location wasn't a valid URI
     */
    private static URI toURI(String location) throws URISyntaxException {
        return new URI(StringUtils.replace(location, " ", "%20"));
    }

    private static boolean isJarResource(URL url) {
        String protocol = url.getProtocol();
        return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) ||
                URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) ||
                (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
    }

    /**
     * 类路径资源。
     */
    public static class ClassPathResource {
        /**
         * 此资源对应的 URL 对象。
         */
        private URL url;

        /**
         * 类路径下的路径。特点是这个路径字符串去掉了类路径的“根”部分。
         */
        private String classPathPath;

        /**
         * ctor.
         */
        public ClassPathResource(URL url, String classPathPath) {
            this.url = url;
            this.classPathPath = classPathPath;
        }

        public URL getUrl() {
            return url;
        }

        public String getClassPathPath() {
            return classPathPath;
        }
    }
}
分享到:
评论

相关推荐

    ClassGraph-超快速超轻量级并行化的Java类路径扫描程序

    ClassGraph是一款强大的Java工具,专为开发者设计,用于执行高效、快速且并行的类路径扫描。这款开源库提供了一种灵活的方式,帮助开发者轻松地探索、分析和管理应用程序的类结构。它不仅适用于常规的类扫描任务,还...

    classgraph,一个Uber快速、超轻量级Java类路径扫描器、模块扫描仪和注释处理器。.zip

    ClassGraph是一款强大的开源Java工具,它作为一个快速、超轻量级的类路径扫描器、模块扫描仪和注释处理器,广泛应用于各种Java开发场景。这个工具由Luke Daley开发,旨在提供灵活、高效且功能丰富的类扫描解决方案。...

    classgraph:超级快速的并行Java类路径扫描器和模块扫描器

    ClassGraph是适用于Java,Scala,Kotlin和其他JVM语言的超快速并行化类路径扫描器和模块扫描器。 ClassGraph在Oracle Code One 2018上获得了Duke's Choice奖(该奖项是对Java生态系统中最有用和/或最具创新性的...

    【Java毕业设计】毕业设计——基于Java的漏洞扫描系统.zip

    【Java毕业设计】毕业设计——基于Java的漏洞扫描系统 在IT行业中,Java是一种广泛应用的编程语言,尤其在企业级应用开发中占有重要地位。基于Java的漏洞扫描系统是网络安全领域的一个重要课题,旨在帮助企业和组织...

    letschat-2.2.129.zip

    《深入理解Java类路径扫描器:ClassGraph》 在Java开发中,类路径(Classpath)的管理和扫描是一项基础但至关重要的任务。它涉及到程序如何找到并加载运行所需的类文件。传统的类路径扫描通常依赖于Java的反射机制...

    基于Java语言的Controller类路径扫描与入出参数结构输出设计源码

    该项目为Java语言开发的Controller类路径扫描工具,包含32个文件,涵盖24个Java源文件、2个属性文件、1个Git忽略文件、1个JAR包文件、1个Markdown文件和1个Maven构建文件。该工具能够自动扫描项目中的Controller类,...

    java扫描仪接口调用源码

    在Java中,通常会创建一个Scanner类来封装扫描仪的接口调用,包括设置扫描参数(如分辨率、色彩模式等)、启动扫描、获取扫描图像等方法。这些方法通过JNI或JNA与扫描仪驱动进行交互。 其次,描述中提到需要在...

    JAVA域名扫描程序

    1. `.classpath`:这是Eclipse IDE的一个配置文件,它包含了项目所需的类路径信息,用于编译和运行Java程序。 2. `README.md`:这是一个Markdown格式的文件,通常包含项目简介、使用方法、安装步骤等信息,是理解...

    java,OpenCV简单实现类似“全能扫描王”功能

    在Eclipse这样的集成开发环境中,我们需要将这个文件添加到项目的类路径中,或者通过系统环境变量指定路径,确保Java能够正确调用OpenCV的函数。 实现“全能扫描王”功能的关键步骤包括以下几个部分: 1. **图像...

    Java的包扫描实现和Jar应用.docx

    注意,`getName()`返回的是类文件的完整路径,而`substring()`和`replace()`方法则用于转换成Java中的包名和类名格式。 在给定的测试用例中,使用了一个名为`ClassScanner`的类来扫描特定包下的所有类。`...

    通过java的反射技术获取,某个包路径下的类,并以表格形式打印类的属性注解注释及属性名称等

    在给定的场景中,我们需要实现的功能是遍历指定包路径下的所有类,获取它们的属性信息,包括属性的注解和注释,并以表格形式输出。 首先,我们需要通过`Package.getPackage(String name)`方法获取指定包名的Package...

    扫描接口实现类

    这个过程通常涉及到类路径扫描和反射技术。以下将详细介绍这一知识点。 首先,我们需要理解Java的类路径(ClassPath)。类路径是Java虚拟机(JVM)寻找类文件的路径,它决定了JVM如何查找和加载类。通过遍历类路径...

    java扫描图片

    2. **文件I/O操作**: 使用`java.io`包中的`File`类可以获取文件路径,创建和操作文件。在扫描图片时,我们需要创建`File`对象来指定图片的路径,然后使用`exists()`检查文件是否存在,`isFile()`确认是文件而非目录...

    java类查找小工具

    总的来说,"java类查找小工具"是Java开发者不可或缺的辅助工具之一,它能够帮助我们更有效地管理和理解项目依赖,提升开发效率,解决与类路径相关的问题。通过掌握这类工具的使用,可以进一步提升我们的Java编程技能...

    IDEA阿里巴巴Java编码规约扫描插件

    **IDEA阿里巴巴Java编码规约扫描插件** 阿里巴巴Java编码规约扫描插件是专为IntelliJ IDEA(IDEA)开发的一款辅助工具,旨在帮助Java开发者遵循阿里巴巴制定的Java编程规范,提升代码质量和可读性。这款插件提供...

    Java常用工具类UtilClass

    在Java编程中,工具类(Util Class)是包含各种实用函数的静态类,它们提供了一种简化常见任务的方法。在给定的`UtilClass`中,我们有五个主要的工具类:`StringUtil`、`FileUtil`、`ConnectDB`、`DateUtil`和`...

    java调用扫描仪,并附测试

    这通常涉及到下载JTwain的jar文件,并将其添加到项目的类路径中。对于Maven或Gradle等构建工具,可以在pom.xml或build.gradle文件中配置依赖。确保正确地引入库后,就可以在Java代码中导入必要的包并开始编写扫描仪...

    通过主动和被动扫描自动识别Java和.NET应用程序中的反序列化问题

    3. 限制反序列化权限:只允许受信任的类进行反序列化,通过配置白名单或黑名单来限制反序列化的类路径。 4. 应用安全更新:及时修复已知的反序列化漏洞,如CVE公告中列出的那些。 5. 定期进行安全审计和渗透测试:...

    java递归与非递归实现扫描文件夹下所有文件

    在Java中,可以使用File类来表示文件和文件夹,然后使用递归函数来实现文件夹下所有文件的扫描。 在上面的代码中,我们定义了一个FolderFileScanner类,该类中有两个静态方法:scanFilesWithRecursion和...

    Java核心技术卷I 卷II 原书中文第8版 非扫描

    4. **模块系统**:Java 9引入的模块系统,模块化开发的优势及如何配置模块路径。 5. **泛型**:泛型的语法,类型擦除,通配符,以及泛型的边界。 6. **并发编程**:并发工具类,如Semaphore、CyclicBarrier、...

Global site tag (gtag.js) - Google Analytics