学习记录:DAY21

我的开发日志:类路径扫描、DI 容器与动态代理

前言


我失忆了,完全不记得自己早上干了什么。


日程


早上 10 点左右开始,学了一早上,主要是类路径扫描相关的调试。
晚上 8 点了,真不能再摸🐟了。


学习记录


计算机网络:
1. 子网划分与子网掩码


学习内容


省流

  1. 手搓类路径扫描器
  2. 手搓基础 DI 容器
  3. 动态代理

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();
}

结语


大脑已经宕机。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Agent】MCP协议 | 用高德MCP Server制作旅游攻略

note MCP (Model Context Protocol) 代表了 AI 与外部工具和数据交互的标准建立。MCP 的本质&#xff1a;它是一个统一的协议标准&#xff0c;使 AI 模型能够以一致的方式连接各种数据源和工具&#xff0c;类似于 AI 世界的"USB-C"接口。 它能够在 LLM/AI Agent 与外…

使用 Spring Data Redis 实现 Redis 数据存储详解

使用 Spring Data Redis 实现 Redis 数据存储详解 Spring Data Redis 是 Spring 生态中操作 Redis 的核心模块&#xff0c;它封装了 Redis 客户端的底层细节&#xff08;如 Jedis 或 Lettuce&#xff09;&#xff0c;提供了统一的 API 来操作 Redis 的数据结构。以下是详细实现…

Qt5与现代OpenGL学习(四)X轴方向旋转60度

把上面两张图像放到D盘1文件夹内&#xff1a; shader.h #ifndef SHADER_H #define SHADER_H#include <QDebug> #include <QOpenGLShader> #include <QOpenGLShaderProgram> #include <QString>class Shader { public:Shader(const QString& verte…

【Machine Learning Q and AI 读书笔记】- 02 自监督学习

Machine Learning Q and AI 中文译名 大模型技术30讲&#xff0c;主要总结了大模型相关的技术要点&#xff0c;结合学术和工程化&#xff0c;对LLM从业者来说&#xff0c;是一份非常好的学习实践技术地图. 本文是Machine Learning Q and AI 读书笔记的第2篇&#xff0c;对应原…

using var connection = connectionFactory.CreateConnection(); using var 是什么意思

在 .NET 中&#xff0c;​​垃圾回收&#xff08;Garbage Collection, GC&#xff09;​​ 确实是自动管理内存的机制&#xff0c;但它 ​​仅适用于托管资源&#xff08;Managed Resources&#xff09;​​&#xff08;如类实例、数组等&#xff09;。然而&#xff0c;对于 ​…

Multicore-TSNE

文章目录 TSNE使用scikit-learn库使用Multicore-TSNE库安装方法基本使用方法采用不同的距离度量 其他资料 TSNE t-Distributed Stochastic Neighbor Embedding (t-SNE) 是一种高维数据的降维方法&#xff0c;由Laurens van der Maaten和Geoffrey Hinton于2008年提出&#xff0…

SI5338-EVB Usage Guide(LVPECL、LVDS、HCSL、CMOS、SSTL、HSTL)

目录 1. 简介 1.1 EVB 介绍 1.2 Si5338 Block Diagram 2. EVB 详解 2.1 实物图 2.2 基本配置 2.2.1 Universal Pin 2.2.2 IIC I/F 2.2.3 Input Clocks 2.2.4 Output Frequencies 2.2.5 Output Driver 2.2.6 Freq and Phase Offset 2.2.7 Spread Spectrum 2.2.8 快…

Spring AI应用系列——基于OpenTelemetry实现大模型调用的可观测性实践

一、项目背景与目标 在AI应用日益复杂的今天&#xff0c;大模型服务&#xff08;如语言理解和生成&#xff09;的性能监控和问题排查变得尤为关键。为了实现对大模型调用链路的可观测性&#xff08;Observability&#xff09;管理&#xff0c;我们基于 Spring Boot Spring AI…

Spyglass:官方Hands-on Training(一)

相关阅读 Spyglasshttps://blog.csdn.net/weixin_45791458/category_12828934.html?spm1001.2014.3001.5482 本文是对Spyglass Hands-on Training中第一个实验的翻译&#xff08;有删改&#xff09;&#xff0c;Lab文件可以从以下链接获取。Spyglass Hands-on Traininghttps:…

PCB设计工艺规范(三)走线要求

