2011-10-11 62 views
8

我有一個Java EE + Spring應用程序,它支持通過XML配置進行註釋。這些bean總是有原型範圍。動態定義在Spring中使用自動裝配的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 { 
    //.. 
} 

我一直在尋找一種方法,使自動佈線機制自動注入正確的豆(美國或加拿大,在這個例子)基於當前請求的國家。該國將被存儲在一個ThreadLocal變量中,並且它會在每個請求中改變。對於所有沒有自己特定規則的國家來說,也會有一個全球性的課程。

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

  1. 在Spring實例化一個對象之前,我自己的自定義代碼將不得不被調用。
  2. 如果我檢測到被請求的接口有多個實現,我會在我的ThreadLocal變量中查找正確的國家,並且會動態地將相應的限定符添加到自動請求中。
  3. 之後,春天將盡其所有的魔力。如果增加了一個限定詞,那就必須考慮;如果沒有,流程將照常進行。

我在正確的道路上嗎?任何想法對我來說呢?

謝謝。

回答

0

您可以提供一個Configuration類,它將根據ThreadLocal值返回正確的bean。這假定你正在使用Spring 3.我做了一些測試,以確保每個請求都調用了提供者方法。這就是我所做的。

@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作用域。

4

創建用於修飾實例變量或setter方法的自己的註釋,然後處理註釋並注入通用代理,該代理在運行時解析正確的實現並將調用委託給它。

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

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

這裏是在bean後處理器的postProcessBeforeInitialization(bean, beanName)方法的算法:

  1. 反思,以便找到實例變量或都標註有@LocalizedResource setter方法的bean類。將結果存儲在由類名稱索引的緩存中(只是一張地圖)。你可以使用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的bean名稱不相關,因爲根據類型和限定符執行查找。
  2. 執行期間,在線程局部變量中設置當前語言環境
  3. 查找您需要的bean(例如TransactionService)。後處理器將爲每個@LocalizedResource實例字段或setter方法注入一個代理。
  4. 當在TransactionService上調用一個最終進入一些TransactionRules方法的方法時,綁定到代理的調用處理程序根據存儲在線程局部變量中的值切換到正確的實現,然後將調用委託給該實現。

不是很微不足道,但它的工作原理。這實際上是Spring如何處理@PersistenceContext,除了實現查找,這是您的用例的一個附加功能。

+1

現在在2017年,是不是有一個更簡單的方法來實現這一目標? – maxxyme

相關問題