Java遍历包中所有类包括jar包(完整转载)

第一部分转自 :http://blog.csdn.net/wangpeng047/article/details/8124390

第二部分转自:http://blog.csdn.net/wangpeng047/article/details/8202353

第三部分转自:http://blog.csdn.net/wangpeng047/article/details/8202353

第一部分

由于项目需要,我想获得某包下所有的类(包括该包的所有子包),从网上找了找,没有什么能用的,即使找到了写的也不怎样,效率低下。索性就自己写吧,正好也锻炼锻炼写代码的功底。特此分享出来,希望能帮到大家......

package com.itkt.mtravel.hotel.util;
 
import java.io.File;
import java.util.ArrayList;
import java.util.List;
 
public class PackageUtil {
	
	public static void main(String[] args) {
		String packageName = "com.itkt.mtravel.hotel";
 
		List<String> classNames = getClassName(packageName);
		for (String className : classNames) {
			System.out.println(className);
		}
	}
 
	public static List<String> getClassName(String packageName) {
		String filePath = ClassLoader.getSystemResource("").getPath() + packageName.replace(".", "\\");
		List<String> fileNames = getClassName(filePath, null);
		return fileNames;
	}
 
	private static List<String> getClassName(String filePath, List<String> className) {
		List<String> myClassName = new ArrayList<String>();
		File file = new File(filePath);
		File[] childFiles = file.listFiles();
		for (File childFile : childFiles) {
			if (childFile.isDirectory()) {
				myClassName.addAll(getClassName(childFile.getPath(), myClassName));
			} else {
				String childFilePath = childFile.getPath();
				childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));
				childFilePath = childFilePath.replace("\\", ".");
				myClassName.add(childFilePath);
			}
		}
 
		return myClassName;
	}
}

没什么特别复杂的,看看基本上就能懂的。给入任意包的命名空间,就能返回该包下的所有类。自我感觉还不错,简单易用,通用和扩展性也不错。其实写代码,在有熟练的基本功之后,剩下的就是组装的思路了。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

第二部分

以上是,关于Java遍历包中所有类,但经过一名网友提醒发现,只能适用于项目src中的包,当包在jar里时就无法遍历jar里的类。就此我针对代码进行了细化,功能得到进一步的完善。

在分享源码之前,先说说我在遍历jar包中的类时所遇到的困难。

这是我测试用的jar包,结构如下:

无论包是在src中还是在jar中,其实根本的思路还是根据给的包域名(如:com.wang.vo.request.hotel.test)定位到包的资源对象。包在src中,我们可以把它当做文件File来进行处理,因此在src中包是以文件夹的形式来体现的,但在jar中,包的含义是有些不同的,我们不能把它当做File来对待(这样你或得到的是jar的File对象)。那么怎么才能获取jar里包的资源对象呢?

代码如下:

	public static void main(String[] args) throws Exception {
		String packageName = "com/wang/vo/request/hotel/test";
		URL url = Thread.currentThread().getContextClassLoader().getResource(packageName);
		if (url != null) {
			System.out.println(url.getPath());
		}
	}

但是通过上述代码,运行后却发现url始终为空,尝试过各种获取资源的办法(如getResourceAsStream),均无法解决,我试了试其他的jar包(如spring的),却发现相同的代码url却有值。经过我反复的测试和分析发现,问题出现在生成jar包的方式上,即所谓打jar包。

一般来说,我们打jar包的步骤大致如下:

之后一直默认,然后Finish。这样看似没问题,但问题出现在这一步:

这种默认方式生成的jar包中,只含有class文件,而并没有我们大众所知的文件夹目录结构。可能我们大多数人认为com.test.Student类,Student类文件就应该在com文件夹下的test文件夹里,这其实是片面的,是一个误区!

com.test真正的含义是package包域名,就好比.net里的命名空间,它只是为了区分、汇总、唯一标识不同的类而提出的概念,跟文件夹目录层次结构是两回事,我们只是习惯上用文件夹目录来展示package而已。但package却不一定非要用过文件夹目录来展示。

我们可以用下面这段代码来进一步说明这个问题
 

	public static void main(String[] args) throws Exception {
		// 项目中jar包所在物理路径
		String jarName = "E:/Work/stsf_skisok_product/WebRoot/WEB-INF/lib/testpackage.jar";
		JarFile jarFile = new JarFile(jarName);
		Enumeration<JarEntry> entrys = jarFile.entries();
		while (entrys.hasMoreElements()) {
			JarEntry jarEntry = entrys.nextElement();
			System.out.println(jarEntry.getName());
		}				
	}

默认生成的jar包,运行结果如下:

