簡體   English   中英

在Spring中動態定義要自動裝配的bean(使用限定符)

[英]Dynamically defining which bean to autowire in Spring (using qualifiers)

我有一個Java EE + Spring應用程序,它比XML配置更喜歡注釋。 Bean始終具有原型范圍。

現在,我的應用程序業務規則取決於用戶發出請求的國家/地區。 所以我會有這樣的事情(請記住,此示例已大大簡化):

@Component
public class TransactionService {
    @Autowired
    private TransactionRules rules;
    //..
}


@Component
@Qualifier("US")
public class TransactionRulesForUS implements TransactionRules {
     //..
}

@Component
@Qualifier("CANADA")
public class TransactionRulesForCanada implements TransactionRules {
     //..
}

我一直在尋找一種使自動裝配機制根據當前請求的國家/地區自動注入合適的bean(在本例中為美國或加拿大)的方法。 該國家/地區將存儲在ThreadLocal變量中,並且在每個請求中都會更改。 對於沒有自己特定規則的所有國家,還將有一個全球班級。

我想我必須定制Spring決定如何創建將注入的對象的方式。 我發現做到這一點的唯一方法是使用FactoryBean,但這並不是我所希望的(不夠通用)。 我希望做這樣的事情:

  1. 在Spring實例化對象之前,必須調用我自己的自定義代碼。
  2. 如果我檢測到所請求的接口具有多個實現,則可以在我的ThreadLocal變量中查找正確的國家/地區,並將動態地將適當的Qualifier添加到自動裝配請求中。
  3. 在那之后,Spring將發揮所有通常的魔力。 如果添加了限定詞,則必須考慮到這一點; 如果沒有,流程將照常進行。

我在正確的道路上嗎? 對我有什么想法嗎?

謝謝。

創建您自己的注釋,該注釋用於裝飾實例變量或setter方法,然后由后處理器處理該注釋,並注入一個通用代理,該代理在運行時解析正確的實現並將其委托給它。

@Component
public class TransactionService {
  @LocalizedResource
  private TransactionRules rules;
  //..
}

@Retention(RUNTIME)
@Target({FIELD, METHOD})
public @interface LocalizedResource {}

這是bean后處理器中的postProcessBeforeInitialization(bean, beanName)方法的算法:

  1. 內省bean類,以查找用@LocalizedResource注釋的實例變量或setter方法。 將結果存儲在按類名稱索引的緩存(僅是地圖)中。 您可以為此使用Spring的InjectionMetadata 您可以通過在Spring代碼中搜索對該類的引用來查找有關其工作方式的示例。
  2. 如果該bean存在這樣的字段或方法,請使用下面描述的InvocationHandler創建代理,並將當前的BeanFactory傳遞給它(bean后處理器必須是ApplicationContextAware)。 在實例變量中插入該代理,或使用代理實例調用setter方法。

這是用於創建本地化資源的代理的InvocationHandler。

public class LocalizedResourceResolver implements InvocationHandler {
  private final BeanFactory bf;
  public LocalizedResourceResolver(BeanFactory bf) {
    this.bf = bf;
  }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String locale = lookupCurrentLocale();
    Object target = lookupTarget(locale);
    return method.invoke(target, args);
  }

  private String lookupCurrentLocale() {
    // here comes your stuff to look up the current locale
    // probably set in a thread-local variable
  }

  private Object lookupTarget(String locale) {
    // use the locale to match a qualifier attached to a bean that you lookup using the BeanFactory.
    // That bean is the target
  }
}

您可能需要對Bean類型進行更多控制,或在InvocationHandler中添加請求的Bean類型。

下一步是自動檢測與本地相關的給定接口的實現,並向與該語言環境相對應的限定符注冊它們。 您可以為此目的實現BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor ,以便使用適當的限定符將新的BeanDefinition添加到注冊表中,每種實現都適用於區域設置感知的接口。 您可以通過遵循以下命名約定來猜測實現的語言環境:如果一個支持語言環境的接口稱為TransactionRules,則在同一程序包中的實現可以命名為TransactionRules_ISOCODE。

如果您負擔不起這樣的命名約定,則將需要某種類路徑掃描+一種猜測給定實現的語言環境的方法(可能是實現類的注釋)。 類路徑掃描是可能的,但是非常復雜且緩慢,因此請避免使用它。

以下是發生的情況的摘要:

  1. 當應用程序啟動時,將發現TransactionRules的實現,並為每個規則創建bean定義,並帶有與每個實現的語言環境相對應的限定符。 這些查詢的bean名稱與類型無關,因為查詢是根據類型和限定符執行的。
  2. 執行期間,在線程局部變量中設置當前語言環境
  3. 查找所需的bean(例如TransactionService)。 后處理器將為每個@LocalizedResource實例字段或setter方法注入一個代理。
  4. 在TransactionService上調用的方法最終變成某些TransactionRules的方法時,綁定到代理的調用處理程序將根據線程局部變量中存儲的值切換到正確的實現,然后將調用委派給該實現。

並不是很瑣碎,但是可以。 實際上,這是Spring處理@PersistenceContext的方式,除了實現查找(這是用例的附加功能)之外。

您可以提供一個Configuration類,該類將基於ThreadLocal值返回正確的bean。 假設您使用的是Spring3。我做了一點測試,以確保在每個請求上都調用了provider方法。 這就是我所做的。

@Configuration
public class ApplicationConfiguration
{
    private static int counter = 0;

    @Bean( name="joel" )
    @Scope( value="request", proxyMode=ScopedProxyMode.TARGET_CLASS)
    List<String> getJoel()
    {
        return Arrays.asList( new String[] { "Joel " + counter++ } );
    }
}

並在我的控制器中引用了以下值。

@Resource( name="joel" )
private List<String> joel;

在提供程序的實現中,可以檢查ThreadLocal的語言環境,並返回正確的TransactionRules對象或類似的對象。 ScopedProxy的原因是因為我正在注入Controller,該控制器是Singleton范圍的,而值是request范圍的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM