Java如何扫描指定package下所有的类

本文介绍了一种用于MVC框架的组件扫描方法,通过自定义ClasspathPackageScanner类实现从指定包及其子包中查找所有类文件的功能。适用于web服务器是否解压JAR包的不同情况。
部署运行你感兴趣的模型镜像

在写一个MVC框架,需要从包中扫描出组件并注册到容器中,而JDK没有提供现成的从方法,只能自己实现。

功能:
给定一个包名,编程得到该包(和其所有子包)下所有的类文件。如,输入包名com.myapp.util, 输出该包下类的全限定名com.myapp.util.StringUtils, com.app.util.ImageUtils等。

思路:
有的web server在部署运行时会解压jar包,因此class文件会在普通的文件目录下。如果web server不解压jar包,则class文件会直接存在于Jar包中。对于前者,只需定位到class文件所在目录,然后将class文件名读取出即可;对于后者,则需先定位到jar包所在目录,然后使用JarInputStream读取Jar包,得到class类名。

实现:
这是从写好的项目代码中直接copy出来的,如果要运行这段代码,需要把所有的Logger.debug改成System.out.println()

/**
 * This scanner is used to find out all classes in a package.
 * Created by whf on 15-2-26.
 */
public class ClasspathPackageScanner implements PackageScanner {
    private Logger logger = LoggerFactory.getLogger(ClasspathPackageScanner.class);

    private String basePackage;
    private ClassLoader cl;

    /**
     * Construct an instance and specify the base package it should scan.
     * @param basePackage The base package to scan.
     */
    public ClasspathPackageScanner(String basePackage) {
        this.basePackage = basePackage;
        this.cl = getClass().getClassLoader();

    }

    /**
     * Construct an instance with base package and class loader.
     * @param basePackage The base package to scan.
     * @param cl Use this class load to locate the package.
     */
    public ClasspathPackageScanner(String basePackage, ClassLoader cl) {
        this.basePackage = basePackage;
        this.cl = cl;
    }

    /**
     * Get all fully qualified names located in the specified package
     * and its sub-package.
     *
     * @return A list of fully qualified names.
     * @throws IOException
     */
    @Override
    public List<String> getFullyQualifiedClassNameList() throws IOException {
        logger.info("开始扫描包{}下的所有类", basePackage);

        return doScan(basePackage, new ArrayList<>());
    }

    /**
     * Actually perform the scanning procedure.
     *
     * @param basePackage
     * @param nameList A list to contain the result.
     * @return A list of fully qualified names.
     *
     * @throws IOException
     */
    private List<String> doScan(String basePackage, List<String> nameList) throws IOException {
        // replace dots with splashes
        String splashPath = StringUtil.dotToSplash(basePackage);

        // get file path
        URL url = cl.getResource(splashPath);
        String filePath = StringUtil.getRootPath(url);

        // Get classes in that package.
        // If the web server unzips the jar file, then the classes will exist in the form of
        // normal file in the directory.
        // If the web server does not unzip the jar file, then classes will exist in jar file.
        List<String> names = null; // contains the name of the class file. e.g., Apple.class will be stored as "Apple"
        if (isJarFile(filePath)) {
            // jar file
            if (logger.isDebugEnabled()) {
                logger.debug("{} 是一个JAR包", filePath);
            }

            names = readFromJarFile(filePath, splashPath);
        } else {
            // directory
            if (logger.isDebugEnabled()) {
                logger.debug("{} 是一个目录", filePath);
            }

            names = readFromDirectory(filePath);
        }

        for (String name : names) {
            if (isClassFile(name)) {
                //nameList.add(basePackage + "." + StringUtil.trimExtension(name));
                nameList.add(toFullyQualifiedName(name, basePackage));
            } else {
                // this is a directory
                // check this directory for more classes
                // do recursive invocation
                doScan(basePackage + "." + name, nameList);
            }
        }

        if (logger.isDebugEnabled()) {
            for (String n : nameList) {
                logger.debug("找到{}", n);
            }
        }

        return nameList;
    }

    /**
     * Convert short class name to fully qualified name.
     * e.g., String -> java.lang.String
     */
    private String toFullyQualifiedName(String shortName, String basePackage) {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(StringUtil.trimExtension(shortName));

        return sb.toString();
    }

    private List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("从JAR包中读取类: {}", jarPath);
        }

        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
        JarEntry entry = jarIn.getNextJarEntry();

        List<String> nameList = new ArrayList<>();
        while (null != entry) {
            String name = entry.getName();
            if (name.startsWith(splashedPackageName) && isClassFile(name)) {
                nameList.add(name);
            }

            entry = jarIn.getNextJarEntry();
        }

        return nameList;
    }

    private List<String> readFromDirectory(String path) {
        File file = new File(path);
        String[] names = file.list();

        if (null == names) {
            return null;
        }

        return Arrays.asList(names);
    }

    private boolean isClassFile(String name) {
        return name.endsWith(".class");
    }

    private boolean isJarFile(String name) {
        return name.endsWith(".jar");
    }

    /**
     * For test purpose.
     */
    public static void main(String[] args) throws Exception {
        PackageScanner scan = new ClasspathPackageScanner("cn.fh.lightning.bean");
        scan.getFullyQualifiedClassNameList();
    }

}

上面的代码中用到了StringUtils类,如下:

public class StringUtil {
    private StringUtil() {

    }

    /**
     * "file:/home/whf/cn/fh" -> "/home/whf/cn/fh"
     * "jar:file:/home/whf/foo.jar!cn/fh" -> "/home/whf/foo.jar"
     */
    public static String getRootPath(URL url) {
        String fileUrl = url.getFile();
        int pos = fileUrl.indexOf('!');

        if (-1 == pos) {
            return fileUrl;
        }

        return fileUrl.substring(5, pos);
    }

