`
i2534
  • 浏览: 179406 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

查找项目中实现接口的所有类

    博客分类:
  • util
阅读更多

最近为项目写了一个公式执行功能,其中函数太多,只能写了一个接口,用到哪个函数实现哪个函数.问题来了:怎么知道实现函数接口的类的存在?

想了两个办法:

1:写配置文件,实现一个类,在配置文件里添加一条实现类的路径.但是此方法限制了灵活性.

2:在函数执行前,自动搜索项目path下所有实现了接口的类.

方法1很简单,不论是xml还是properties都可以.这里就不用多说了.

方法2在网上找了很多资料,都说使用ClassLoader下的getResource(s)方式,但是经过我测试,项目没打包时可以正常工作,一旦打成jar包(我用eclipse3.4的导出成可执行jar),就不行了.

最后参考这些原理,想了下,自己实现了一个.

首先得到项目的path,使用System.getProperty("java.class.path"),然后逐个查找path里出现的文件,如果是文件夹,遍历文件夹,加载类,如果是jar或者zip,遍历此压缩文件查找类.然后判断类是否实现了接口即可.

查找的工具类叫做FunctionHelper

 

/**
 * 函数的接口.<br>
 * 所有函数都应该实现此接口或者继承此接口的抽象实现:{@link AbstractFunction}<br>
 * 所有实现此接口的函数,需要注册才能被加载和使用.<br>
 * 注册的方式:
 * <ul>
 * <li>在functions.xml中进行注册(仅限本包中)</li>
 * <li>放置在本接口所在的包的子包impl包下,此时,类名就被认为是函数名.若本接口位于test.formula,
 * 则放置实现于test.formula.impl下可被自动进行注册</li>
 * </ul>
 * 
 */
public interface IFunction {}

 判断类是否实现了接口

	/**
	 * 判断类是否函数类.<br>
	 * 首先,类不能是抽象的,其次,类必须实现函数接口
	 * 
	 * @param c
	 *            类
	 * @return 是否是函数类
	 */
	public static boolean isFunction(Class<?> c) {
		if (c == null) {
			return false;
		}
		if (c.isInterface()) {
			return false;
		}
		if (Modifier.isAbstract(c.getModifiers())) {
			return false;// 抽象
		}
		// Class<?>[] interfaces = c.getInterfaces();
		// if (interfaces == null || interfaces.length == 0) {
		// return false;
		// }
		// for (Class<?> i : interfaces) {
		// if (i == IFunction.class) {
		// return true;
		// }
		// }
		return IFunction.class.isAssignableFrom(c);
	}

 

查找path下所有的文件

	/**
	 * 获取项目的path下所有的文件夹和文件
	 * 
	 * @return 文件列表
	 */
	private static List<File> listPaths() {
		List<File> files = new ArrayList<File>();
		String jars = System.getProperty("java.class.path");
		if (jars == null) {
			System.err.println("java.class.path is null!");
			return files;
		}
		URL root = FunctionHelper.class.getClassLoader().getResource("");
		if (root == null) {
			System.err.println("path root is null!");
			return files;
		}
		String path = null;
		try {
			path = URLDecoder.decode(root.getFile(), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return files;
		}
		File dir = new File(path);
		String[] array = (jars).split(";");
		if (array != null) {
			for (String s : array) {
				if (s == null) {
					continue;
				}
				File f = new File(s);
				if (f.exists()) {
					files.add(f);
				} else {//有些jar就在系统目录下,省略了路径,要加上
					File jar = new File(dir, s);
					if (jar.exists()) {
						files.add(jar);
					}
				}
			}
		}
		return files;
	}

 

开始遍历上面的文件:

	/**
	 * 获取包下所有的函数实现类
	 * 
	 * @param pkg
	 *            包名,此处只是为了限定,防止漫无目的的查找.不用设置也可以,就要每找到一个类就要加载一次判断了
	 * @return 类列表
	 */
	private static List<Class<?>> getClasses(String pkg) {
		List<Class<?>> list = new ArrayList<Class<?>>();
		for (File f : FunctionHelper.listPaths()) {
			// 如果是以文件的形式保存在服务器上
			if (f.isDirectory()) {
				// 获取包的物理路径
				String path = pkg.replace('.', File.separatorChar);
				FunctionHelper.dirWalker(path, f, list);
			} else {//尝试是否是jar文件
				// 获取jar
				JarFile jar = null;
				try {
					jar = new JarFile(f);
				} catch (IOException e) {
					// 有可能不是一个jar
				}
				if (jar == null) {
					continue;
				}
				String path = pkg.replace('.', '/');
				// 从此jar包 得到一个枚举类
				Enumeration<JarEntry> entries = jar.entries();
				// 同样的进行循环迭代
				while (entries.hasMoreElements()) {
					// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
					JarEntry entry = entries.nextElement();
					String name = entry.getName();
					// 如果是以/开头的
					if (name.charAt(0) == '/') {
						// 获取后面的字符串
						name = name.substring(1);
					}
					// 如果前半部分和定义的包名相同
					if (name.contains(path)) {
						if (name.endsWith(".class") && !entry.isDirectory()) {
							name = name.replace("/", ".").substring(0,
									name.lastIndexOf("."));
							try {
								Class<?> c = Class.forName(name);
								if (FunctionHelper.isFunction(c)) {
									list.add(c);
								}
							} catch (Exception e) {
								// 找不到无所谓了
							}
						}
					}
				}
			}
		}
		return list;
	}

 

dirWalker,文件夹遍历

	/**
	 * 遍历文件夹下所有的类
	 * 
	 * @param path
	 *            包路径
	 * @param file
	 *            文件
	 * @param list
	 *            保存类列表
	 */
	private static void dirWalker(String path, File file, List<Class<?>> list) {
		if (file.exists()) {
			if (file.isDirectory()) {
				for (File f : file.listFiles()) {
					FunctionHelper.dirWalker(path, f, list);
				}
			} else {
				Class<?> c = FunctionHelper.loadClassByFile(path, file);
				if (c != null) {
					list.add(c);
				}
			}
		}
	}

 从文件加载类

	/**
	 * 从文件加载类
	 * 
	 * @param pkg
	 *            包路径
	 * @param file
	 *            文件
	 * @return 类或者null
	 */
	private static Class<?> loadClassByFile(String pkg, File file) {
		if (!file.isFile()) {
			return null;
		}
		String name = file.getName();
		if (name.endsWith(".class")) {
			String ap = file.getAbsolutePath();
			if (!ap.contains(pkg)) {
				return null;
			}
			name = ap.substring(ap.indexOf(pkg) + pkg.length());
			if (name.startsWith(File.separator)) {
				name = name.substring(1);
			}
			String path = (pkg + "." + name.substring(0, name.lastIndexOf(".")))
					.replace(File.separatorChar, '.');
			try {
				Class<?> c = Class.forName(path);
				if (FunctionHelper.isFunction(c)) {
					return c;
				}
			} catch (ClassNotFoundException e) {
				// do nothing
			}
		}
		return null;
	}
分享到:
评论
19 楼 i2534 2010-11-29  
taolei0628 写道
何谓灵活性?通过配置来定制系统不算灵活吗?自动搜索实现类可以做到个性化定制吗?
几个简单的问题:

    如果你想替换其中某个函数的实现,怎么做?
    如果函数是跟外部资源相关的,那么不同的运行环境需要选择不同的实现,怎么做?

我的建议是:配置必需有,自动搜索、加载的方法可以做,但只限于辅助配置管理的工具。


配置文件自然有的,我自己实现的函数就在配置文件里写的.这些是为不能写入配置文件的人做的.譬如,我的jar经过了签名,别人就不能动我的jar了,也就不能再改我的配置文件里,所以要自动加载.至于替换函数,别人可以写个同名函数.因为是先加载我的,后加载他的,而我定的规则就是后加载的覆盖前加载的,这样就实现了替换.
18 楼 taolei0628 2010-11-27  
何谓灵活性?通过配置来定制系统不算灵活吗?自动搜索实现类可以做到个性化定制吗?
几个简单的问题:

    如果你想替换其中某个函数的实现,怎么做?
    如果函数是跟外部资源相关的,那么不同的运行环境需要选择不同的实现,怎么做?

我的建议是:配置必需有,自动搜索、加载的方法可以做,但只限于辅助配置管理的工具。

17 楼 walnut_tom 2010-11-26  
你可以参考一下 java.lang.instrument 包的内容。
16 楼 snailke 2010-11-21  
一般做项目的话  借口放在一个叫interface包中,实现类放在一个叫impl的包中,这样保证方便
15 楼 liuwei_blog 2010-11-19  
设计问题,应该给使用者提供一个方法注入实现类对象,缺省使用你自己的实现类或什么都不做的实现类。
14 楼 i2534 2010-11-17  
undancer 写道
将配置文件写在/META-INF/services下,并使用java.util.ServiceLoader去加载。即使以后所有的实现不在同一个jar里也OK,比如java.sql.Driver等接口都是如此实现的。


感谢,又学到一点API.
这位童鞋的话真是精辟啊,我找了google的第一页,做法都一样,很多还是互相转的.都没人试试真的能不能行.....
没办法,只能自己想想解决了.
13 楼 i2534 2010-11-17  
引用
哈哈,/META-INF/services来说也是一个配置, Lz要的是最灵活的方法

以前也有过类似的想法,自动扫描一个指定接口的实现或者指定后缀的名字,扫到class后自动注册到一个Responsiory中。

这样会存在一些问题,比如你要扫描的class的package是一个父级的,比如com.xxxx。在公司这么多lib库下,这时候你遍历整个classpath真的要死人了

这也是没办法的.为了灵活就必须牺牲一点效率.
反正这东西就在启动时扫描一次,应该可以接受.加载后就缓存吧.
12 楼 agapple 2010-11-17  
undancer 写道
将配置文件写在/META-INF/services下,并使用java.util.ServiceLoader去加载。即使以后所有的实现不在同一个jar里也OK,比如java.sql.Driver等接口都是如此实现的。

查很多资料,不如多读一下API和常用jar的源码。国内资料很令人失望。

演示如下

public interface IFunction {
	//某个接口...
}



public class IFunctionImpl implements IFunction {
	//该接口的实现...
}



public class FooImpl implements IFunction {
	// 该接口的其他实现...
}


#filename:META-INF/services/IFunction
IFunctionImpl
FooImpl

import java.util.Iterator;
import java.util.ServiceLoader;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
 		// 配置文件写在META-INF/services/下,文件名为该接口名,没有文件后缀,一行一个实现,#表示该行为注释。
		iterator(java.sql.Driver.class);
		/*
		 * sun.jdbc.odbc.JdbcOdbcDriver@83cc67 
		 * org.hsqldb.jdbc.JDBCDriver@e09713
		 */
		iterator(IFunction.class);
		/*
		 * IFunctionImpl@47b480 
		 * FooImpl@10d448
		 */
	}

	public static <T> void iterator(Class<T> service) {
		Iterator<T> iterator = ServiceLoader.load(service).iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
	}

}




哈哈,/META-INF/services来说也是一个配置, Lz要的是最灵活的方法

以前也有过类似的想法,自动扫描一个指定接口的实现或者指定后缀的名字,扫到class后自动注册到一个Responsiory中。

这样会存在一些问题,比如你要扫描的class的package是一个父级的,比如com.xxxx。在公司这么多lib库下,这时候你遍历整个classpath真的要死人了
11 楼 undancer 2010-11-17  
将配置文件写在/META-INF/services下,并使用java.util.ServiceLoader去加载。即使以后所有的实现不在同一个jar里也OK,比如java.sql.Driver等接口都是如此实现的。

查很多资料,不如多读一下API和常用jar的源码。国内资料很令人失望。

演示如下

public interface IFunction {
	//某个接口...
}



public class IFunctionImpl implements IFunction {
	//该接口的实现...
}



public class FooImpl implements IFunction {
	// 该接口的其他实现...
}


#filename:META-INF/services/IFunction
IFunctionImpl
FooImpl

import java.util.Iterator;
import java.util.ServiceLoader;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
 		// 配置文件写在META-INF/services/下,文件名为该接口名,没有文件后缀,一行一个实现,#表示该行为注释。
		iterator(java.sql.Driver.class);
		/*
		 * sun.jdbc.odbc.JdbcOdbcDriver@83cc67 
		 * org.hsqldb.jdbc.JDBCDriver@e09713
		 */
		iterator(IFunction.class);
		/*
		 * IFunctionImpl@47b480 
		 * FooImpl@10d448
		 */
	}

	public static <T> void iterator(Class<T> service) {
		Iterator<T> iterator = ServiceLoader.load(service).iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
	}

}

10 楼 i2534 2010-11-16  
liquidthinker 写道
楼上,你太打击楼主的动手能力了

无所谓打击不打击了,习惯了.
9 楼 i2534 2010-11-16  
219 写道
eclipse 中 ctrl+t 完事

...那脱离了eclipse呢?你别说到客户机器上装个eclipse,然后在里面运行项目...
8 楼 i2534 2010-11-16  
ywlqi 写道
万一一个接口有两个实现怎么办?

这个就是要找多个实现的.
参考excel中的函数.
如果有同名函数实现,谁后加载算谁的.这个参考项目中同时存在两个不同版本的相同功能jar包的表现.
如tomcat下运行的web程序,在lib下放了个老版本的servlet.jar,会出现一些莫名其妙的错误.
7 楼 liquidthinker 2010-11-16  
楼上,你太打击楼主的动手能力了
6 楼 219 2010-11-16  
eclipse 中 ctrl+t 完事
5 楼 ywlqi 2010-11-16  
万一一个接口有两个实现怎么办?
4 楼 i2534 2010-11-16  
nuclearg 写道
这段代码我之前也写过,但是放到web容器就彻底不行了

究其原因,web容器会起一个自定义的ClassLoader来装载你的WEB-INF下面的东西,这个跟java.class.path一点关系都没有

这个问题困扰过我很长时间,最后结论是根据java的设计,不可能实现这个需求。

当然如果有谁想明白怎么做的话还望指教

我用的是j2se,不知道web上的表现.web的话,可以尝试查找WEB-INF的吧,我记得有方法可能得到这个路径的.其他的路径就不要管了.貌似也管不着...
3 楼 nuclearg 2010-11-16  
这段代码我之前也写过,但是放到web容器就彻底不行了

究其原因,web容器会起一个自定义的ClassLoader来装载你的WEB-INF下面的东西,这个跟java.class.path一点关系都没有

这个问题困扰过我很长时间,最后结论是根据java的设计,不可能实现这个需求。

当然如果有谁想明白怎么做的话还望指教
2 楼 i2534 2010-11-15  
limengchengg 写道
本人愚昧
找到了又怎么样呢
你是想要动态的找  动态的加载?

...动态的找.省去了写配置文件而已.
是这样的场景;我写了接口,但是以后的实现的函数可能不是我写的,更可能和我这个jar包不是放在一起的.这样实现者值需要把实现的东西放在test.formalu.impl包或其子包下,然后导入到项目的classpath下,他就可以在公式里使用他实现的函数了.
1 楼 limengchengg 2010-11-15  
本人愚昧
找到了又怎么样呢
你是想要动态的找  动态的加载?

相关推荐

    基于Java抽象类和接口实现疯狂动物城

    本项目主要包括项目开发环境搭建、不同功能的类的设计、抽象类的设计、接口的设计、及其继承抽象类重写和接口实现类等具体功能的实现。 ●工程项目搭建与游戏初始化功能实现(2学时) ; ●动物城成员列表与动物信息...

    Android项目ListView 实现点击侧边A-Z快速查找.rar

    本项目为Android项目中的一个功能模块,实现了在ListView中点击侧边字母导航栏进行A-Z的快速查找。此功能模块具有较高的实用性和可扩展性,可以满足用户在大量数据中快速定位的需求。 该模块的核心功能是通过监听...

    原创--java 可配置式 批量 查找 替换工具-MYFinder源代码

    做项目时,要在原有项目的基础增加国际化版本,查找和替换中文成了必做的工作,为了加快工作速度,所有的重复性工作都交给程序来处理,自己只做核心工作,经过自己的编码和实践应用,写了一套配合自己工作的程序,...

    基于C++实现的职工管理系统源码+设计报告+程序说明文档.zip

    5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源介绍】 基于C++实现的职工管理系统源码+设计报告+程序说明文档.zip 1、管理系统需求 2、创建项目 2.1 创建项目 2.2 添加文件 3、创建管理类 3.1...

    Java典型模块

    29.3.2 实现数据连接操作(DAO)的实现类 29.3.3 实现数据连接操作(DAO)的代理类 29.3.4 实现数据连接操作(DAO)的工厂类 29.4 人员信息管理项目——服务层和表示层 29.4.1 人员信息管理项目的服务层 29.4.2 人员...

    明日科技C#开发入门及项目实战

    实例193 获取网络中所有工作组名称 实例194 列出指定工作组中的所有计算机名 实例195 监测当前网络连接状态 实例196 使用udp协议设计聊天室 第18章 注册表技术 实例197 禁止运行注册表 实例198 使应用程序开机自动...

    微信接口库:PHPToQyWeixin PHP调用企业微信API接口底层类库.zip

    本项目是基于个人喜好及实际开发需求进行编写和设计的,主要功能是完成了对微信企业号API的封装,目前还在不断完善中,通过调用本项目的类库和函数,可以实现调用微信企业号API,降低了开发成本,开发者可以不用再...

    微信接口库:PHPToQyWeixin PHP调用企业微信API接口底层类库

    本项目是基于个人喜好及实际开发需求进行编写和设计的,主要功能是完成了对微信企业号API的封装,目前还在不断完善中,通过调用本项目的类库和函数,可以实现调用微信企业号API,降低了开发成本,开发者可以不用再...

    Spring连接图存数据库Neo4j实现增删改查

    本项目基于Spring-data-neo4j,整合图存数据库Noe4j, 实现增删改查的功能。主要功能包括: 1.基于spring-data-neo4j 3.2.0通过REST远程连接Neo4j服务器,并非嵌入式连接; 2.创建接口用于创建一个简单的图存数据库...

    基于C++实现的自助导游及信息服务系统【100011214】

    模块功能说明 数据结构模块:该模块实现了基本的数据结构,为上层模块提供...服务接口模块:该模块实现了程序的主要功能,在数据结构模块和数据模型模块的基础上,对于程序的所有功能进行了具体实现,利用辅助工具模块

    达内Java培训项目(当当网/通用电子商务系统)

    商品展示模块实现了依照分类进行查找显示商品功能,并实现了组建化分页。 购物车模块实现了添加购物、修改购物信息、删除货品、恢复删除的功能。 订单模块实现了用户地址添加、回填,生成订单,购物车清空等功能。 ...

    C++课程作业基于跳表实现的轻型键值型数据库源码+项目说明.zip

    &gt; - 本项目使用了类模板进行编程;实现了对数据的增删改查,以及数据落盘和文件加载数据等功能;对数据库性能进行了简单的压力测试,采用了随机写读的方法,在不同数据规模下获得了每秒可处理请求数(QPS)指标。 &gt; ...

    精通Windows.API-函数、接口、编程实例.pdf

    4.3.5 查找文件、遍历指定目录下的文件和子目录 100 4.3.6 递归遍历目录树 103 4.3.7 获取、设置文件属性和时间 105 4.4 内存映射文件 110 4.4.1 使用Mapping File提高文件读写的效率 110 4.4.2 通过...

    JAVA程序开发大全---上半部分

    6.3.5 创建Web项目中的Java类文件 92 6.3.6 发布和运行Web项目 94 6.4 Web应用实例:登录系统 96 6.5 本章小结 102 第7章 数据库应用程序的开发及应用 103 7.1 MySQL数据库的安装与配置 103 7.2 MyEclipse中的...

    基于python+opencv实现的欢乐斗地主记牌器系统源码+项目说明(数字图像处理课程设计).zip

    基于python+opencv实现的欢乐斗地主记牌器系统源码+项目说明(数字图像处理课程设计).zip 把图像分成上中下三块,分别是地主牌、对手出牌、自己手牌,计算出大致区域(直接切片选取) 读取图片,转为SHV提取白色区域...

    Java项目源码之文本编辑器的实现.rar

    文本处理:使用Java提供的字符串处理功能,实现文本编辑和查找替换功能。 语法高亮:通过正则表达式匹配文本中的关键词,并设置相应的文本颜色实现语法高亮显示。 扩展性与定制性: 插件支持:设计插件接口,允许...

    基于WEB平台的阅读APP设计与实现毕业设计课程设计.zip

    项目特色 ... 用户在找书,阅读的过程中,应用会自动记录用户的查找、阅读历史,并在用户再次进入应用时智能提醒用户是否回到上次阅读的状态。 完美的实现了多平台同步使用。 该应用实现了电脑、手机、平

    java 面试题 总结

    接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的...

Global site tag (gtag.js) - Google Analytics