META-INF/MANIFEST.MF
com/wang/util/DateStyle.class
com/wang/util/PropertiesUtil$1.class
com/wang/util/PropertiesUtil.class
com/wang/util/Week.class
com/wang/util/DateUtil.class
com/wang/vo/request/hotel/test/PopularCityRequest.class
com/wang/vo/request/hotel/test/EconomicsRequest.class
com/wang/vo/request/hotel/test/HotelProductVouchRequest.class
com/wang/vo/request/hotel/test/QueryOrderListRequest.class
com/wang/vo/request/hotel/test/HotelListQueryRequest.class
com/wang/vo/request/hotel/test/RoomReserveRequest.class
com/wang/vo/request/hotel/test/HotelOneQueryRequest.class
com/wang/vo/request/hotel/test/HotelBrandRequest.class
如果勾选Add directory entries选项生成的jar包,运行结果如下:

META-INF/MANIFEST.MF
com/
com/wang/
com/wang/util/
com/wang/util/DateStyle.class
com/wang/util/PropertiesUtil$1.class
com/wang/util/PropertiesUtil.class
com/wang/util/Week.class
com/wang/util/DateUtil.class
com/wang/vo/
com/wang/vo/request/
com/wang/vo/request/hotel/
com/wang/vo/request/hotel/test/
com/wang/vo/request/hotel/test/PopularCityRequest.class
com/wang/vo/request/hotel/test/EconomicsRequest.class
com/wang/vo/request/hotel/test/HotelProductVouchRequest.class
com/wang/vo/request/hotel/test/QueryOrderListRequest.class
com/wang/vo/request/hotel/test/HotelListQueryRequest.class
com/wang/vo/request/hotel/test/RoomReserveRequest.class
com/wang/vo/request/hotel/test/HotelOneQueryRequest.class
com/wang/vo/request/hotel/test/HotelBrandRequest.class
这样也就解释了为何打成jar包后用getResource获取资源url总是为空的原因了。

好了,这个问题解决了之后,那么如何利用Java遍历jar包中所有类的问题也前进了一大步了,我将在下一篇博文中正是分享源码。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

第三部分

上面,我向大家讲述了遍历jar包时所遇到的困难,本篇将向大家分享最终版代码。

package com.itkt.mtravel.hotel.util;
 
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
 
public class PackageUtil {
 
	public static void main(String[] args) throws Exception {
		String packageName = "com.wang.vo.request.hotel";
		// List<String> classNames = getClassName(packageName);
		List<String> classNames = getClassName(packageName, false);
		if (classNames != null) {
			for (String className : classNames) {
				System.out.println(className);
			}
		}
	}
 
	/**
	 * 获取某包下(包括该包的所有子包)所有类
	 * @param packageName 包名
	 * @return 类的完整名称
	 */
	public static List<String> getClassName(String packageName) {
		return getClassName(packageName, true);
	}
 
	/**
	 * 获取某包下所有类
	 * @param packageName 包名
	 * @param childPackage 是否遍历子包
	 * @return 类的完整名称
	 */
	public static List<String> getClassName(String packageName, boolean childPackage) {
		List<String> fileNames = null;
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		String packagePath = packageName.replace(".", "/");
		URL url = loader.getResource(packagePath);
		if (url != null) {
			String type = url.getProtocol();
			if (type.equals("file")) {
				fileNames = getClassNameByFile(url.getPath(), null, childPackage);
			} else if (type.equals("jar")) {
				fileNames = getClassNameByJar(url.getPath(), childPackage);
			}
		} else {
			fileNames = getClassNameByJars(((URLClassLoader) loader).getURLs(), packagePath, childPackage);
		}
		return fileNames;
	}
 
