mini-ssm Lab1 - 包扫描
在第一个 Lab 中,我们将创建项目并实现包扫描功能。包扫描是 Spring 框架中用于自动发现和注册类的重要特性,我们通过实现一个简单的版本,了解这一核心机制的底层原理。
项目结构
我们先看一下项目的整体结构:
-
项目结构
-
Lab1 结构
代码说明
在 Lab1 中,主要包含以下几个类和功能模块:
- ClassUtil:实现包扫描逻辑并加载指定包下的所有
Class对象。 - Assert:从 Spring 框架中复制的类,用于断言和错误检查。
- StringUtil:负责一些字符串的处理,例如路径格式转换等。
实现包扫描
包扫描的目的是遍历项目中指定包路径下的所有类,并将其加载为 Class 对象。下面的代码展示了包扫描的核心实现:
public static Set<Class<?>> getClassSet(String packageName){
Set<Class<?>> classSet = new HashSet<>();
try {
// 获取包名下的资源 URL 枚举
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(POINT, SPLIT));
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url != null) {
String protocol = url.getProtocol();
if (FILE.equals(protocol)) {
// 处理文件协议的路径
String packagePath = url.getPath().replaceAll(SPACE, " ");
addClass(classSet, packagePath, packageName);
} else if ("jar".equals(protocol)) {
// 处理 JAR 包中的类文件
JarURLConnection jarUrlConnection = (JarURLConnection)url.openConnection();
if (jarUrlConnection != null) {
JarFile jarFile = jarUrlConnection.getJarFile();
if (jarFile != null) {
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String jarEntryName = jarEntry.getName();
if (jarEntryName.endsWith(CLASS_SUFFIX)) {
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(POINT)).replaceAll(SPLIT,POINT);
doAddClass(classSet, className);
}
}
}
}
}
}
}
} catch (Exception e) {
log.warn("get class set failure", e);
throw new RuntimeException(e);
}
return classSet;
}
代码解析
-
URL 资源获取:首先,通过类加载器获取指定包路径下的所有资源 URL,将它们存储在
Enumeration中。 -
协议判断:根据 URL 的协议(如
file或jar),处理不同类型的资源路径。-
File 协议:对于文件协议,获取包路径并替换特殊字符,然后调用
addClass()方法将扫描到的类加入集合。 -
Jar 协议:对于 JAR 包,利用
JarURLConnection连接 JAR 文件并遍历其中的条目 (JarEntry),找到以.class结尾的文件,并加载其类名。
-
-
异常处理:在资源扫描过程中,若遇到异常,记录警告日志并抛出运行时异常。
这个实现较为简单,逻辑直接且易于理解,主要涉及文件路径和类名的处理。由于包扫描功能相对单一,代码实现上较为固定化。
小结
这个包扫描模块是 mini-SSM 的核心基础之一。通过简单地实现,我们可以深入理解包扫描的基本流程以及类加载的原理。对包扫描机制感兴趣的同学可以参考项目中的注释,探索更深入的实现细节。