简体   繁体   English

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

[英]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: 我希望做这样的事情:

  1. Before Spring instantiates an object, my own custom code would have to be called. 在Spring实例化对象之前,必须调用我自己的自定义代码。
  2. If I detect that the interface being requested has more than one implementation, I would look up in my ThreadLocal variable the right country and would dynamically add the appropriate Qualifier to the auto-wire request. 如果我检测到所请求的接口具有多个实现,则可以在我的ThreadLocal变量中查找正确的国家/地区,并将动态地将适当的Qualifier添加到自动装配请求中。
  3. After that, Spring would do all its usual magic. 在那之后,Spring将发挥所有通常的魔力。 If a qualifier was added, that would have to be taken in consideration; 如果添加了限定词,则必须考虑到这一点; if it hasn't, the flow would proceed as usual. 如果没有,流程将照常进行。

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)方法的算法:

  1. Introspect the bean class in order to find instance variables or setter methods which are annotated with @LocalizedResource. 内省bean类,以查找用@LocalizedResource注释的实例变量或setter方法。 Store the result in a cache (just a map) indexed by the class name. 将结果存储在按类名称索引的缓存(仅是地图)中。 You can use Spring's 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代码中搜索对该类的引用来查找有关其工作方式的示例。
  2. If such a field or method exists for the bean, create a proxy using the InvocationHandler described below, passing it the current BeanFactory (the bean post-processor has to be ApplicationContextAware). 如果该bean存在这样的字段或方法,请使用下面描述的InvocationHandler创建代理,并将当前的BeanFactory传递给它(bean后处理器必须是ApplicationContextAware)。 Inject that proxy in the instance variable, or invoke the setter method with the proxy instance. 在实例变量中插入该代理,或使用代理实例调用setter方法。

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. 您可以为此目的实现BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor ,以便使用适当的限定符将新的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: 以下是发生的情况的摘要:

  1. When the application starts up, implementations of TransactionRules will be discovered and bean definitions will be created for each of them, with a qualifier corresponding to the locale of each implementation. 当应用程序启动时,将发现TransactionRules的实现,并为每个规则创建bean定义,并带有与每个实现的语言环境相对应的限定符。 The bean name for these beans is not relevant as lookup is performed based on type and qualifier. 这些查询的bean名称与类型无关,因为查询是根据类型和限定符执行的。
  2. During execution, set the current locale in a thread-local variable 执行期间,在线程局部变量中设置当前语言环境
  3. Lookup the bean you need (eg. TransactionService). 查找所需的bean(例如TransactionService)。 The post-processor will inject a proxy for each @LocalizedResource instance fields or setter method. 后处理器将为每个@LocalizedResource实例字段或setter方法注入一个代理。
  4. When invoking a method on TransactionService that ends up into some TransactionRules' method, the invocation handler bound to the proxy switches to the correct implementation based on the value stored in the thread-local variable, then delegates the call to that implementation. 在TransactionService上调用的方法最终变成某些TransactionRules的方法时,绑定到代理的调用处理程序将根据线程局部变量中存储的值切换到正确的实现,然后将调用委派给该实现。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM