一、背景
想在自己开发的项目上加一个算法工具类用来整合不同的算法,并且要求低耦合符合开闭原则,于是想到了《大话设计模式》里的策略模式,但是书中的策略模式还没有达到完全符合开闭原则,同时我在文章结尾看见说可以运用反射机制来大幅降低代码的耦合度,因此开始在学习如何实现,在学习过程中发现需要写一个方法用来找到指定接口的实现类,因此开启了这趟学习之旅。策略模式改进文章:通过反射机制实现一个完美的策略模式
二、寻求答案的路途
刚开始看到根据指定接口找到其实现类,我一下子就想到的Spring,Spring中有一个现成的方法getBeansOfType、getBeanNamesForType 刚好符合我们的要求,这个方法就是获取指定bean的所有具体实现类,可以直接拿过来用,用法如下:
//applicationContext通过ApplicationAware自动注入
//key位 beanName,value为bean
Map result = applicationContext.getBeansOfType(Interface.class);
//返回 beanName 的String 数组
String[] result = applicationContext.getBeanNamesForType(Interface.class);
但是我更想要自己实现一个自己用,因为Spring的方法会遍历搜索所有的实例来判断找出其实现类,而我个人是打算把算法的接口和具体实现都方在同一个包里,没打算放其他地方,所以完全没有必要循环遍历所有的实例
既然要自己实现就先看看别人怎么写的,最好能直接找到别人实现好的轮子
java最全的获取某个接口或者某个类所有对应的所有实现类和继承类的工具类--反射动态获取、非动态获取、按照路径获取等总结
这篇博客的大致思路是:
(1)获取当前线程的ClassLoader对象
(2)根据ClassLoader获取目录对象,并对目录里的文件进行遍历
(3)过滤出.class为结尾的文件,并加载到Vector中
(4)然后对Vector中的类进行判断是不是指定接口的实现类
(5)返回所有符合的类
这篇文章给出了两种实现方式,但是还是有不足支持,这篇文章只能在自己本地测试运行使用,无法应用线上,项目发到线上代码打包成jar包后,这种方式就无法使用了
第二篇文章:获取全部子类或接口的全部实现
这一篇博客给出了两种方式,并同时给出了jar包的处理方法,用到了JarFile工具类,并且实现了全路径的搜索。
第三篇文章:获取Java接口的所有实现类
这篇文章直接实现了我想要的东西并作了总结,里面的流程图做得非常棒。同时解决了如何通过classLoader获取JarFile对象:
此外classLoader.getResource 获得的 资源目录 是个URL对象,如何转换成JarFile对象:
JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection(); JarFile jarFile = jarURLConnection.getJarFile();
从阅读上面的几篇文章可一得到一个完整的解决思路:
三、实现代码
package utils;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ClassUtils {
/*
* 获取指定接口的所有实现实例
*/
public static List getAllObjectByInterface(Class> c)
throws InstantiationException, IllegalAccessException {
List list = new ArrayList();
List> classes = getAllClassByInterface(c);
for (int i = 0; i < classes.size(); i++) {
list.add(classes.get(i).newInstance());
}
return list;
}
/*
* 获取指定接口的实例的Class对象
*/
public static List> getAllClassByInterface(Class> c) {
// 如果传入的参数不是接口直接结束
if (!c.isInterface()) {
return null;
}
// 获取当前包名
String packageName = c.getPackage().getName();
List> allClass = null;
try {
allClass = getAllClassFromPackage(packageName);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
ArrayList> list = new ArrayList>();
for (int i = 0; i < allClass.size(); i++) {
if (c.isAssignableFrom(allClass.get(i))) {
if (!c.equals(allClass.get(i))) {
list.add(allClass.get(i));
}
}
}
return list;
}
private static List> getAllClassFromPackage(String packageName) throws IOException, ClassNotFoundException{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace(".", "/");
Enumeration enumeration = classLoader.getResources(path);
List classNames = getClassNames(enumeration, packageName);
ArrayList> classes = new ArrayList>();
for (int i = 0; i < classNames.size(); i++) {
classes.add(Class.forName(classNames.get(i)));
}
return classes;
}
public static List getClassNames(Enumeration enumeration, String packageName) {
List classNames = null;
while (enumeration.hasMoreElements()) {
URL url = enumeration.nextElement();
if (url != null) {
String type = url.getProtocol();
if (type.equals("file")) {
System.out.println("type : file");
String fileSearchPath = url.getPath();
if(fileSearchPath.contains("META-INF")) {
System.out.println("continue + " + fileSearchPath);
continue;
}
classNames = getClassNameByFile(fileSearchPath);
} else if (type.equals("jar")) {
try {
System.out.println("type : jar");
JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
classNames = getClassNameByJar(jarFile, packageName);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("type : none");
}
}
}
return classNames;
}
/*
* 获取项目某路径下的所有类
*/
public static List getClassNameByFile(String fileSearchPath) {
List classNames = new ArrayList();
File file = new File(fileSearchPath);
File[] childFiles = file.listFiles();
for(File childFile : childFiles) {
if(childFile.isDirectory()) {
classNames.addAll(getClassNameByFile(childFile.getPath()));
} else {
String childFilePath = childFile.getPath();
if (childFilePath.endsWith(".class")) {
String className = childFilePath.substring(childFilePath.lastIndexOf("\\bin\\") + 1,
childFilePath.length()).replaceAll("\\\\", ".");
className = className.substring(4, className.indexOf(".class"));
classNames.add(className);
}
}
}
return classNames;
}
/*
* 从jar包中获取某路径下的所有类
*/
public static List getClassNameByJar(JarFile jarFile, String packageName) {
List classNames = new ArrayList();
Enumeration entrys = jarFile.entries();
while (entrys.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) entrys.nextElement();
String entryName = jarEntry.getName();
if(entryName.endsWith(".class")) {
String className = entryName.replace("/", ".");
className = className.substring(0, className.indexOf(".class"));
classNames.add(className);
}
}
return classNames;
}
}
四、测试
测试代码github:https://github.com/codeHaoHao/Strategy
测试项目结构如下:
1.先测试在普通的java项目中能不能运行
运行结果非常成功!
2.测试jar包状态下的运行结果
再eclipse中把项目导出为Runnable JAR file
然后在命令框中执行,执行结果如下:
运行结果非常成功!!
3.测试在另一个项目中引入该项目jar包
将刚刚包含ClassUtils的项目打成jar
然后在另一个项目中引用:
运行结果:
运行结果也非常的成功!!!
五、结果
从测试的结果来看代码运行成功,但是这还只是代码的第一代版本,代码本身还有很多可以优化的地方,足够好的优化能让代码用到更多的地方,并且还能提升代码运行的效率。