动态定义在Spring中使用自动装配的bean(使用限定符)

问题描述:

我有一个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. 之后,春天将尽其所有的魔力。如果增加了一个限定词,那就必须考虑;如果没有,流程将照常进行。

我在正确的道路上吗?任何想法对我来说呢?

谢谢。

您可以提供一个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作用域。

创建用于修饰实例变量或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