技術探索之旅:YAML配置,依賴注入、控制反轉與Java注解
前言
最近有點懶了,太松懈可不行。為了讓自己保持學習的動力,我決定將最近的學習內容整理成博客,目標是讓未來的自己也能輕松理解。我會盡量以整體記錄的方式呈現,所以一篇博客可能會分幾天完成。今天,我們先來研究一下依賴注入和控制反轉,順便把昨天的 YAML 配置映射的博客補充一下。
日程
- 8點:中午稍微看了一點內容,先來寫博客,再看看今天晚上能學到多少東西。
- 11點:下班吧,復習一會兒。
學習記錄
操作系統
- 分段存儲管理
- 段頁式管理
學習內容
省流
- YAML 配置文件
- 依賴注入與控制反轉
- 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 的接口
工作流程如下:
-
組件注冊階段:
- 啟動時掃描配置的包路徑。
- 將被注解的類轉換為
BeanDefinition
對象。 - 注冊到
BeanDefinitionRegistry
(由DefaultListableBeanFactory
實現):- 使用
beanDefinitionMap
(ConcurrentHashMap
)存儲 Bean 的定義。 - 維護
beanDefinitionNames
列表保持順序。
- 使用
-
預處理階段:
- 執行
BeanFactoryPostProcessor
(可修改BeanDefinition
)。 - 處理一些元數據相關的注解。
- 預解析依賴關系。
- 執行
-
實例化管理階段(
DefaultListableBeanFactory
):- 按依賴順序實例化:
- 通過
createBean()
創建實例。 - 使用三級緩存解決循環依賴。
- 依賴注入(
@Autowired
由AutowiredAnnotationBeanPostProcessor
處理)。 - 調用初始化方法(
@PostConstruct
)。
- 通過
- 存儲單例 Bean 到
singletonObjects
(ConcurrentHashMap
)。
- 按依賴順序實例化:
-
運行時階段:
- 處理
getBean()
請求。 - 管理生命周期(包括銷毀過程)。
- 處理作用域(單例/原型/請求/會話等)。
- 處理
重要概念:
BeanDefinition
不等同于 Bean 本身,它是 Bean 的解釋。- 比如:Bean 是一道要做的菜,
BeanDefinition
是這道菜的菜譜。 Registry
是管理這些菜譜的管理員。Factory
是負責做菜的那個廚神。
- 比如:Bean 是一道要做的菜,
流程圖:
源碼分析
這是不得不品鑒的一環。先 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
文件中,在運行時可以通過反射掃描到對應的字段。
結語
真的快把我看麻了,距離能夠自己實現一個依賴注入還有很長的路要走,加油吧!