走线要求 1.走线要求2.固定孔、安装孔、过孔要求3.基准点要求4.丝印要求 1.走线要求 印制板距板边距离:V-CUT 边大于 0.75mm&#xff0c;铣槽边大于0.3mm。为了保证 PCB 加工时不出现露铜的缺陷&#xff0c;要求所有的走线及铜箔距离板边:V-CUT边大于 0.75mm&#xff0c;铣槽边…

抓取工具Charles配置教程(mac电脑+ios手机)

mac电脑上的配置 1. 下载最新版本的Charles 2. 按照以下截图进行配置 2.1 端口号配置&#xff1a; 2.2 https配置 3. mac端证书配置 4. IOS手机端网络配置 4.1 先查看电脑上的配置 4.2 配置手机网络 连接和电脑同一个wifi&#xff0c;然后按照以下截图进行配置 5. 手机端证书…

【CSS】精通Flex布局(全)

目录 1. flex布局体验 1.1 传统布局 与 flex布局 1.2 初体验 2. flex布局原理 2.1 布局原理 3. flex布局父项常见属性 3.1 常见父项属性 3.2 属性值 3.3 justify-content 设置主轴上的子元素排列方式 3.4 flex-wrap设置子元素是否换行 3.5 align-items 设置侧轴上的…

力扣第447场周赛

这次终于赶上力扣的周赛了, 赛时成绩如下(依旧还是三题 )&#xff1a; 1. 统计被覆盖的建筑 给你一个正整数 n&#xff0c;表示一个 n x n 的城市&#xff0c;同时给定一个二维数组 buildings&#xff0c;其中 buildings[i] [x, y] 表示位于坐标 [x, y] 的一个 唯一 建筑。 如…

AI中常用概念的理解

1. RAG&#xff08;检索增强生成&#xff09; 通俗理解&#xff1a;就像你写作业时&#xff0c;先查课本 / 百度找资料&#xff0c;再根据资料写答案&#xff0c;而不是纯靠记忆瞎编。 AI 模型&#xff08;比如 ChatGPT&#xff09;回答问题时&#xff0c;先去 “数据库 / 互联…

SQLServer多版本兼容Java方案和数据采集

Maven引入 <dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>sqljdbc4</artifactId><version>4.0</version></dependency><dependency><groupId>net.sourceforge.jtds</groupId><ar…

【每日八股】复习 Redis Day4:线程模型

文章目录 复习 Redis Day4&#xff1a;线程模型介绍一下 Redis 的线程模型核心线程模型&#xff08;Redis 6.0 之前&#xff09;Redis 6.0 的多线程改进Redis 真的是单线程吗&#xff1f;Redis 的线程模型剖析 上一篇 Redis 的应用我今天才完成&#xff0c;因此明天一并复习 Re…

树莓派智能摄像头实战指南:基于TensorFlow Lite的端到端AI部署

引言&#xff1a;嵌入式AI的革新力量 在物联网与人工智能深度融合的今天&#xff0c;树莓派这一信用卡大小的计算机正在成为边缘计算的核心载体。本文将手把手教你打造一款基于TensorFlow Lite的低功耗智能监控设备&#xff0c;通过MobileNetV2模型实现实时物体检测&#xff0…

vs2019编译occ7.9.0时,出现fatal error C1060: compiler is out of heap space

问题描述 visual studio 2019编译opencascade 7.9.0时&#xff0c;出现编译错误 fatal error C1060: compiler is out of heap space 解决方案 修改vs2019并行编译的线程个数&#xff0c;默认是12个&#xff0c;我改成了4个&#xff0c;问题解决 Tools > Project and Sol…

vue跨域问题总结笔记

目录 一、Websocket跨域问题 1.nginx配置 2.VUE CLI代理 3.env.development配置 4.nginx日志 5.解决 一、解决跨域的几种常用方法 1.Vue CLI代理 2.JSONP 3.WebSocket 4.NGINX解决跨域问题 6.Java解决跨域 二、Vue跨域问题详解 1. 什么是跨域 2. 跨域的例子 3.…

数据结构篇:线性表的另一表达—链表之单链表(下篇)

目录 1.前言 2.是否使用二级指针 3.插入/删除 3.1 pos位置前/后插入 3.2 查找函数 3.3 pos位置删除 3.4 pos位置后面删除 3.5 函数的销毁 4.断言问题 4.1 断言pphead 4.2 断言*pphead 5.三个文件的代码 5.1 头文件 5.2 具体函数实现 5.3 测试用例 1.前言 之前是讲…