锁定老帖子 主题:查找项目中实现接口的所有类
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-11-15
最后修改:2010-11-15
最近为项目写了一个公式执行功能,其中函数太多,只能写了一个接口,用到哪个函数实现哪个函数.问题来了:怎么知道实现函数接口的类的存在? 想了两个办法: 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; } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-11-15
本人愚昧
找到了又怎么样呢 你是想要动态的找 动态的加载? |
|
返回顶楼 | |
发表时间:2010-11-15
最后修改:2010-11-15
limengchengg 写道 本人愚昧
找到了又怎么样呢 你是想要动态的找 动态的加载? ...动态的找.省去了写配置文件而已. 是这样的场景;我写了接口,但是以后的实现的函数可能不是我写的,更可能和我这个jar包不是放在一起的.这样实现者值需要把实现的东西放在test.formalu.impl包或其子包下,然后导入到项目的classpath下,他就可以在公式里使用他实现的函数了. |
|
返回顶楼 | |
发表时间:2010-11-16
这段代码我之前也写过,但是放到web容器就彻底不行了
究其原因,web容器会起一个自定义的ClassLoader来装载你的WEB-INF下面的东西,这个跟java.class.path一点关系都没有 这个问题困扰过我很长时间,最后结论是根据java的设计,不可能实现这个需求。 当然如果有谁想明白怎么做的话还望指教 |
|
返回顶楼 | |
发表时间:2010-11-16
nuclearg 写道 这段代码我之前也写过,但是放到web容器就彻底不行了
究其原因,web容器会起一个自定义的ClassLoader来装载你的WEB-INF下面的东西,这个跟java.class.path一点关系都没有 这个问题困扰过我很长时间,最后结论是根据java的设计,不可能实现这个需求。 当然如果有谁想明白怎么做的话还望指教 我用的是j2se,不知道web上的表现.web的话,可以尝试查找WEB-INF的吧,我记得有方法可能得到这个路径的.其他的路径就不要管了.貌似也管不着... |
|
返回顶楼 | |
发表时间:2010-11-16
万一一个接口有两个实现怎么办?
|
|
返回顶楼 | |
发表时间:2010-11-16
eclipse 中 ctrl+t 完事
|
|
返回顶楼 | |
发表时间:2010-11-16
楼上,你太打击楼主的动手能力了
|
|
返回顶楼 | |
发表时间:2010-11-16
ywlqi 写道 万一一个接口有两个实现怎么办?
这个就是要找多个实现的. 参考excel中的函数. 如果有同名函数实现,谁后加载算谁的.这个参考项目中同时存在两个不同版本的相同功能jar包的表现. 如tomcat下运行的web程序,在lib下放了个老版本的servlet.jar,会出现一些莫名其妙的错误. |
|
返回顶楼 | |
发表时间:2010-11-16
219 写道 eclipse 中 ctrl+t 完事
...那脱离了eclipse呢?你别说到客户机器上装个eclipse,然后在里面运行项目... |
|
返回顶楼 | |