在编写IoC容器之前,我们首先要实现@ComponentScan
,即解决“在指定包下扫描所有Class”的问题。
Java的ClassLoader机制可以在指定的Classpath中根据类名加载指定的Class,但遗憾的是,给出一个包名,例如,org.example
,它并不能获取到该包下的所有Class,也不能获取子包。要在Classpath中扫描指定包名下的所有Class,包括子包,实际上是在Classpath中搜索所有文件,找出文件名匹配的.class
文件。例如,Classpath中搜索的文件org/example/Hello.class
就符合包名org.example
,我们需要根据文件路径把它变为org.example.Hello
,就相当于获得了类名。因此,搜索Class变成了搜索文件。
我们先定义一个Resource
类型表示文件:
public record Resource(String path, String name) {
}
再仿造Spring提供一个ResourceResolver
,定义scan()
方法来获取扫描到的Resource
:
public class ResourceResolver {
String basePackage;
public ResourceResolver(String basePackage) {
this.basePackage = basePackage;
}
public <R> List<R> scan(Function<Resource, R> mapper) {
...
}
}
这样,我们就可以扫描指定包下的所有文件。有的同学会问,我们的目的是扫描.class
文件,如何过滤出Class?
注意到scan()
方法传入了一个映射函数,我们传入Resource
到Class Name的映射,就可以扫描出Class Name:
// 定义一个扫描器:
ResourceResolver rr = new ResourceResolver("org.example");
List<String> classList = rr.scan(res -> {
String name = res.name(); // 资源名称"org/example/Hello.class"
if (name.endsWith(".class")) { // 如果以.class结尾
// 把"org/example/Hello.class"变为"org.example.Hello":
return name.substring(0, name.length() - 6).replace("/", ".").replace("\\", ".");
}
// 否则返回null表示不是有效的Class Name:
return null;
});
这样,ResourceResolver
只负责扫描并列出所有文件,由客户端决定是找出.class
文件,还是找出.properties
文件。
在ClassPath中扫描文件的代码是固定模式,可以在网上搜索获得,例如StackOverflow的这个回答。这里要注意的一点是,Java支持在jar包中搜索文件,所以,不但需要在普通目录中搜索,也需要在Classpath中列出的jar包中搜索,核心代码如下:
// 通过ClassLoader获取URL列表:
Enumeration<URL> en = getContextClassLoader().getResources("org/example");
while (en.hasMoreElements()) {
URL url = en.nextElement();
URI uri = url.toURI();
if (uri.toString().startsWith("file:")) {
// 在目录中搜索
}
if (uri.toString().startsWith("jar:")) {
// 在Jar包中搜索
}
}
几个要点:
Thread.getContextClassLoader()
获取,如果获取不到,再从当前Class获取,因为Web应用的ClassLoader不是JVM提供的基于Classpath的ClassLoader,而是Servlet容器提供的ClassLoader,它不在默认的Classpath搜索,而是在/WEB-INF/classes
目录和/WEB-INF/lib
的所有jar包搜索,从Thread.getContextClassLoader()
可以获取到Servlet容器专属的ClassLoader;\
,后者是/
,需要正确处理;abc/xyz
,也可能是abc/xyz/
,需要注意处理末尾的/
。这样我们就完成了能扫描指定包以及子包下所有文件的ResourceResolver
。