    /**
     * "cn.fh.lightning" -> "cn/fh/lightning"
     * @param name
     * @return
     */
    public static String dotToSplash(String name) {
        return name.replaceAll("\\.", "/");
    }

    /**
     * "Apple.class" -> "Apple"
     */
    public static String trimExtension(String name) {
        int pos = name.indexOf('.');
        if (-1 != pos) {
            return name.substring(0, pos);
        }

        return name;
    }

    /**
     * /application/home -> /home
     * @param uri
     * @return
     */
    public static String trimURI(String uri) {
        String trimmed = uri.substring(1);
        int splashIndex = trimmed.indexOf('/');

        return trimmed.substring(splashIndex);
    }
}

执行结果:
这里写图片描述

您可能感兴趣的与本文相关的镜像

Anything-LLM

Anything-LLM

AI应用

AnythingLLM是一个全栈应用程序,可以使用商用或开源的LLM/嵌入器/语义向量数据库模型,帮助用户在本地或云端搭建个性化的聊天机器人系统,且无需复杂设置

<think>好的,我需要帮助用户解决如何使用Java反射机制扫描指定目录下的所有文件的问题。首先,我需要回顾一下用户提供的引用内容,看看有没有相关的实现方法。用户引用的内容提到扫描内的,使用加载器加载,以及结合反射和注解的应用场景。 用户的问题是关于如何实现扫描目录下的所有文件。根据引用[2],提到了获取路径、获取内所有文件名,并根据文件名得到Class对象。这可能涉及到将目录路径转换为名,然后遍历文件系统查找.class文件。 接下来,我需要考虑Java反射本身是否能直接扫描目录。反射主要用于加载和操作,但扫描目录可能需要结合IO操作。例如,通过加载器获取资源,然后遍历目录中的文件。引用[5]中的链接可能提供了一些代码示例,但链接不可用,所以需要依赖其他引用。 引用[4]提到自定义轻量级测试框架,可能需要扫描,这通常需要处理文件路径和加载。可能的步骤括:确定对应的物理路径,递归遍历所有.class文件,将文件名转换为全限定名,然后使用Class.forName加载。 需要注意的是,如果未被加载,直接使用Class.forName可能会导致问题,所以需要确保路径中。此外,处理文件系统路径时,不同操作系统的分隔符可能需要处理,比如将“/”替换为File.separator。 代码示例可能需要使用Files.walk或递归遍历目录,过滤出.class文件,然后处理文件名得到名,最后加载。引用[2]和[5]可能提到了似的方法。 总结步骤:1. 将名转换为文件路径。2. 遍历目录下的所有.class文件。3. 将文件路径转换为全限定名。4. 使用反射加载。需要注意异常处理,如ClassNotFoundException,以及处理内部等情况。</think>### 实现Java反射扫描指定目录下所有文件的步骤 #### 1. **获取路径对应的物理路径** 将名(如 `com.example`)转换为文件系统的物理路径(如 `classpath:com/example`)。可以通过加载器的 `getResource()` 方法实现: ```java String packagePath = packageName.replace('.', '/'); URL resource = Thread.currentThread().getContextClassLoader().getResource(packagePath); ``` #### 2. **遍历目录下的所有.class文件** 使用 `Files.walk()` 或递归遍历目录,筛选出 `.class` 文件: ```java Path start = Paths.get(resource.toURI()); Files.walk(start) .filter(path -> path.toString().endsWith(".class")) .forEach(path -> processClassFile(path, packageName)); ``` #### 3. **将文件路径转换为全限定名** 例如,文件 `com/example/Test.class` 对应名 `com.example.Test`: ```java String className = path.toString() .replace(start.toString(), "") .replace(File.separator, ".") .replace(".class", ""); if (className.startsWith(".")) className = className.substring(1); ``` #### 4. **使用反射加载** 通过 `Class.forName()` 加载(需捕获异常): ```java Class<?> clazz = Class.forName(className); ``` #### 完整代码示例 ```java public static List<Class<?>> scanClasses(String packageName) throws Exception { List<Class<?>> classes = new ArrayList<>(); String packagePath = packageName.replace('.', '/'); URL resource = Thread.currentThread().getContextClassLoader().getResource(packagePath); if (resource == null) return classes; Path start = Paths.get(resource.toURI()); Files.walk(start) .filter(path -> path.toString().endsWith(".class")) .forEach(path -> { String className = path.toString() .replace(start.toString().replace(File.separator, "/"), "") .replace("/", ".") .replace(".class", "") .replaceFirst("^\\.", ""); try { classes.add(Class.forName(packageName + "." + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } }); return classes; } ``` #### 注意事项 1. **加载限制**:只能扫描含在路径(classpath)中的文件[^2][^4]。 2. **动态加载**:若未被加载,需确保其位于路径中,否则会抛出 `ClassNotFoundException`。 3. **文件系统差异**:路径分隔符需适配不同操作系统(如Windows用`\`,Linux用`/`)[^2]。 4. **内部处理**:需过滤掉编译器生成的内部(如 `Outer$Inner.class`)[^5]。 --- ### 相关问题 1. **如何通过Java反射获取的注解信息?** (反射中可用 `clazz.getAnnotations()` 获取注解[^3]) 2. **扫描文件时如何避免性能问题?** (可缓存扫描结果或使用索引文件优化[^3]) 3. **如何结合反射和注解实现依赖注入?** (扫描标记了 `@Component` 的,通过反射实例化并注入) 4. **Java反射加载时如何避免安全问题?** (需配置安全管理器,限制敏感操作)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值