在Spring中动态定义要自动装配的bean(使用限定符) [英] Dynamically defining which bean to autowire in Spring (using qualifiers)

查看:60
本文介绍了在Spring中动态定义要自动装配的bean(使用限定符)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Java EE + Spring应用程序,它比XML配置更喜欢注释.Bean始终具有原型作用域.

I have a Java EE + Spring app that favors annotations over XML configuration. The beans always have prototype scope.

我现在在我的应用程序中有业务规则,具体取决于用户发出请求的国家/地区.所以我会有这样的事情(请记住,此示例已大大简化):

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 {
     //..
}

我一直在寻找一种使自动装配机制根据当前请求的国家/地区自动注入合适的bean(在本例中为美国或加拿大)的方法.该国家/地区将存储在ThreadLocal变量中,并且在每个请求中都会更改.对于所有没有自己特定规则的国家,还将有一个全球班级.

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. The country would be stored in a ThreadLocal variable, and it would change in each request. There would also be a global class, for all countries that didn't have their own particular rules.

我想我将不得不定制Spring决定如何创建将注入的对象的方式.我发现做到这一点的唯一方法是使用FactoryBean,但这并不是我所希望的(不够通用).我希望做这样的事情:

I imagine I would have to customize the way Spring decides how to create the objects it will inject. The only way I found to do this was using FactoryBean, but that was not quite what I hoped for (not generic enough). I was hoping to do something like this:

  1. 在Spring实例化对象之前,必须调用我自己的自定义代码.
  2. 如果我检测到所请求的接口具有多个实现,则可以在ThreadLocal变量中查找正确的国家/地区,并将动态地将适当的Qualifier添加到自动装配请求中.
  3. 在那之后,Spring将尽其所能.如果添加了限定词,则必须考虑到这一点;如果没有,流程将照常进行.

我在正确的道路上吗?对我有什么想法吗?

Am I in the right path? Any ideas for me on this?

谢谢.

推荐答案

创建您自己的用于装饰实例变量或setter方法的注释,然后由后处理器处理该注释并注入一个通用代理,以解析正确的在运行时实施,并将调用委派给它.

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.

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

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

这是bean后处理器中 postProcessBeforeInitialization(bean,beanName)方法的算法:

Here is the algorithm for the postProcessBeforeInitialization(bean, beanName) method in your bean post-processor :

  1. 自检bean类,以查找用@LocalizedResource注释的实例变量或setter方法.将结果存储在按类名称索引的高速缓存(仅是地图)中.为此,您可以使用Spring的 InjectionMetadata .您可以通过在春季代码中搜索对该类的引用来查找有关其工作方式的示例.
  2. 如果该bean存在这样的字段或方法,请使用下面描述的InvocationHandler创建代理,并将当前的BeanFactory传递给它(bean后处理器必须是ApplicationContextAware).在实例变量中插入该代理,或使用代理实例调用setter方法.
  1. Introspect the bean class in order to find instance variables or setter methods which are annotated with @LocalizedResource. Store the result in a cache (just a map) indexed by the class name. You can use Spring's InjectionMetadata for this purpose. You can look for examples on how it works by searching references to this classe in spring code.
  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). Inject that proxy in the instance variable, or invoke the setter method with the proxy instance.

这是用于创建本地化资源的代理的InvocationHandler.

Here is the InvocationHandler for the proxy that will be used to create localized resources.

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类型.

You may need to make some more controls over the bean type, or add the requested bean type in the InvocationHandler.

下一步是自动检测与本地相关的给定接口的实现,并将其注册到与该语言环境相对应的限定符中.为此,您可以实现 BeanDefinitionRegistryPostProcessor BeanFactoryPostProcessor ,以便使用适当的限定符将新的 BeanDefinition 添加到注册表中,每个限定符一个语言环境感知接口的实现.您可以通过遵循以下命名约定来猜测实现的语言环境:如果将支持语言环境的接口称为TransactionRules,则在同一程序包中的实现可能被命名为TransactionRules_ISOCODE.

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 BeanDefinitions to the registry, with proper qualifier, one for each implementation of locale-aware interfaces. 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.

如果您负担不起这样的命名约定,则需要某种类路径扫描+一种猜测给定实现的语言环境的方法(可能是实现类的注释).类路径扫描是可能的,但是非常复杂且缓慢,因此请避免使用它.

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. 当应用程序启动时,将发现TransactionRules的实现,并为每个规则创建bean定义,并带有与每个实现的语言环境相对应的限定符.这些bean的bean名称是无关紧要的,因为查找是根据类型和限定符执行的.
  2. 在执行过程中,将当前语言环境设置为线程局部变量
  3. 查找所需的bean(例如TransactionService).后处理器将为每个@LocalizedResource实例字段或setter方法注入代理.
  4. 当在TransactionService上调用一个最终成为某些TransactionRules方法的方法时,绑定到代理的调用处理程序将根据存储在线程局部变量中的值切换到正确的实现,然后将调用委派给该实现.

并非微不足道,但可以.实际上,这是Spring处理@PersistenceContext的方式,除了实现查找(这是您的用例的附加功能)之外.

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中动态定义要自动装配的bean(使用限定符)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