開發小工具、腳本、微服務原型時,引入 Spring 等重型框架會帶來:啟動緩慢、依賴龐大、配置繁瑣。但手動傳遞實例、頻繁重構又極其痛苦。SimpleFactory (SF) 以單類、零依賴、~100 行代碼實現核心 DI 功能:單例/多例、構造器注入、Setter 注入、初始化回調,適合「不想引框架又想要解耦」的輕量場景。
| 特性 | 實現方式 | 對應 Spring |
|---|---|---|
| 單例 Bean | get(Class) 緩存 Map | @Scope("singleton") |
| 多例 Bean | getMulti(Class) 每次新建 | @Scope("prototype") |
| 構造器注入 | 反射匹配參數類型調用構造器 | 構造器注入 |
| Setter 注入 | 掃描 setXxx 方法,遞歸從容器獲取依賴 | @Autowired setter |
| 初始化回調 | 調用無參 init() 方法 | @PostConstruct / InitializingBean |
| 手動註冊 | put(Class, instance) 覆蓋自動創建 | @Bean 方法返回 |
package com.honeysuckle.webx.context;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
/**
* SimpleBeanFactory - 極簡依賴注入容器
* 功能:單例/多例、構造器/Setter注入、init()回調
* 原則:零依賴、單文件、開箱即用
*/
public class SF {
/** 單例緩存:Class -> Instance */
private final Map, Object> context = new HashMap<>();
/**
* 手動註冊實例(覆蓋自動創建)
* @param clazz 實例類型(可為介面)
* @param object 實例對象
*/
public void put(Class> clazz, Object object) {
this.context.put(clazz, object);
}
/**
* 獲取單例 Bean(不存在則自動創建並緩存)
* @param clazz 目標類型
* @param args 構造器參數(按類型匹配)
* @return Bean 實例,創建失敗返回 null
*/
public Object get(Class> clazz, Object... args) {
Object object = this.context.get(clazz);
if (object == null) {
try {
object = this.instanceClazz(clazz, args);
this.instanceSetMethod(object);
this.instanceInitMethod(object);
this.put(clazz, object);
} catch (Exception e) {
return null;
}
}
return object;
}
/**
* 獲取多例 Bean(每次調用創建新實例,不緩存)
*/
public Object getMulti(Class> clazz, Object... args) {
try {
Object object = this.instanceClazz(clazz, args);
this.instanceSetMethod(object);
this.instanceInitMethod(object);
return object;
} catch (Exception e) {
return null;
}
}
/** 反射實例化:匹配參數類型的構造器 */
private Object instanceClazz(Class> clazz, Object[] args) throws Exception {
Class>[] paramTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) paramTypes[i] = args[i].getClass();
for (Constructor> con : clazz.getConstructors()) {
if (con.getParameterTypes().length != args.length) continue;
// 簡化:僅匹配參數數量,生產建議加強類型兼容性檢查
return con.newInstance(args);
}
// 無匹配構造器嘗試無參構造器
return clazz.getDeclaredConstructor().newInstance();
}
/** Setter 注入:遞歸解析依賴 */
private void instanceSetMethod(Object object) {
Method[] methods = object.getClass().getDeclaredMethods();
Queue queue = new LinkedList<>();
for (Method m : methods) queue.add(m);
// 迭代直到無新注入發生(解決依賴順序問題)
for (int i = queue.size(); queue.size() > 0; i = queue.size()) {
Method method = queue.poll();
if (!method.getName().startsWith("set")) continue;
if (method.getParameterTypes().length != 1) continue;
Class> paramClazz = method.getParameterTypes()[0];
try {
Object dependency = this.get(paramClazz); // 遞歸獲取依賴
method.invoke(object, dependency);
} catch (Exception ignored) {
continue; // 依賴未就緒,下輪重試
}
if (i == queue.size()) break; // 本輪無進展,防死循環
}
}
/** 初始化回調:調用無參 init() */
private void instanceInitMethod(Object object) {
try {
Method init = object.getClass().getMethod("init");
init.invoke(object);
} catch (Exception ignored) {} // 無 init 方法視為無需初始化
}
}
public class Bean1 {
private String hello;
public void init() { // 初始化回調
this.hello = "hello kitty";
}
public void show() {
System.out.println(this.hello);
}
}
public class TestSF {
private Bean1 bean; // 依賴
public void setBean(Bean1 bean) { // Setter 注入點
this.bean = bean;
}
public void test() {
this.bean.show();
}
public static void main(String[] args) {
SF sf = new SF();
// 1. 獲取 TestSF 實例(觸發遞歸創建 Bean1 → Setter 注入 → init)
TestSF test = (TestSF) sf.get(TestSF.class);
// 2. 執行業務
test.test();
}
}
hello kitty
SF sf = new SF();
sf.put(Bean1.class, new Bean1() {{ init(); }}); // 手動構建並初始化
TestSF test = (TestSF) sf.get(TestSF.class); // 直接注入手動實例
Object bean1 = sf.getMulti(Bean1.class); // 每次新實例
Object bean2 = sf.getMulti(Bean1.class);
System.out.println(bean1 == bean2); // false
public class ServiceA {
private final Repository repo;
public ServiceA(Repository repo) { this.repo = repo; } // 構造器注入
}
SF sf = new SF();
sf.put(Repository.class, new RepositoryImpl());
ServiceA svc = (ServiceA) sf.get(ServiceA.class); // 自動注入 Repository
| 局限 | 說明 | Spring 如何解決 |
|---|---|---|
| 無循環依賴檢測 | A→B→A 會棧溢出 | 三級緩存 + 早期暴露 |
| 構造器參數匹配簡陋 | 僅匹配數量,不支援類型兼容、@Autowired(required=false) | 完整參數解析器 |
| 無 AOP/事務/事件 | 純 DI 容器 | 完整生態 |
| 無配置元數據 | 純代碼驅動 | XML/JavaConfig/註解 |
| 非線程安全 | Map/Queue 非併發結構 | ConcurrentHashMap + 雙檢鎖 |
SF 用最小代碼量演示了 DI 容器的核心骨架:實例緩存、反射實例化、依賴圖遞歸解析、生命周期回調。它不是 Spring 的替代品,而是理解 DI 本質、解決輕量場景解耦的利器。若專案增長超出其承載,平滑遷移至 Spring/Guice/Dagger 只需替換容器啟動邏輯,業務代碼(Setter/構造器/初始化方法)幾乎無需修改——這正是依賴倒置原則的價值所在。