首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

單文件實現輕量級依賴注入容器:SimpleFactory (SF) 設計與實戰

     編輯:Java 相關知識

動機:小專案也需要解耦

開發小工具、腳本、微服務原型時,引入 Spring 等重型框架會帶來:啟動緩慢、依賴龐大、配置繁瑣。但手動傳遞實例、頻繁重構又極其痛苦。SimpleFactory (SF) 以單類、零依賴、~100 行代碼實現核心 DI 功能:單例/多例、構造器注入、Setter 注入、初始化回調,適合「不想引框架又想要解耦」的輕量場景。

核心功能特性

特性實現方式對應 Spring
單例 Beanget(Class) 緩存 Map@Scope("singleton")
多例 BeangetMulti(Class) 每次新建@Scope("prototype")
構造器注入反射匹配參數類型調用構造器構造器注入
Setter 注入掃描 setXxx 方法,遞歸從容器獲取依賴@Autowired setter
初始化回調調用無參 init() 方法@PostConstruct / InitializingBean
手動註冊put(Class, instance) 覆蓋自動創建@Bean 方法返回

完整源碼 (SF.java)

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 方法視為無需初始化
    }
}

使用示例

1. 定義 Bean

public class Bean1 {
    private String hello;
    
    public void init() {           // 初始化回調
        this.hello = "hello kitty";
    }
    
    public void show() {
        System.out.println(this.hello);
    }
}

2. 定義消費者(Setter 注入)

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

3. 輸出結果

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 + 雙檢鎖

適用場景建議

  • ✅ 單元測試 Mock 容器
  • ✅ 命令行工具、批處理腳本
  • ✅ 微服務原型、Serverless 函數
  • ✅ Android 早期開發(無 Dagger/Hilt 時)
  • ✅ 教學演示 DI 原理
  • ❌ 生產級企業應用
  • ❌ 高併發多線程環境
  • ❌ 複雜依賴圖、需 AOP 場景

總結

SF 用最小代碼量演示了 DI 容器的核心骨架:實例緩存、反射實例化、依賴圖遞歸解析、生命周期回調。它不是 Spring 的替代品,而是理解 DI 本質、解決輕量場景解耦的利器。若專案增長超出其承載,平滑遷移至 Spring/Guice/Dagger 只需替換容器啟動邏輯,業務代碼(Setter/構造器/初始化方法)幾乎無需修改——這正是依賴倒置原則的價值所在。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved