当前位置: 首页 > news >正文

学习记录:DAY20

技术探索之旅:YAML配置,依赖注入、控制反转与Java注解

前言

最近有点懒了,太松懈可不行。为了让自己保持学习的动力,我决定将最近的学习内容整理成博客,目标是让未来的自己也能轻松理解。我会尽量以整体记录的方式呈现,所以一篇博客可能会分几天完成。今天,我们先来研究一下依赖注入和控制反转,顺便把昨天的 YAML 配置映射的博客补充一下。

日程

  • 8点:中午稍微看了一点内容,先来写博客,再看看今天晚上能学到多少东西。
  • 11点:下班吧,复习一会儿。

学习记录

操作系统

  1. 分段存储管理
  2. 段页式管理

学习内容

省流

  1. YAML 配置文件
  2. 依赖注入与控制反转
  3. Java 注解

1. YAML 配置文件

问题背景

在一轮项目中,我将一些配置相关的常量写到了单例里面。这种方法的优点是实现简单,但不利于更改,尤其是在将项目作为 JAR 包部署到服务器时。将配置信息写入 YAML 文件,主要就是为了解决这个问题。

实现方式

主要调用了 Jackson 的 YAML 解析依赖:

<!-- YAML 格式解析器 -->
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-yaml</artifactId><version>2.17.1</version>
</dependency>

以我现在的 YAML 配置文件为例:

# Tomcat 相关配置
tomcat:# 端口port: 8080# webapp 目录webappDir: "Anykat-application/src/webapp"# 扫描的目录classesDir: "Anykat-application/target/classes"# Jwt 相关配置
jwt:# Jwt 密钥secretKey:  "ProjectAnykatKaCatIsMyLongLongSecretKeyTo256Bits"# 管理员 Jwt 密钥adminSecretKey: "ProjectAnykatKaCatIsMyLongLongSecretKeyTo256BitsUsingByAdmin"# 有效时间expireTime: 21600000 # 6h

通过一个单例来管理:

public class AppConfig {private TomcatConfig tomcat;private JwtConfig jwt;// 私有静态实例变量private static volatile AppConfig instance;// 私有构造函数防止外部实例化private AppConfig() throws FileNotFoundException {}// 获取单例的静态方法public static AppConfig getInstance() throws FileNotFoundException {if (instance == null) {...}return instance;}
}

在单例实例化时进行加载:

// 加载配置文件
Yaml yaml = new Yaml();
instance = yaml.loadAs(new FileInputStream("config.yml"),AppConfig.class
);
缺点

这种方法的实现比较粗糙简单,它对 YAML 字段的对应有严格要求,且必须有相应的实体类来接收 YAML 映射。因为通过单例进行加载,所以不支持热更新。

改进建议
  • 通过反射和动态代理来生成对应字段的实体类,不依赖硬编码的实体类。
  • 把单例模式换成其他模式,来支持配置的热更新。

2. 依赖注入与控制反转

作用分析

首先,我们来了解一下这是一种什么工作模式:

  • 不直接创建实体类的对象,而是先把实体类交给 IoC 容器管理(控制反转),在需要使用时,再从容器中取出对应的对象(依赖注入)。
  • 对比直接通过 new 创建对象,这样做的好处包括:
    • 生命周期管理:自动处理对象销毁,避免内存泄漏;处理复杂的生命周期(单独实例/共享单例实例)。
    • 依赖关系管理:装配时自动处理依赖的装配;解决循环依赖、嵌套依赖(三层缓冲)。
    • 低耦合度:实例替换;单元测试(mockBean 替换)。
    • 可配置性和灵活性:可以在不修改代码的情况下通过配置改变应用程序行为;支持运行时决定具体实现(如根据环境选择不同实现)。
    • 集中管理:所有对象的创建和依赖关系在一个地方管理;便于维护和了解系统整体结构。
原理分析

Spring Boot 中的依赖注入主要有以下核心功能:

  • 注册组件(类)
  • 管理组件实例
  • 自动解析依赖关系
  • 提供获取 Bean 的接口

工作流程如下:

  1. 组件注册阶段

    • 启动时扫描配置的包路径。
    • 将被注解的类转换为 BeanDefinition 对象。
    • 注册到 BeanDefinitionRegistry(由 DefaultListableBeanFactory 实现):
      • 使用 beanDefinitionMapConcurrentHashMap)存储 Bean 的定义。
      • 维护 beanDefinitionNames 列表保持顺序。
  2. 预处理阶段

    • 执行 BeanFactoryPostProcessor(可修改 BeanDefinition)。
    • 处理一些元数据相关的注解。
    • 预解析依赖关系。
  3. 实例化管理阶段DefaultListableBeanFactory):

    • 按依赖顺序实例化:
      • 通过 createBean() 创建实例。
      • 使用三级缓存解决循环依赖。
      • 依赖注入(@AutowiredAutowiredAnnotationBeanPostProcessor 处理)。
      • 调用初始化方法(@PostConstruct)。
    • 存储单例 Bean 到 singletonObjectsConcurrentHashMap)。
  4. 运行时阶段

    • 处理 getBean() 请求。
    • 管理生命周期(包括销毁过程)。
    • 处理作用域(单例/原型/请求/会话等)。

