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

Spring 服務定位器模式:任意位置獲取 Bean 的最佳實踐

     編輯:Java 綜合教學

核心設計理念

透過持有 Spring ApplicationContext,打破依賴注入的作用域限制,實現在非 Spring 管理類別、靜態方法、工具類等任意位置獲取 Bean 實例。採用單例模式與 ApplicationContextAware 接口結合,確保上下文全局可訪問且生命週期安全。

完整實作代碼

package com.example.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 服務定位器 - 全局 Spring Bean 訪問入口
 * 支援按名稱/類型獲取 Bean,自動處理類型轉換
 */
public final class ServiceLocator implements ApplicationContextAware, DisposableBean {

    private static final Log logger = LogFactory.getLog(ServiceLocator.class);
    private static ApplicationContext context = null;

    /**
     * Spring 容器啟動時自動注入 ApplicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        logger.debug("Injected the ApplicationContext into ServiceLocator: " + applicationContext);
        if (ServiceLocator.context != null) {
            logger.warn("ApplicationContext in ServiceLocator is being overridden. Original: " 
                    + ServiceLocator.context);
        }
        ServiceLocator.context = applicationContext;
    }

    /**
     * 容器關閉時清理靜態引用,防止記憶體洩漏
     */
    @Override
    public void destroy() throws Exception {
        ServiceLocator.clear();
    }

    /**
     * 獲取原始 ApplicationContext 實例
     */
    public static ApplicationContext getApplicationContext() {
        assertContextInjected();
        return context;
    }

    /**
     * 按 Bean 名稱獲取實例(自動類型轉換)
     * @param name Bean 名稱
     * @param  目標類型
     * @return Bean 實例
     */
    @SuppressWarnings("unchecked")
    public static  T getService(String name) {
        assertContextInjected();
        return (T) context.getBean(name);
    }

    /**
     * 按 Bean 類型獲取實例
     * @param requiredType Bean 類別
     * @param  目標類型
     * @return Bean 實例
     */
    public static  T getService(Class requiredType) {
        assertContextInjected();
        return context.getBean(requiredType);
    }

    /**
     * 手動清理上下文引用
     */
    public static void clear() {
        logger.debug("Clear ApplicationContext in ServiceLocator: " + context);
        context = null;
    }

    /**
     * 斷言上下文已注入,未注入則拋出異常
     */
    private static void assertContextInjected() {
        if (context == null) {
            throw new IllegalStateException("ApplicationContext not injected. " +
                    "Please define ServiceLocator in context.xml");
        }
    }
}

使用示例

// 在任意非 Spring 管理類別中使用
public class SomeUtilityClass {
    public void doSomething() {
        // 按名稱獲取
        UserService userService = ServiceLocator.getService("userService");
        
        // 按類型獲取(單例 Bean)
        OrderService orderService = ServiceLocator.getService(OrderService.class);
        
        // 獲取原始上下文進行進階操作
        ApplicationContext ctx = ServiceLocator.getApplicationContext();
    }
}

核心優勢與適用場景

優勢說明
零侵入式無需修改現有 Bean 定義,不影響 Spring 依賴注入機制
全局可訪問靜態方法調用,無需傳遞上下文引用
類型安全泛型支援自動轉換,減少強制轉型錯誤
生命週期安全實現 DisposableBean,容器關閉自動清理

典型應用場景

  • 第三方庫回調函數中需要訪問 Spring Bean
  • 舊系統遷移過渡期,非 Spring 管理類別需調用新服務
  • 工具類、常量類、靜態方法中獲取服務實例
  • 定時任務、JMX、WebSocket 等特殊執行緒環境

注意事項與最佳實踐

  1. 謹慎使用:優先考慮構造器/Setter 注入,僅在無法注入時使用
  2. 避免循環依賴:不要在 Bean 初始化階段調用 getService
  3. 測試友好:單元測試時可手動設置 Mock 上下文
  4. 多上下文環境:若存在多個 DispatcherServlet,需區分父子上下文
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved