我的開發日志:類路徑掃描、DI 容器與動態代理
前言
我失憶了,完全不記得自己早上干了什么。
日程
早上 10 點左右開始,學了一早上,主要是類路徑掃描相關的調試。
晚上 8 點了,真不能再摸🐟了。
學習記錄
計算機網絡:
1. 子網劃分與子網掩碼
學習內容
省流
- 手搓類路徑掃描器
- 手搓基礎 DI 容器
- 動態代理
1. 手搓類路徑掃描器
1)首先要確定需要掃描的包
String path = packageName.replace('.', '/');
2)然后獲取系統類加載器獲取路徑,并獲取該路徑下的所有資源
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
注意:系統類加載器 (ClassLoader.getSystemClassLoader()
) 的掃描范圍包括所有在 JVM 啟動時通過 -classpath
或 -cp
指定的路徑(包括 Maven/Gradle 依賴)。
3)遍歷所有的資源,通過 resource.getProtocol()
獲取 URL 對象的協議類型,獲取 URL 對象的類文件
while (resources.hasMoreElements()){URL resource = resources.nextElement();if (resource.getProtocol().equals("file")) {classes.addAll(findClasses(new File(resource.getFile()), packageName, classFilter));}
}
->進入 findClasses
方法
4)獲取文件列表,遍歷文件和子目錄,獲取 clazz
對象,并返回 List<Class<?>> classes
列表
File[] files = directory.listFiles();
for (File file : files) {if (file.isDirectory()) {String subPackage = packageName + "." + file.getName();classes.addAll(findClasses(file, subPackage, classFilter));} else if (file.getName().endsWith(".class")) {String className = packageName + '.' +file.getName().substring(0, file.getName().length() - 6); //獲取class全類名Class<?> clazz = Class.forName(className, false, Thread.currentThread().getContextClassLoader()); //根據全類名找到clazz對象(不對類進行初始化)if (classFilter.test(clazz)) { //過濾器檢查classes.add(clazz);}}
}
return classes;
5)提供了一個掃描含有對應注解的類
public static List<Class<?>> scanClassesWithAnnotation(String packageName,Class<? extends java.lang.annotation.Annotation> annotation) {return scanClasses(packageName, clazz -> clazz.isAnnotationPresent(annotation));
}
Class<? extends java.lang.annotation.Annotation>
表示接收的 Class
對象是 java.lang.annotation.Annotation
的任意子類。
2. 手搓基礎 DI 容器
0)用 map 來儲存映射,在創建類對象時進行掃描
// 存儲類定義的映射(類名 -> 類對象)
private final Map<String, Class<?>> classRegistry = new HashMap<>();
// 存儲單例實例的映射(類名 -> 實例)
private final Map<String, Object> singletonInstances = new HashMap<>();
// 正在創建的Bean記錄(用于解決循環依賴)
private final Set<String> beansInCreation = new HashSet<>();
//接口到實現類的映射
private final Map<Class<?>, Class<?>> interfaceToImplementation = new HashMap<>();
// 包掃描路徑
private final String basePackage;public ContainerFactory(String basePackage) {this.basePackage = basePackage;scanComponents();initializeInterfaceLinks();initializeSingletons();
}
1)組件掃描
private void scanComponents() {List<Class<?>> componentClasses = ClassPathScanner.scanClassesWithAnnotation(basePackage, KatComponent.class);for (Class<?> clazz : componentClasses) {register(clazz);}
}
-> 進入 register
方法
2)注冊組件
public void register(Class<?> clazz) {if (clazz.isAnnotationPresent(KatComponent.class)) {String beanName = getBeanName(clazz);classRegistry.put(beanName, clazz);}
}
// 獲取Bean名稱
private String getBeanName(Class<?> clazz) {KatComponent component = clazz.getAnnotation(KatComponent.class);return component.value().isEmpty() ? clazz.getSimpleName() : component.value(); //注解沒有指定Bean名稱時,以類名作為Bean名稱
}
3)對單例 Bean 進行初始化
// 初始化所有單例Bean
private void initializeSingletons() {for (Map.Entry<String, Class<?>> entry : classRegistry.entrySet()) {Class<?> clazz = entry.getValue();if (clazz.isAnnotationPresent(KatSingleton.class)) {getBean(clazz); // 觸發單例初始化}}
}
->進入 getBean
方法
4)獲取 Bean 實例
這里采用了依賴注入接口模式,所以要從接口索引中獲取對應的實現類
// 獲取Bean實例(接口映射)
@SuppressWarnings("unchecked") //忽略泛型警告
public <T> T getBean(Class<T> interfaceType) {//接口模式Class<?> implementationClass = interfaceToImplementation.get(interfaceType); if (implementationClass == null) {throw new RuntimeException("No implementation found for " + interfaceType);}return (T) getBean(getBeanName(implementationClass), implementationClass);
}
//初始化接口索引
private void initializeInterfaceLinks() {for (Class<?> clazz : classRegistry.values()) {for (Class<?> intf : clazz.getInterfaces()) {if (!interfaceToImplementation.containsKey(intf)) {interfaceToImplementation.put(intf, clazz);}}}
}
–>進入實現類 Bean 創建
@SuppressWarnings("unchecked") //忽略泛型警告
public <T> T getBean(String beanName, Class<T> clazz) {// 檢查單例緩存if (singletonInstances.containsKey(beanName)) {return (T) singletonInstances.get(beanName);}// 檢查是否已注冊if (!classRegistry.containsKey(beanName)) {throw new RuntimeException("Bean not registered: " + beanName);}// 檢查循環依賴if (beansInCreation.contains(beanName)) {throw new RuntimeException("Circular dependency detected for bean: " + beanName);}beansInCreation.add(beanName);try {Class<?> targetClass = classRegistry.get(beanName);Object instance = createInstance(targetClass); //創建實例// 如果是單例則緩存if (targetClass.isAnnotationPresent(KatSingleton.class)) {singletonInstances.put(beanName, instance);}return (T) instance;} catch (Exception e) {throw new RuntimeException("Failed to create bean: " + beanName, e);} finally {beansInCreation.remove(beanName);}
}
—>進入 createInstance
方法
5)創建實例
private Object createInstance(Class<?> clazz) throws Exception {// 1. 優先使用@KatAutowired構造器Constructor<?> autowiredCtor = findAutowiredConstructor(clazz);if (autowiredCtor != null) {return createInstanceWithConstructor(autowiredCtor);}// 2. 使用默認無參構造器try {Object instance = clazz.getDeclaredConstructor().newInstance();injectFields(instance);return instance;} catch (NoSuchMethodException e) {throw new RuntimeException("No suitable constructor found for " + clazz.getName());}
}
---->進入 findAutowiredConstructor
方法
// 查找@KatAutowired構造器
private Constructor<?> findAutowiredConstructor(Class<?> clazz) {Constructor<?>[] ctors = clazz.getConstructors();for (Constructor<?> ctor : ctors) {if (ctor.isAnnotationPresent(KatAutowired.class)) {return ctor;}}return null;
}
---->進入 createInstanceWithConstructor
方法
// 使用構造器創建實例
private Object createInstanceWithConstructor(Constructor<?> ctor) throws Exception {Class<?>[] paramTypes = ctor.getParameterTypes(); //獲取參數Object[] args = new Object[paramTypes.length];for (int i = 0; i < paramTypes.length; i++) { //添加參數args[i] = getBean(paramTypes[i]);}Object instance = ctor.newInstance(args); //創建實例injectFields(instance); //注入依賴字段return instance;
}
----->進入 injectFields
方法
// 注入字段依賴
private void injectFields(Object instance) throws IllegalAccessException {Class<?> clazz = instance.getClass();//遍歷目標類的所有字段(包括私有字段)for (Field field : clazz.getDeclaredFields()) {// 檢查字段是否被@KatAutowired注解標注if (field.isAnnotationPresent(KatAutowired.class)) {Object dependency = getBean(field.getType());field.setAccessible(true); //允許訪問私有字段field.set(instance, dependency); //注入目標字段}}
}
目前只是一個非常基礎的版本,處理不了復雜的依賴關系,整體效率也比較低。
明天考慮兼容動態代理,多路徑掃描(通過配置文件加載)。
3. 動態代理
在運行時動態創建代理類和對象,而不是在編譯時靜態定義。它對于依賴注入后的事務實現以及 AOP 非常重要。
1)原理分析
以 InvocationHandler
為例
InvocationHandler
是 Java 動態代理機制中的核心接口,它定義了代理對象方法調用的轉發邏輯。
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}
proxy
:動態生成的代理對象實例method
:被調用的方法對象args
:方法調用時傳入的參數數組
使用示例:
class DebugInvocationHandler implements InvocationHandler {private final Object target;public DebugInvocationHandler(Object target) {this.target = target;} @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 方法調用前邏輯System.out.printf("調用方法: %s,參數: %s%n", method.getName(), Arrays.toString(args));// 調用真實對象的方法Object result = method.invoke(target, args);// 方法調用后邏輯System.out.printf("方法 %s 調用完成,結果: %s%n", method.getName(), result);return result;}
}public static void main(String[] args) {RealSubject real = new RealSubject(); //真實的對象//創建一個代理對象Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class[]{Subject.class},new DebugInvocationHandler(real));//代理對象.method() → InvocationHandler.invoke() → 真實對象.method()proxy.request();
}
結語
大腦已經宕機。