重要概念

  • BeanDefinition 不等同于 Bean 本身,它是 Bean 的解释。
    • 比如:Bean 是一道要做的菜,BeanDefinition 是这道菜的菜谱。
    • Registry 是管理这些菜谱的管理员。
    • Factory 是负责做菜的那个厨神。

流程图

不存在
单例
原型
请求级
启动SpringBoot应用
组件注册阶段
扫描组件注解
生成BeanDefinition
注册到Registry
存储beanDefinitionMap
维护beanDefinitionNames
预处理阶段
执行BeanFactoryPostProcessor
处理PrimaryDependsOn
构建依赖关系图
实例化阶段
是否单例
检查singletonObjects
调用createBean
三级缓存处理
依赖注入
执行初始化
存入singletonObjects
直接创建实例
运行时阶段
接收getBean请求
作用域类型
返回缓存实例
新建实例
作用域获取
生命周期管理
销毁清理
源码分析

这是不得不品鉴的一环。先 clone 一下 Spring Framework 的源码:Spring Framework 源码。然后我自己慢慢看源码(略过了哈)。

3. Java 注解

在正式编写自己的 BeanFactory 方法之前,先来了解一下关于 Java 注解的基本知识。

Java 提供了一些内置注解:

  • @Override:表示方法覆盖了父类中的方法。
  • @Deprecated:表示元素已过时,不推荐使用。
  • @SuppressWarnings:告诉编译器忽略特定警告。
  • @SafeVarargs:断言方法或构造器不会对其可变参数执行不安全的操作。
  • @FunctionalInterface:表示接口是函数式接口(Java 8)。

而实现自己的注解,则要用到 Java 的元注解:

  • @Target:指定注解可以应用的位置(如 TYPE(类、接口、注解、枚举)、METHOD(方法)、FIELD(字段、常量)等)。
  • @Retention:指定注解的保留策略(SOURCE(源码级,编译时丢弃)、CLASS(类文件,运行时不加载)、RUNTIME(运行时))。
  • @Documented:表示注解应包含在 Javadoc 中。
  • @Inherited:表示子类可以继承父类的注解。
  • @Repeatable:表示注解可以在同一位置重复使用(Java 8)。

示例:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

重要说明
注解的本身没有任何作用,它只是做了一个标记。例如,@Retention(RetentionPolicy.RUNTIME) 以属性表的形式存储在 .class 文件中,在运行时可以通过反射扫描到对应的字段。

结语

真的快把我看麻了,距离能够自己实现一个依赖注入还有很长的路要走,加油吧!

http://www.xdnf.cn/news/204013.html

相关文章:

  • 人工智能数学基础(四):线性代数
  • Vue3调度器错误解析,完美解决Unhandled error during execution of scheduler flush.
  • 【C#】.net core6.0无法访问到控制器方法,直接404。由于自己的不仔细,出现个低级错误,这让DeepSeek看出来了,是什么错误呢,来瞧瞧
  • 51c自动驾驶~合集37
  • 【资料分享】全志T536(异构多核ARMCortex-A55+玄铁E907 RISC-V)工业核心板硬件说明书
  • 【MCP Node.js SDK 全栈进阶指南】高级篇(3):MCP 安全体系建设
  • HarmonyOS NEXT 诗词元服务项目开发上架全流程实战(一、项目介绍及实现效果)
  • [Android] GKD v1.10.3
  • 摆动序列(Java)
  • 安卓基础(点击项目)
  • 怎么把Ubuntu系统虚拟环境中启动命令做成系统服务可以后台运行?
  • ArcPy 中的地理处理工具
  • 打印及判断回文数组、打印N阶数组、蛇形矩阵
  • STL 算法库中的 min_element 和 max_element
  • AI日报 - 2025年04月29日
  • JAVA:线程池
  • 弹性盒子布局
  • 嘉黎县传统手工艺传承与发展交流会圆满举行
  • 【LInux网络】网络层IP协议全面解析
  • 亚马逊低价商城战略全解析:跨境卖家突围价格战的7维作战体系
  • 鸿蒙应用开发 知识点 官网快速定位表
  • 鉴权方案与 Sa-Token(元宝胡编乱造中)
  • 【LaTex】8.2 段落格式
  • 关于codeforces设置中文 以及 插件安装后没显示中文的问题解决
  • 【MQ篇】RabbitMQ之惰性队列!
  • Java——构造方法
  • 数据结构算法竞赛训练网站OJ(Online Judge)
  • el-dialog弹窗关闭时调了两次刷新数据的接口
  • KBEngine 源代码分析(二):协议注册和处理
  • Vue 生命周期钩子总结