首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Spring @Autowired 反射調用失效?手動注入與 ApplicationContextAware 解決方案

     編輯:程式設計綜合問答

場景:類 A 通過反射 `Class.forName(...).newInstance()` 建立類 B 實例,類 B 標註 `@Autowired` 注入 Service/DAO,結果注入失敗(字段為 null)。原因:**反射建立的對象不在 Spring 容器管理範圍內,生命週期回調(`@PostConstruct`、`AutowiredAnnotationBeanPostProcessor`)不會觸發**。以下提供三種標準解決方案。

核心原理

// ❌ 反射直接 newInstance,Spring 完全不知情
Object obj = Class.forName("com.example.ServiceB").newInstance();
// obj 內部 @Autowired 欄位永遠為 null

Spring 只負責管理「容器建立」的 Bean。反射繞過容器,自然無法享受依賴注入。

方案一:手動注入(最直接、適用於少量舊代碼整合)

public class ReflectiveCaller {
    @Autowired private ApplicationContext applicationContext; // 注入容器上下文

    public void callServiceB() {
        try {
            Class clazz = Class.forName("com.example.ServiceB");
            Object instance = clazz.getDeclaredConstructor().newInstance();

            // ✅ 關鍵:手動觸發依賴注入
            AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
            factory.autowireBean(instance);        // 注入 @Autowired/@Inject/@Resource
            factory.initializeBean(instance, "serviceB"); // 觸發 @PostConstruct、InitializingBean

            // 現在可以安全調用
            Method method = clazz.getMethod("doBusiness");
            method.invoke(instance);
        } catch (Exception e) {
            throw new RuntimeException("反射調用失敗", e);
        }
    }
}

方案二:實現 ApplicationContextAware,靜態獲取 Bean(舊專案常用)

 T getBean(Class clazz) {
        return context.getBean(clazz);
    }

    public static Object getBean(String name) {
        return context.getBean(name);
    }
}

// 反射邏輯中改為:
Object serviceB = SpringContextHolder.getBean(ServiceB.class); // 直接從容器獲取已注入好的 Bean
// 或
ServiceB serviceB = (ServiceB) Class.forName("...").newInstance();
SpringContextHolder.getAutowireCapableBeanFactory().autowireBean(serviceB);

方案三:重構為 Spring 管理的 Prototype Bean(最符合 Spring 設計哲學)

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 每次注入/獲取都是新實例
public class ServiceB {
    @Autowired private UserDao userDao; // 正常注入
    @Autowired private OrderService orderService;

    public void doBusiness() { ... }
}

// 使用端:注入 ObjectProvider 或 Provider
@Service
public class ServiceA {
    @Autowired private ObjectProvider serviceBProvider; // Spring 4.3+

    public void call() {
        ServiceB b = serviceBProvider.getObject(); // 容器建立,完整注入
        b.doBusiness();
    }
}

方案對比與選型建議

方案優點缺點適用場景
手動 autowireBean改動最小、不改變調用結構需持有 ApplicationContext、略顯「破壞框架」舊代碼整合、第三方庫實例化
SpringContextHolder 靜態獲取隨處可用、無需傳遞上下文靜態強耦合、難測試、違反 IoC 原則舊專案大量反射、工具類、定時任務
Prototype Bean + ObjectProvider完全受容器管理、支援 AOP、事務、生命週期需重構反射為依賴注入、JavaConfig 配置新代碼、核心業務邏輯、長期維護

注意事項

  • 事務邊界:手動 `autowireBean` 的實例若需 `@Transactional`,必須再經過 `factory.initializeBean` 並確保 AOP 代理生效,否則事務不生效。建議改用方案三。
  • 循環依賴:Prototype Bean 注入 Singleton 可能導致循環依賴,使用 `ObjectProvider`/`Provider` 延遲獲取可緩解。
  • 效能:頻繁反射建立實例較慢,Prototype Bean 同樣每次新建,可考慮池化或改為 Singleton + 狀態隔離。

總結:能重構就重構為 Prototype Bean + ObjectProvider;不得不反射時,用 `AutowireCapableBeanFactory.autowireBean()` 手動注入並初始化,切勿讓 `@Autowired` 字段處於 null 狀態運行業務邏輯。

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