[英]Dynamically defining which bean to autowire in Spring (using qualifiers)
I have a Java EE + Spring app that favors annotations over XML configuration. 我有一个Java EE + Spring应用程序,它比XML配置更喜欢注释。 The beans always have prototype scope. Bean始终具有原型范围。
I now have in my app business rules that depend on the country the user request was made from. 现在,我的应用程序业务规则取决于用户发出请求的国家/地区。 So I would have something like this (keep in mind this example was heavily simplified): 所以我会有这样的事情(请记住,此示例已大大简化):
@Component
public class TransactionService {
@Autowired
private TransactionRules rules;
//..
}
@Component
@Qualifier("US")
public class TransactionRulesForUS implements TransactionRules {
//..
}
@Component
@Qualifier("CANADA")
public class TransactionRulesForCanada implements TransactionRules {
//..
}
I was looking for a way to make the auto-wiring mechanism automatically inject the right bean (either US or Canada, in this example) based on the country of the current request. 我一直在寻找一种使自动装配机制根据当前请求的国家/地区自动注入合适的bean(在本例中为美国或加拿大)的方法。 The country would be stored in a ThreadLocal variable, and it would change in each request. 该国家/地区将存储在ThreadLocal变量中,并且在每个请求中都会更改。 There would also be a global class, for all countries that didn't have their own particular rules. 对于没有自己特定规则的所有国家,还将有一个全球班级。
I imagine I would have to customize the way Spring decides how to create the objects it will inject. 我想我必须定制Spring决定如何创建将注入的对象的方式。 The only way I found to do this was using FactoryBean, but that was not quite what I hoped for (not generic enough). 我发现做到这一点的唯一方法是使用FactoryBean,但这并不是我所希望的(不够通用)。 I was hoping to do something like this: 我希望做这样的事情:
Am I in the right path? 我在正确的道路上吗? Any ideas for me on this? 对我有什么想法吗?
Thanks. 谢谢。
Create your own annotation that is used to decorate instance variables or setter methods, then a post-processor that processes the annotation and injects a generic proxy which resolves the correct implementation at runtime and delegates the call to it. 创建您自己的注释,该注释用于装饰实例变量或setter方法,然后由后处理器处理该注释,并注入一个通用代理,该代理在运行时解析正确的实现并将其委托给它。
@Component
public class TransactionService {
@LocalizedResource
private TransactionRules rules;
//..
}
@Retention(RUNTIME)
@Target({FIELD, METHOD})
public @interface LocalizedResource {}
Here is the algorithm for the postProcessBeforeInitialization(bean, beanName)
method in your bean post-processor : 这是bean后处理器中的postProcessBeforeInitialization(bean, beanName)
方法的算法:
InjectionMetadata
for this purpose. 您可以为此使用Spring的InjectionMetadata
。 You can look for examples on how it works by searching references to this classe in spring code. 您可以通过在Spring代码中搜索对该类的引用来查找有关其工作方式的示例。 Here is the InvocationHandler for the proxy that will be used to create localized resources. 这是用于创建本地化资源的代理的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
}
}
You may need to make some more controls over the bean type, or add the requested bean type in the InvocationHandler. 您可能需要对Bean类型进行更多控制,或在InvocationHandler中添加请求的Bean类型。
The next thing is to autodetect implementations of a given interface, which are local-dependant, and register them with the qualifier corresponding to the locale. 下一步是自动检测与本地相关的给定接口的实现,并向与该语言环境相对应的限定符注册它们。 You can implement a BeanDefinitionRegistryPostProcessor
or BeanFactoryPostProcessor
for that purpose, in order to add new BeanDefinition
s to the registry, with proper qualifier, one for each implementation of locale-aware interfaces. 您可以为此目的实现BeanDefinitionRegistryPostProcessor
或BeanFactoryPostProcessor
,以便使用适当的限定符将新的BeanDefinition
添加到注册表中,每种实现都适用于区域设置感知的接口。 You can guess the locale of an implementation by following naming conventions : if a locale-aware interface is called TransactionRules, then implementations may be named TransactionRules_ISOCODE in the same package. 您可以通过遵循以下命名约定来猜测实现的语言环境:如果一个支持语言环境的接口称为TransactionRules,则在同一程序包中的实现可以命名为TransactionRules_ISOCODE。
If you cannot afford such a naming convention, you will need to have some sort of classpath scanning + a way to guess the locale of a given implementation (maybe an annotation on the implementation classes). 如果您负担不起这样的命名约定,则将需要某种类路径扫描+一种猜测给定实现的语言环境的方法(可能是实现类的注释)。 Classpath scanning is possible but quite complex and slow, so try to avoid it. 类路径扫描是可能的,但是非常复杂且缓慢,因此请避免使用它。
Here's a summary of what happens: 以下是发生的情况的摘要:
Not really trivial, but it works. 并不是很琐碎,但是可以。 This is actually how @PersistenceContext is processed by Spring, except for implementations lookup, which is an additional feature of your use case. 实际上,这是Spring处理@PersistenceContext的方式,除了实现查找(这是用例的附加功能)之外。
You could provide a Configuration class that will return the correct bean based on the ThreadLocal value. 您可以提供一个Configuration类,该类将基于ThreadLocal值返回正确的bean。 This assumes you are using Spring 3. I did a little test to make sure that the provider method was called on each request. 假设您使用的是Spring3。我做了一点测试,以确保在每个请求上都调用了provider方法。 Here's what I did. 这就是我所做的。
@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++ } );
}
}
And referenced the value in my Controller as follows. 并在我的控制器中引用了以下值。
@Resource( name="joel" )
private List<String> joel;
in your implementation of the provider you could check the ThreadLocal for the locale and return the correct TransactionRules object or something like that. 在提供程序的实现中,可以检查ThreadLocal的语言环境,并返回正确的TransactionRules对象或类似的对象。 The ScopedProxy stuff is because I was injecting into a Controller, which is Singleton scoped whereas the value is request scoped. ScopedProxy的原因是因为我正在注入Controller,该控制器是Singleton范围的,而值是request范围的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.