Class收集器(本章代码:https://gitee.com/kkk3582/MyIOC/tree/v1.0.0)
本章代码对应的Tag 为:v1.0.0
前置说明: IOC的总体流程
- 扫包,获取被目标注解标注的Class
- 如 @Service 、@Configuration等
- 分析上一步得到的Class
- 获取类上注解的相关的值
- 例如,获取@Service(isSinglet = true) 中是否是单例的布尔值
- 获取该类(以及父类)中被 目标注解标注的Field
- 如 @Autowired 、 @Value等
- 获取类上注解的相关的值
- 使用反射,并依据上述获取到的所有Class、注解信息实现 getBean系列方法
- 按BeanId获取
- 按class name获取
- 按某个接口,批量获取
注:以上所有注解,只有名称沿用了spring ioc中的命名。所以,发现有用法与你平时接触的不一致时,请不要惊讶。
Class Collector 的实现
Class Collector 的工作目标:将所有Class找出来。
对,它的工作就是这么简单!只需要做这么点工作就行了。要是一切都这么简单,那该有多好啊!
- 基本思路:使用递归,扫描整个项目下所有的class文件
- 将所有class都搜集起来。(对,你没看错,所有class都搜集起来)
基本思路以及理清楚了,接下来看代码了。
读者:啊,等等,你刚才不是说只是收集被 @Service这些注解标记的class么?
我:啊对对对,目标是只收集特定的class,但当前阶段,我们不用管这么多,一股脑儿都收集起来就对了。日后会再继续处理这些class的。(可能时下一章,也可能是下下章,我也不确定)
首先,我们创建一个类ClassCollector.java,并定义了set<Class<?>> collecte(String path)方法,等待我们去实现它。
package zm.ioc.classCollector;
import org.apache.log4j.Logger;
public class ClassCollector {
private static final Logger log = Logger.getLogger(ClassCollector.class);
public static final ClassCollector CLASS_COLLECTOR = new ClassCollector();
/**
* 使用单例模式
*/
private ClassCollector(){}
/**
* Collecte all classes under the path
* @param path
* @return
*/
public Set<Class<?>> collecte(String path){
log.debug("Load class start.(Path is " + path + ")");
return null;
}
}
第二步,我们需要一个方法递归的遍历指定目录下的所有文件,并找出所有class文件.
前置知识:
java.io.File类能帮我们完成这项任务:
- file.listFiles()方法:能列出当前目录下的所有文件(包括子目录)
- file.isDirectory()方法:能判断当前file是不是目录
- file.getName()方法:获取文件名
所以,我们依靠强大的java.io.File,实现了以下方法
/**
* 递归收集class
* @param clazzes 用于收集class的一个容器
* @param path 当前处理的绝对路径,格式为 /xxx/xxx/xxx
* @param classPath 与当前目录同步的java package路径 zm.ioc.xxx
*/
private void doCollecte(Set<Class<?>> clazzes, String path, String classPath){
File currentFolder = new File(path);//获取当前路径的File对象
File[] files = currentFolder.listFiles();// 列出当前目录下所有文件(包括子目录)
if(files == null || files.length == 0){
return;
}
// 开始遍历
for(File file : files){
String fileName = file.getName();
if(file.isDirectory()){
// 遍历到一个目录时,需要再当前目录的基础上,
// 拼接上该子目录后,递归再调用本方法
String subPath = path + "/" + fileName;//拼接上子目录的名称,获取到子目录的完整路径
String subClassPath = classPath + fileName + ".";//同理,获取到子目录对于的java包路径
this.doCollecte(clazzes, subPath, subClassPath);//递归调用
}
//当遍历到的不是一个目录(也就是一个文件)时
if( this.ifClassFile(file)){
// 当文件时class文件时(其实就是判断文件后缀是不是 .class)
// 将class文件名去掉后缀并 拼接再当前的java包路径后面
// 例如当前包路径为 zm.ioc.test
// 当前class文件名为 Test.class
// 可知:class的全名为 zm.ioc.test.Test
// 此时,就可以使用 Class.forName("zm.ioc.test.Test")获取到class对象了。
String className = classPath + fileName.replace(".class", "");
Class<?> clazz = this.loadClass(className);
// 不容易啊,经历了这么多磨难,终于把我们要的东西(class对象)收集到了
// 找个对象真不容易!!!!
clazzes.add(clazz);
}
}
// 为了doCollecte方法中的抽象层次而提取的一个方法
// 用来判断是否时一个class文件
private boolean ifClassFile(File file){
return file != null && file.isFile() && file.getName().endsWith(".class");
}
// 为了doCollecte方法中的抽象层次,并把难看的try-catch藏起来,而提取的一个方法
// 加载一个class对象
private Class<?> loadClass(String className){
try{
return Class.forName(className);
}catch(Exception e){
log.error("Load class error!",e);
}
return null;
}
}
第三步,测试一下
public static void main(String[] args) {
ClassCollector classCollector = ClassCollector.CLASS_COLLECTOR;
//获取class文件所在的目录
String rootPath = System.getProperty("user.dir") + "/target/classes/";
log.info("Class collect start ! Root path is " + rootPath);
Set<Class<?>> clazzes = classCollector.collecte(rootPath);
// 打印出来检查下,看看结果是否正确
clazzes.stream().forEach((c)->log.info(c));
}
这是再我电脑的执行结果,获取到了两个class对象,分别是:zm.ioc.demo.bean.BeanA, zm.ioc.classCollector.ClassColletor