最近为项目写了一个公式执行功能,其中函数太多,只能写了一个接口,用到哪个函数实现哪个函数.问题来了:怎么知道实现函数接口的类的存在?
想了两个办法:
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;
}