动态定义在Spring中自动assembly哪个bean(使用限定符)

我有一个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变量中查找正确的国家/地区,并动态地将相应的限定符添加到自动连线请求中。
  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代码中搜索对此classe的引用来查找有关其工作原理的示例。
  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方法的方法时,绑定到代理的调用处理程序根据存储在thread-local变量中的值切换到正确的实现,然后将调用委托给该实现。

不是很琐碎,但它确实有效。 这实际上是Spring处理@PersistenceContext的方式,除了实现查找,这是用例的附加function。

您可以提供一个Configuration类,它将根据ThreadLocal值返回正确的bean。 这假设您使用的是Spring 3.我做了一些测试,以确保在每个请求上调用了provider方法。 这就是我做的。

 @Configuration public class ApplicationConfiguration { private static int counter = 0; @Bean( name="joel" ) @Scope( value="request", proxyMode=ScopedProxyMode.TARGET_CLASS) List getJoel() { return Arrays.asList( new String[] { "Joel " + counter++ } ); } } 

并在我的控制器中引用了如下值。

 @Resource( name="joel" ) private List joel; 

在您的提供程序实现中,您可以检查ThreadLocal的语言环境并返回正确的TransactionRules对象或类似的东西。 ScopedProxy的东西是因为我注入了一个Controller,它是Singleton作用域,而值是请求作用域。