- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
- 有的时候我们需要获取当前项目下所有实现某接口的所有实现类,这个时候如果我们是spring项目的还是有方式来实现的,但是纯Java又该如何实现呢?下面我们来看看如何实现。
- 首先我们澄清下是获取当前项目下的实现类,其它项目不可以哦。
前置准备
- 首先我们得实现如何获取当前项目下的所有的类,只有获取到所有的类然后逐一比较是否是接口的实现类就可以了。
- 这里就涉及到我们包下是文件还是Jar包,如果是Jar我们是否需要读取jar里的文件呢?
public static List<Class<?>> getClasses(String packageName){
//第一个class类的集合
List<Class<?>> classes = new ArrayList<Class<?>>();
//是否循环迭代
boolean recursive = true;
//获取包的名字 并进行替换
String packageDirName = packageName.replace('.', '/');
//定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
//循环迭代下去
while (dirs.hasMoreElements()){
//获取下一个元素
URL url = dirs.nextElement();
//得到协议的名称
String protocol = url.getProtocol();
//如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
//获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
//以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)){
//如果是jar包文件
//定义一个JarFile
JarFile jar;
try {
//获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
//从此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.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
//如果以"/"结尾 是一个包
if (idx != -1) {
//获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
//如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive){
//如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
//去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
//添加到classes
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
开始比对
- 然后我们再开始根据接口获取当前所在包路径。在根据包路径获取所有类,进行比对确认
public static List<Class> getAllClassByInterface(Class c) {
List<Class> returnClassList = null;
if(c.isInterface()) {
// 获取当前的包名
String packageName = c.getPackage().getName();
// 获取当前包下以及子包下所以的类
List<Class<?>> allClass = getClasses(packageName);
if(allClass != null) {
returnClassList = new ArrayList<Class>();
for(Class classes : allClass) {
// 判断是否是同一个接口
if(c.isAssignableFrom(classes)) {
// 本身不加入进去
if(!c.equals(classes)) {
returnClassList.add(classes);
}
}
}
}
}
return returnClassList;
}
- 最终就可以获取到所有的实现类了。
总结
- 其实想法很简单。但是实现起来确实不容易的,实现过程中涉及到jar内部如何实现卡了很久。
- 这里顺带说下在spring中是如何操作的呢?我们可以在实现类上添加我们自定义注解,然后再上下文中可以根据注解方式获取
Map<String, Object> beansWithAnnotation = ApplicationContextUtil.getApplicationContext().getBeansWithAnnotation(SuperDirectionHandler.class);
\