查找接口类对应包下的所有类ClassUtil

本文介绍了一个Java工具类,用于扫描指定接口所在的包及其子包下所有实现该接口的类,并提供了一种方法来获取这些类的实例。此工具类通过递归遍历目录和解析类文件来完成任务。
package com.corejava.model.abstractmodel.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import com.corejava.model.abstractmodel.inter.test.TestA;

public class ClassUtils {

public static List<Class> getAllClassbyInterface(Class c) {

List<Class> returnList = new ArrayList<Class>();

if (c.isInterface()) { // 如果是接口。。。。
String packageName = c.getPackage().getName();
System.out.println("当前接口在的包"+packageName);

try {
List<Class> allClass = getClasses(packageName);
for(int i=0;i<allClass.size();i++){
if(!c.equals(allClass.get(i))){
returnList.add(allClass.get(i));
}
}

}
catch (IOException e) {

e.printStackTrace();
}
catch (ClassNotFoundException e) {

e.printStackTrace();
}
}

return returnList;
}

/**
* 从一个包中查到所有的类
*
* @param packageName
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
private static List<Class> getClasses(String packageName) throws IOException, ClassNotFoundException {

List<Class> classes = new ArrayList<Class>();

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
System.out.println("************"+packageName.indexOf("."));
// String aa = "com/corejava/model.abstractmodel.inter";

String path = packageName.replace(".","/");
Enumeration<URL> resources = classLoader.getResources(path);

List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
for (File dir : dirs) {
classes.addAll(findClass(dir, packageName));
}
return classes;
}

/**
* @param directory
* 文件路径
* @param packageName
* 包名
* @return 查找这个包下面的所有的class文件
* @throws ClassNotFoundException
*/
private static List<Class> findClass(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClass(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + "." + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}






public static void main(String[] args) {


List <Class> list = getAllClassbyInterface(TestA.class);

System.out.println("------"+list.size());

for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}





}



}
<think>我们正在处理一个关于如何从Java文件中读取包名的问题。 根据用户的问题:“我想使用Java读取class文件的包名 请问java read package name from class file” 我们需要从.class文件中读取包名。.class文件是Java编译后的字节码文件,其结构是固定的。包名实际上存储在的全限定名中,而全限定名存储在常量池中。 步骤: 1. 读取.class文件。 2. 解析.class文件的二进制结构,获取常量池。 3. 从常量池中找到的全限定名,然后提取包名。 .class文件的基本结构(基于Java虚拟机规范): - 魔数(4字节) - 版本号(4字节) - 常量池数量(2字节)和常量池项(变长) - 访问标志(2字节) - 当前索引(2字节,指向常量池中的名) - 超索引(2字节) - 接口索引表 - 字段表 - 方法表 - 属性表 关键点:当前索引指向常量池中的一个CONSTANT_Class_info结构,该结构又包含一个指向CONSTANT_Utf8_info的索引,该Utf8常量存储了的全限定名(内部形式,如com/example/MyClass)。 因此,我们可以通过以下步骤获取包名: a. 读取当前索引(在常量池之后,位置固定:魔数4+版本4+常量池数量2,然后跳过整个常量池,再读取访问标志2,然后就是当前索引) b. 根据当前索引,在常量池中找到对应的CONSTANT_Class_info项,然后从该项中获取name_index(指向一个CONSTANT_Utf8_info)。 c. 从该Utf8常量中获取字符串,即的全限定名(例如"com/example/MyClass")。 d. 将全限定名中的名部分去掉,剩下的就是包名(用'/'分隔),然后我们可以将'/'替换为'.'。 但是,直接解析.class文件需要处理很多细节。我们可以使用现有的库来简化操作,比如ASM或者javap等,但用户要求使用Java代码读取,因此我们可能会自己解析。 考虑到复杂度,我们可以选择只解析到获取常量池中的全名,而不实现完整的解析器。 另一种方法是使用Java自带的工具,例如ClassLoader,但这里我们是从.class文件直接读取,而不是加载。 由于用户没有指定是否允许使用第三方库,我们可以提供两种方式: 1. 使用标准Java API(不加载)来解析:需要自己解析二进制文件。 2. 使用第三方库(如ASM)来轻松获取。 我们先提供自己解析的方法(注意:只处理基本结构,不处理所有情况,仅用于获取名)。 步骤详细: 1. 读取文件到字节数组。 2. 检查魔数(0xCAFEBABE)。 3. 跳过版本号(4字节)。 4. 读取常量池数量(cp_count,注意实际常量池项数为cp_count-1)。 5. 遍历常量池,每个常量池项的第一个字节是tag,然后根据tag读取不同长度的数据。 6. 记录我们需要的常量池项(特别是CONSTANT_Utf8_info,因为名会指向它)。 7. 跳过访问标志后,读取当前索引(this_class,2字节)。 8. 根据this_class的值,找到对应的常量池项(应该是CONSTANT_Class_info,然后从它里面获取name_index)。 9. 根据name_index找到CONSTANT_Utf8_info,得到的全限定名字符串。 10. 从全限定名中提取包名:去掉最后一个'/'及其后面的部分。 注意:常量池的索引从1开始。 但是,自己解析整个常量池比较复杂,因为不同tag的项长度不同。我们可以只关心CONSTANT_Utf8_info,因为最终名是字符串,但其他项需要跳过。 我们参考一个简化的解析常量池的方法: 常量池项型(部分): - CONSTANT_Utf8 (1): 后面2字节是长度,然后是UTF-8编码的字符串(长度为指定长度)。 - CONSTANT_Integer (3): 4字节 - CONSTANT_Float (4): 4字节 - CONSTANT_Long (5): 8字节(注意:占两个位置,索引会加2) - CONSTANT_Double (6): 8字节(同样占两个位置) - CONSTANT_Class (7): 2字节(指向Utf8的索引) - CONSTANT_String (8): 2字节(指向Utf8的索引) - CONSTANT_Fieldref (9): 2字节(索引) + 2字节(名称和型索引) - CONSTANT_Methodref (10): 同上 - CONSTANT_InterfaceMethodref (11): 同上 - CONSTANT_NameAndType (12): 2字节(名称索引) + 2字节(描述符索引) - ... 等等 因此,在解析常量池时,我们需要根据tag来跳过不同的长度。 由于我们只需要名,所以我们可以这样做: - 我们只需要两个常量池项:由this_class指向的CONSTANT_Class_info,以及该Class_info指向的CONSTANT_Utf8_info。 - 但是,常量池是连续的,我们需要跳过前面的所有项才能到达后面的索引。 因此,我们可以遍历常量池,记录每个索引对应的项,但我们只关心Utf8项(因为最终名是Utf8),不过Class_info项我们也要记录,因为我们通过this_class得到Class_info,然后才能得到name_index。 然而,在解析过程中,我们并不需要保存所有的常量池项,我们只需要在解析完常量池后,能够通过索引找到对应的Utf8字符串。所以我们可以只保存Utf8常量池项(但注意,其他型的项可能包含指向Utf8的索引,但我们不关心,我们只关心对应的Utf8)。 但是,我们无法在解析过程中直接通过索引获取,因为常量池项是顺序存储的。因此,我们可以创建一个数组(大小为常量池数量),用于存储每个索引对应的值(对于Utf8项,存储字符串;对于其他项,存储null或者跳过)。但这样会浪费内存,因为我们只需要名。 另一种思路:我们只解析我们需要的那个Utf8项。但是,常量池必须顺序解析,因为每个项的长度不同,我们无法直接跳转到指定索引的项。 因此,我们只能顺序解析整个常量池,并记录下我们感兴趣的索引(即this_class指向的Class_info项中的name_index,然后我们还需要记录这个name_index对应的Utf8字符串)。但是,在解析过程中,我们并不知道哪个索引是我们要的,所以我们可以先解析整个常量池,构建一个从索引到值的映射(只记录Utf8字符串,其他项不记录字符串,但需要跳过以保持位置)。 具体步骤: 1. 读取常量池数量(cp_count),然后创建一个数组(大小为cp_count+1)来存储常量池项(我们只存储Utf8字符串,其他项存储null,因为我们只需要字符串)。 2. 从索引1开始到cp_count-1(注意:常量池索引从1到cp_count-1,因为常量池数量是cp_count,实际项数是cp_count-1?不对,规范中常量池数量是constant_pool_count,项索引从1到constant_pool_count-1。所以我们要解析constant_pool_count-1项)。 3. 对于每个索引位置,读取1字节的tag,然后根据tag跳过相应的字节数,如果是Utf8,则读取字符串并存储到数组的当前索引位置。 4. 解析完常量池后,我们读取访问标志(2字节),然后读取this_class(2字节)。 5. 根据this_class的值,从常量池数组中获取该索引的项。注意:this_class指向的是一个CONSTANT_Class_info项,但我们并没有存储这个项的内容(我们只存了Utf8项)。所以我们需要修改:在解析常量池时,对于CONSTANT_Class_info项,我们无法直接存储,但是我们可以记录它包含的name_index(即它指向的Utf8索引)。但是,这样就需要为每个常量池项都存储一些信息,比较复杂。 考虑到复杂度,我们改变策略:我们顺序解析常量池,同时记录每个索引对应型和必要信息(只记录我们关心的:对于Class_info,记录其name_index;对于Utf8,记录字符串)。这样,我们可以构建一个常量池信息表。 但是,由于我们只需要名,我们可以这样做: 在解析常量池时,我们只记录Utf8项(索引和字符串)。同时,当我们遇到Class_info项时,我们记录它所在的索引和它的name_index(但此时我们还没有解析到name_index对应的Utf8,所以我们需要在解析完整个常量池后,再通过name_index去我们记录的Utf8表中查找)。 但是,这样我们只能得到Class_info项中的name_index,然后我们还需要知道这个name_index对应的字符串是什么,所以我们必须记录所有Utf8项(索引->字符串)。 所以,我们只需要一个Map(或者数组)来存储Utf8常量池项(索引->字符串)。而Class_info项我们不需要存储内容,只需要跳过即可。因为我们的目标是:在得到this_class后,我们知道this_class指向一个常量池项(假设索引为x),然后我们要求索引x必须是一个CONSTANT_Class_info。但是,在解析过程中,我们并没有存储索引x是什么型,所以我们需要在解析常量池时,记录每个索引的项的型吗?实际上,我们可以不记录,因为我们在解析常量池时,对于每个索引,我们解析了它的内容,但我们只保存了Utf8的字符串。那么,当我们得到this_class(假设值为x)时,我们如何知道x对应的项是CONSTANT_Class_info?我们不知道,因为我们没有记录型。所以我们需要记录型吗? 其实,根据规范,this_class指向的必须是一个CONSTANT_Class_info。所以我们可以假设它指向的就是CONSTANT_Class_info。但是,CONSTANT_Class_info项包含一个name_index(2字节),所以我们在解析常量池时,当解析到索引x时,如果x正好是this_class,那么我们需要记录这个name_index。但我们此时还不知道this_class的值(因为this_class在常量池后面)。所以,我们必须在解析常量池时,记录所有CONSTANT_Class_info项(索引和name_index)吗? 这样看来,自己解析确实有点复杂。为了简化,我们可以使用以下方法: 1. 解析常量池,只保存Utf8字符串(建立索引到字符串的映射)。 2. 同时,我们忽略其他项,但正确跳过它们的字节。 3. 解析完常量池后,我们读取this_class(2字节)。 4. 然后,我们读取超索引(2字节,跳过),然后跳过接口(2字节的接口数量,然后每个接口2字节索引)。 5. 然后,我们跳过字段表、方法表,直到属性表。但是,我们其实不需要这些,因为我们的目标只是名。 但是,我们无法从常量池中直接通过this_class获取名,因为this_class指向的是CONSTANT_Class_info项,而CONSTANT_Class_info项并不是Utf8字符串。所以我们需要在解析常量池时,对于每个CONSTANT_Class_info项,记录它的索引和name_index(这样,当我们得到this_class时,就可以通过这个记录找到name_index,然后再通过name_index从Utf8映射中得到名字符串)。 因此,我们需要: - 一个Map: utf8Map,存放Utf8常量池项的索引到字符串的映射。 - 一个Map: classMap,存放Class_info常量池项的索引到name_index的映射。 解析常量池的伪代码: int cp_count = 读取2字节,然后cp_count表示常量池中的项数(实际索引从1到cp_count-1) 创建utf8Map和classMap(或者用两个数组,索引作为下标) int index = 1; // 常量池索引从1开始 while (index < cp_count) { 读取1字节的tag switch (tag) { case CONSTANT_Utf8 (1): 读取2字节的长度len 读取len个字节,用UTF-8解码为字符串(注意:这里简化,直接读字节,然后转换为字符串,但实际格式是MUTF-8,但Java字符串可以处理) 将字符串存入utf8Map,key=index index++ // 移动到下一个索引 break; case CONSTANT_Class (7): 读取2字节的name_index 将name_index存入classMap,key=index index++ // 移动到下一个索引 break; case CONSTANT_Long (5): 跳过8字节 index += 2; // 因为Long占两个索引 break; case CONSTANT_Double (6): 跳过8字节 index += 2; break; // 其他情况,根据tag跳过固定字节,并且index+1 default: 根据tag跳过相应的字节(可以查表,比如Integer和Float是4字节,Fieldref等是4字节,等等) index++; } } 然后,继续读取访问标志(2字节),然后读取this_class(2字节)。 现在,this_class的值是一个索引,我们到classMap中查找这个索引对应的name_index(假设为n_index)。 然后,再用n_index去utf8Map中查找,得到的全限定名字符串(例如"com/example/MyClass")。 最后,提取包名:找到最后一个'/'的位置,然后取子字符串(如果没有'/',说明在默认包,包名为空)。 注意:常量池中可能还有其他CONSTANT_Class_info,但我们只关心this_class指向的那一个。 代码实现中,我们需要注意字节顺序(.class文件是大端序)。 由于这个解析过程相对底层,我们也可以考虑使用第三方库,比如ASM的ClassReader,它可以轻松获取名。 下面我们提供两种实现方式: 方式一:自己解析(简化版,只处理部分常量池项,可能不完整,但适用于大多数文件) 方式二:使用ASM库(推荐,因为更可靠) 我们先写自己解析的版本,然后写使用ASM的版本。 自己解析的版本可能不处理所有常量池项,但常见的应该可以。 注意:自己解析版本仅用于学习目的,生产环境建议使用ASM等成熟库。 步骤: 1. 读取文件为字节数组。 2. 验证魔数(4字节:0xCA, 0xFE, 0xBA, 0xBE)。 3. 跳过版本号(4字节)。 4. 读取常量池数量(2字节,无符号,注意大端序)。 5. 解析常量池(跳过非目标项,记录Utf8和Class_info)。 6. 读取访问标志(2字节,跳过)。 7. 读取this_class(2字节)。 8. 从classMap中获取this_class对应的name_index,然后从utf8Map中获取该name_index对应的字符串(即全名)。 9.全名中的'/'替换为'.',然后提取包名(去掉名部分)。 提取包名:例如全名为"com/example/MyClass",则包名为"com.example"。 具体做法:找到最后一个'/'的位置,然后取前面的部分,并将'/'替换为'.'。 如果全名中没有'/',则包名为空字符串。 下面我们写代码示例: 注意:我们只处理部分常量池项(上述提到的几种),如果遇到不支持的tag,我们可以抛出异常或跳过(根据规范跳过相应字节数)。 常量池项标签对应的字节数(未列出的我们暂时不支持,但可以跳过): 1 (Utf8): 2字节长度 + 长度为len的字节数组 3 (Integer): 4 4 (Float): 4 5 (Long): 8(注意:占两个索引) 6 (Double): 8(占两个索引) 7 (Class): 2 8 (String): 2 9 (Fieldref): 4 10 (Methodref): 4 11 (InterfaceMethodref): 4 12 (NameAndType): 4 15 (MethodHandle): 3 16 (MethodType): 2 18 (InvokeDynamic): 4 其他标签(如模块、包等)可能出现在更高版本,我们暂时不考虑。 我们用一个switch语句来处理。 代码示例(自己解析):</think>### 从Class文件中读取包名的Java实现 要从.class文件中直接读取包名,需要解析Java文件的二进制结构。核心思路是读取文件常量池中的全限定名,然后从中提取包名部分。以下是两种实现方式: #### 方法一:手动解析文件(不依赖第三方库) ```java import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.HashMap; import java.util.Map; public class ClassPackageReader { // 常量池标签常量 private static final int CONSTANT_Utf8 = 1; private static final int CONSTANT_Class = 7; public static String getPackageName(File classFile) throws IOException { try (DataInputStream in = new DataInputStream(new FileInputStream(classFile))) { // 1. 验证魔数(0xCAFEBABE) if (in.readInt() != 0xCAFEBABE) { throw new IllegalArgumentException("无效的class文件"); } // 2. 跳过版本号 in.readShort(); // minor_version in.readShort(); // major_version // 3. 读取常量池 int cpCount = in.readUnsignedShort(); Map<Integer, Integer> classMap = new HashMap<>(); Map<Integer, String> utf8Map = new HashMap<>(); for (int i = 1; i < cpCount; i++) { int tag = in.readUnsignedByte(); switch (tag) { case CONSTANT_Utf8: int length = in.readUnsignedShort(); byte[] bytes = new byte[length]; in.readFully(bytes); utf8Map.put(i, new String(bytes, "UTF-8")); break; case CONSTANT_Class: classMap.put(i, in.readUnsignedShort()); break; case 5: // LONG case 6: // DOUBLE in.readLong(); // 占8字节 i++; // 额外占用一个常量池位置 break; case 3: // INTEGER case 4: // FLOAT in.readInt(); // 占4字节 break; case 9: // FIELDREF case 10: // METHODREF case 11: // INTERFACE_METHODREF case 12: // NAME_AND_TYPE in.readInt(); // 占4字节 break; case 15: // METHOD_HANDLE in.readByte(); in.readShort(); break; case 16: // METHOD_TYPE case 18: // INVOKE_DYNAMIC in.readShort(); // 占2字节 break; default: throw new IOException("未知常量池标签: " + tag); } } // 4. 读取this_class索引 in.readShort(); // 跳过access_flags int thisClassIndex = in.readUnsignedShort(); // 5. 通过常量池获取全名 Integer nameIndex = classMap.get(thisClassIndex); if (nameIndex == null) throw new IOException("名索引未找到"); String className = utf8Map.get(nameIndex); if (className == null) throw new IOException("名未找到"); // 6. 提取包名 int lastSlash = className.lastIndexOf('/'); return (lastSlash == -1) ? "" : className.substring(0, lastSlash).replace('/', '.'); } } public static void main(String[] args) throws IOException { File classFile = new File("YourClass.class"); System.out.println("包名: " + getPackageName(classFile)); } } ``` #### 方法二:使用ASM库(推荐) ```java import org.objectweb.asm.ClassReader; import java.io.IOException; import java.io.InputStream; public class ASMPackageReader { public static String getPackageName(InputStream classStream) throws IOException { ClassReader reader = new ClassReader(classStream); String className = reader.getClassName(); int lastSlash = className.lastIndexOf('/'); return (lastSlash == -1) ? "" : className.substring(0, lastSlash).replace('/', '.'); } // 使用示例 public static void main(String[] args) throws IOException { try (InputStream in = YourClass.class.getResourceAsStream("YourClass.class")) { System.out.println("包名: " + getPackageName(in)); } } } ``` ### 关键步骤解析 1. **文件结构**: - 魔数 `0xCAFEBABE` 验证文件型 - 常量池存储所有符号引用(包括名) - `this_class` 指向当前的全限定名 2. **包名提取原理**: - 全名格式:`com/example/ClassName` - 包名 = 最后一个`/`前的部分(替换`/`为`.`) - 默认包时返回空字符串 3. **常量池处理**: - `CONSTANT_Class` 项指向 `CONSTANT_Utf8` - 长型(LONG/DOUBLE)占用两个常量池位置 - 需处理大端序(Big-Endian)字节顺序 ### 注意事项 1. **字节序处理**:文件使用大端序(Big-Endian),需用`DataInputStream`或`ByteBuffer`处理 2. **常量池索引**:从1开始计数,长/双型占两个索引位 3. **异常处理**:需处理无效文件、格式错误等情况 4. **ASM优势**:更健壮(支持所有Java版本),代码更简洁 5. **默认包**:当在默认包时,返回空字符串而非`null` > 引用说明:文件解析方法参考Java虚拟机规范[^1],ASM库的使用参考官方文档[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值