类扫描器获取全部子类或接口的全部实现

一、问题提出

在JAVA中,获取一个类的全部父类(包括父类的父类)是比较简单的,只需要通过反射(Class的getSuperclass()方法)即可。然而,如果想获得一个类的所有子类,或者获得实现某一个接口的所有实现类,相对比较麻烦。

用过Eclipse的开发人员都知道,通过F4键或(Ctrl+T组合键)可以查到指定类的类层次结构。仔细想一下该快捷键的实现原理,或许可以找到一个可行的设计思路。

首先,需要确定一个前提是,寻找所有子类,必须先指定搜索的文件范围。打个比方,要寻找一个古人的所有后代成员,必须设置查找的地理范围是在中国内,否则就无从入手。

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。
任意门--》ai学习网站

二、解决思路

结合实际的项目部署环境,查找子类的方法需要有两种方式。第一种,在开发环境,可以直接遍历指定范围下的所有源代码文件,再结合反射的知识;第二种,假设项目已经打成jar包,或者引入第三方依赖,则只能通过jar包获得所有类文件。

2.1非spring环境代码实现

下面给出具体的代码实现

1.若是开发环境,则通过递归查找指定目录下的类文件的全路径,代码如下

/**
	 *  递归查找指定目录下的类文件的全路径
	 * @param baseFile 查找文件的入口
	 * @param fileList 保存已经查找到的文件集合
	 */
	public static  void getSubFileNameList(File baseFile, List<String> fileList){
		if(baseFile.isDirectory()){
			File[] files = baseFile.listFiles();
			for(File tmpFile : files){
				getSubFileNameList(tmpFile,fileList);
			}
		}
		String path = baseFile.getPath();
		if(path.endsWith(".java")){
			String name1 = path.substring(path.indexOf("src")+4, path.length());
			String name2 = name1.replaceAll("\\\\", ".");
			String name3 = name2.substring(0, name2.lastIndexOf(".java"));
			fileList.add(name3);
		}
	}

2.若是jar包环境,则可以通过JarFile这个工具类,获得所有全部类信息

