使用反射Java获取指定包下所有类及其方法

使用反射获取指定包下所有类及其方法时的诡异问题及解决方案

引言

最近在项目中遇到了一个棘手的问题:通过反射获取指定包下面的所有类和类下面的所有方法,在本地使用IDEA运行项目时一切正常,但将项目打成JAR包后部署到服务器上却无法获取到。经过一番调查,发现问题出在类加载方式的不同上。

原因分析

问题的根源在于类加载器的不同。IDEA运行时使用的是IDEA自带的类加载器,而JAR包在服务器上运行时使用的是Java标准的类加载器。这种差异会导致反射获取类和方法时的行为不同。

解决方案

为了在JAR包部署环境中也能正确获取指定包下的所有类及其方法:

    /**
     * 获取包下所有类
     *
     * @param packageName 包路径
     * @return 类集合
     */
    public static Set<Class<?>> getClasses(String packageName) throws IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        Set<Class<?>> classes = new LinkedHashSet<>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            if ("file".equals(resource.getProtocol())) {
                processDirectory(classes, resource, packageName);
            } else if ("jar".equals(resource.getProtocol())) {
                processJarFile(classes, resource, packageName);
            }
        }
        return classes;
    }

	/**
     * 递归遍历目录下的所有文件
     *
     * @param classes     类集合
     * @param directory   目录
     * @param packageName 包名
     */
    private static void processDirectory(Set<Class<?>> classes, URL directory, String packageName) throws UnsupportedEncodingException, MalformedURLException {
        String path = URLDecoder.decode(directory.getFile(), "UTF-8");
        File[] files = new File(path).listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    processDirectory(classes, file.toURI().toURL(), packageName + "." + file.getName());
                } else if (file.getName().endsWith(".class")) {
                    String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                    try {
                        classes.add(Class.forName(className, false, getClassLoader()));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    /**
     * 解析jar包中的类集合
     *
     * @param classes     类集合
     * @param jarFileUrl  jar包
     * @param packageName 包名
     */
    private static void processJarFile(Set<Class<?>> classes, URL jarFileUrl, String packageName) throws IOException {
        JarFile jarFile = null;
        try {
            JarURLConnection jarURLConnection = (JarURLConnection) jarFileUrl.openConnection();
            if (jarURLConnection != null) {
                jarFile = jarURLConnection.getJarFile();
                if (jarFile != null) {
                    Enumeration<JarEntry> jarEntries = jarFile.entries();
                    while (jarEntries.hasMoreElements()) {
                        JarEntry jarEntry = jarEntries.nextElement();
                        String jarEntryName = jarEntry.getName();
                        if (jarEntryName.startsWith(packageName.replace('.', '/') + '/') && jarEntryName.endsWith(".class")) {
                            String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                            try {
                                classes.add(Class.forName(className));
                            } catch (ClassNotFoundException e) {
                                log.error(e.getMessage());
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            System.err.println("Error processing the Jar file: " + e.getMessage());
        } finally {
            // 在 finally 块中确保 JarFile 被正确关闭
            if (jarFile != null) {
                try {
                    jarFile.close();
                } catch (IOException e) {
                    System.err.println("Error closing the Jar file: " + e.getMessage());
                }
            }
        }
    }

    /**
     * 获取类的方法列表
     *
     * @param clazz 类
     * @return 方法列表
     */
    public static List<Map<String, Object>> getMethodsByClass(Class<?> clazz) {
        List<Map<String, Object>> methodList = new ArrayList<>();
        Arrays.stream(clazz.getDeclaredMethods()).forEach(method -> {
            Map<String, Object> methodNode = new HashMap<>();
            methodNode.put("methodName", method.getName());
            methodNode.put("methodPath", method.toString());
            methodList.add(methodNode);
        });
        return methodList;
    }
代码说明
  1. 获取指定包下的所有类
    使用Thread.currentThread().getContextClassLoader().getResources(packagePath)获取指定包路径下的所有资源。

  2. 处理JAR文件
    如果资源是JAR文件,则通过JarURLConnection获取JAR文件中的所有条目,并筛选出类文件。

  3. 处理目录
    如果资源是目录,则递归查找目录中的所有类文件。

  4. 使用反射获取方法
    对于每个找到的类,使用clazz.getDeclaredMethods()获取其所有方法,并输出方法名。

结论

通过上述方法,可以在本地IDEA开发环境和服务器部署环境中,正确地通过反射获取指定包下的所有类及其方法。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LOVE_DDZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值