	/**
	 * 从项目文件获取某包下所有类
	 * @param filePath 文件路径
	 * @param className 类名集合
	 * @param childPackage 是否遍历子包
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
		List<String> myClassName = new ArrayList<String>();
		File file = new File(filePath);
		File[] childFiles = file.listFiles();
		for (File childFile : childFiles) {
			if (childFile.isDirectory()) {
				if (childPackage) {
					myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
				}
			} else {
				String childFilePath = childFile.getPath();
				if (childFilePath.endsWith(".class")) {
					childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));
					childFilePath = childFilePath.replace("\\", ".");
					myClassName.add(childFilePath);
				}
			}
		}
 
		return myClassName;
	}
 
	/**
	 * 从jar获取某包下所有类
	 * @param jarPath jar文件路径
	 * @param childPackage 是否遍历子包
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByJar(String jarPath, boolean childPackage) {
		List<String> myClassName = new ArrayList<String>();
		String[] jarInfo = jarPath.split("!");
		String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/"));
		String packagePath = jarInfo[1].substring(1);
		try {
			JarFile jarFile = new JarFile(jarFilePath);
			Enumeration<JarEntry> entrys = jarFile.entries();
			while (entrys.hasMoreElements()) {
				JarEntry jarEntry = entrys.nextElement();
				String entryName = jarEntry.getName();
				if (entryName.endsWith(".class")) {
					if (childPackage) {
						if (entryName.startsWith(packagePath)) {
							entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
							myClassName.add(entryName);
						}
					} else {
						int index = entryName.lastIndexOf("/");
						String myPackagePath;
						if (index != -1) {
							myPackagePath = entryName.substring(0, index);
						} else {
							myPackagePath = entryName;
						}
						if (myPackagePath.equals(packagePath)) {
							entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
							myClassName.add(entryName);
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return myClassName;
	}
 
	/**
	 * 从所有jar中搜索该包,并获取该包下所有类
	 * @param urls URL集合
	 * @param packagePath 包路径
	 * @param childPackage 是否遍历子包
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByJars(URL[] urls, String packagePath, boolean childPackage) {
		List<String> myClassName = new ArrayList<String>();
		if (urls != null) {
			for (int i = 0; i < urls.length; i++) {
				URL url = urls[i];
				String urlPath = url.getPath();
				// 不必搜索classes文件夹
				if (urlPath.endsWith("classes/")) {
					continue;
				}
				String jarPath = urlPath + "!/" + packagePath;
				myClassName.addAll(getClassNameByJar(jarPath, childPackage));
			}
		}
		return myClassName;
	}
}

由于我们并不确定jar包生成时采用的哪种方式,如果采用默认生成jar包的方式,那我们通过Thread.currentThread().getContextClassLoader().getResource()是获取不到的,因此我增加了从所有jar包中搜索提供的包域名,这样功能就完善了很多。

那么就此关于“如何遍历包中所有类”就结束了,PackageUtil这个类的功能还有些少,不排除日后进一步完善的可能,如果大家关于这个util有什么新的需求或者建议,随时欢迎大家提出。发现bug的,也请及时通知我以便改进。

当你想要遍历一个Jar包中的所有Jar包,并使用URLClassLoader加载它们时,你可以使用前面提到的JarURLConnection和URLClassLoader的组合来实现。以下是一个示例代码: ```java import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class Main { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { // 定义需要遍历Jar包路径 String jarPath = "path/to/your/jar/file.jar"; // 创建JarFile对象 JarFile jarFile = new JarFile(jarPath); // 获取JarFile中的所有条目 Enumeration<JarEntry> entries = jarFile.entries(); // 创建存储Jar包URL的列表 List<URL> jarURLs = new ArrayList<>(); // 遍历JarFile中的所有条目 while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); // 判断条目是否为Jar文件 if (entryName.endsWith(".jar")) { // 获取Jar文件的URL URL jarURL = new URL("jar:file:" + jarPath + "!/" + entryName); // 添加到列表中 jarURLs.add(jarURL); } } // 创建URLClassLoader对象,加载所有Jar包的URL URLClassLoader classLoader = new URLClassLoader(jarURLs.toArray(new URL[0])); // 遍历所有Jar包的URL,加载Jar包中的 for (URL url : jarURLs) { JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); JarFile jar = jarURLConnection.getJarFile(); // 遍历Jar包中的所有 Enumeration<JarEntry> jarEntries = jar.entries(); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); String className = jarEntry.getName(); // 判断条目是否为文件 if (className.endsWith(".class")) { // 转换名为的全限定名 className = className.replace("/", ".").substring(0, className.length() - 6); // 使用URLClassLoader加载 Class<?> loadedClass = classLoader.loadClass(className); // 创建该的实例,并调用方法 Object instance = loadedClass.newInstance(); loadedClass.getMethod("someMethod").invoke(instance); } } } // 关闭JarFile jarFile.close(); } } ``` 在上述代码中,我们首先遍历Jar包中的所有条目,筛选出其中的Jar文件,并将它们的URL添加到jarURLs列表中。 然后,我们使用URLClassLoader加载jarURLs列表中的所有Jar包。通过遍历jarURLs列表,我们打开每个Jar包的连接,并使用JarFile获取其中的条目。我们筛选出文件,并使用URLClassLoader加载这些。最后,我们可以根据需要创建实例并调用方法。 请记得将"path/to/your/jar/file.jar"替换为实际的Jar包路径。 希望这能帮助到你!如有更多问题,请随时提问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值