/**
	 *  从jar包读取所有的class文件名
	 */
	private static List<String> getClassNameFrom(String jarName){
		List<String> fileList = new ArrayList<String>();
		try {
			JarFile jarFile = new JarFile(new File(jarName));
			 Enumeration<JarEntry> en = jarFile.entries(); // 枚举获得JAR文件内的实体,即相对路径  
			 while (en.hasMoreElements()) {
				 String name1 =  en.nextElement().getName();
				 if(!name1.endsWith(".class")){//不是class文件
					 continue;
				 }
				 String name2 = name1.substring(0, name1.lastIndexOf(".class"));
				 String name3 = name2.replaceAll("/", ".");
				 fileList.add(name3);
			 }
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return fileList;
	}

3.从前两步可以得到所有子类或所有接口实现类的类路径信息,有了类的全路径,就可以通过反射拿到类的信息,用来判断是否满足条件

/**
	 *  判断一个类是否继承某个父类或实现某个接口
	 */
	public static boolean isChildClass(String className,Class parentClazz){
		if(className == null) return false;
		
		Class clazz = null;
		try {
			clazz = Class.forName(className);
			if(Modifier.isAbstract(clazz.getModifiers())){//抽象类忽略
				return false;
			}
			if(Modifier.isInterface(clazz.getModifiers())){//接口忽略
				return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return parentClazz.isAssignableFrom(clazz);
		
	}

4.写几个简单的测试类,用来说明问题

public abstract class Animal {

	public abstract void eat();
	
	public abstract void walk();
}
package bean;

public class Cat extends Animal{

	@Override
	public void eat() {
		System.err.println("小猫吃东西");
	}

	@Override
	public void walk() {
		System.err.println("小猫走路");
	}

}
public class Dog extends Animal{

	@Override
	public void eat() {
		System.err.println("小狗吃东西");
	}

	@Override
	public void walk() {
		System.err.println("小狗走路");
	}

}
public class Person {
	private String name;
	private int age;
	
	public Person(){
		
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public void sayHello(){
		System.err.println("hello,i am " + this.name);
	}
}

5.入口方法,打印输出结果(jar包可直接使用Eclipse导出可执行jar文件)

	List<String> fileList = new ArrayList<String>();
		
		File baseFile = new File(getSrcPath()+File.separator+"src"+File.separator+"bean");
		if(baseFile.exists()){//开发环境,读取源文件
			getSubFileNameList(baseFile,fileList);
		}else{//jar包环境
			fileList = getClassNameFrom("server.jar");
		}
		System.err.println("Animal类的所有子类有");
		for(String name:fileList){
			if(isChildClass(name, Animal.class))
			System.err.println(name);
		}

6.代码运行结果如下

2.2spring环境代码实现

如果项目使用了spring框架,则一切变得更简单了。因为spring本身就需要扫描各种类文件。结合spring提供的API,我们可以设计一个高可用的类扫描工具。

直接分享源码

public class ClassScanner {

	private static Logger logger = LoggerFactory.getLogger(ClassScanner.class);

	/**
	 * 默认过滤器(无实现)
	 */
	private final static Predicate<Class<?>> EMPTY_FILTER = clazz -> true;

	/**
	 * 扫描目录下的所有class文件
	 * 
	 * @param scanPackage 搜索的包根路径
	 * @return
	 */
	public static Set<Class<?>> getClasses(String scanPackage) {
		return getClasses(scanPackage, EMPTY_FILTER);
	}

	/**
	 * 返回所有的子类(不包括抽象类)
	 * 
	 * @param scanPackage 搜索的包根路径
	 * @param parent
	 * @return
	 */
	public static Set<Class<?>> listAllSubclasses(String scanPackage, Class<?> parent) {
		return getClasses(scanPackage, (clazz) -> {
			return parent.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers());
		});
	}

	/**
	 * 返回所有带制定注解的class列表
	 * 
	 * @param scanPackage 搜索的包根路径
	 * @param annotation
	 * @return
	 */
	public static <A extends Annotation> Set<Class<?>> listClassesWithAnnotation(String scanPackage,
			Class<A> annotation) {
		return getClasses(scanPackage, (clazz) -> {
			return clazz.getAnnotation(annotation) != null;
		});
	}

	/**
	 * 扫描目录下的所有class文件
	 * 
	 * @param pack   包路径
	 * @param filter 自定义类过滤器
	 * @return
	 */
	public static Set<Class<?>> getClasses(String pack, Predicate<Class<?>> filter) {
		ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
		MetadataReaderFactory metaFactory = new SimpleMetadataReaderFactory(patternResolver);

		String path = ClassUtils.convertClassNameToResourcePath(pack);
		String location = ResourceUtils.CLASSPATH_URL_PREFIX + path + "/**/*.class";
		Resource[] resources;

		Set<Class<?>> result = new HashSet<>();
		try {
			resources = patternResolver.getResources(location);
			for (Resource resource : resources) {
				MetadataReader metaReader = metaFactory.getMetadataReader(resource);
				if (resource.isReadable()) {
					String clazzName = metaReader.getClassMetadata().getClassName();
					if (clazzName.contains("$")) {
						// 忽略内部类
						continue;
					}
//					Class<?> clazz = Class.forName(clazzName);
					Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(clazzName);
					if (filter.test(clazz)) {
						result.add(clazz);
					}
				}
			}
		} catch (Exception e) {
			logger.error("", e);
		}

		return result;
	}

	public static void main(String[] args) {
		Set<Class<?>> clazzs = getClasses("com.kingston.mmorpg.framework.util");
		System.err.println(clazzs);

		Set<Class<?>> clazzs2 = listAllSubclasses("com.kingston.mmorpg.game", Message.class);
		System.err.println(clazzs2);
	}

}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jforgame

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

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

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

打赏作者

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

抵扣说明:

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

余额充值