繁体   English   中英

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

[英]Dynamically defining which bean to autowire in Spring (using qualifiers)

我有一个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变量中查找正确的国家/地区,并将动态地将适当的Qualifier添加到自动装配请求中。
  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代码中搜索对该类的引用来查找有关其工作方式的示例。
  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名称与类型无关,因为查询是根据类型和限定符执行的。
  2. 执行期间,在线程局部变量中设置当前语言环境
  3. 查找所需的bean(例如TransactionService)。 后处理器将为每个@LocalizedResource实例字段或setter方法注入一个代理。
  4. 在TransactionService上调用的方法最终变成某些TransactionRules的方法时,绑定到代理的调用处理程序将根据线程局部变量中存储的值切换到正确的实现,然后将调用委派给该实现。

并不是很琐碎,但是可以。 实际上,这是Spring处理@PersistenceContext的方式,除了实现查找(这是用例的附加功能)之外。

您可以提供一个Configuration类,该类将基于ThreadLocal值返回正确的bean。 假设您使用的是Spring3。我做了一点测试,以确保在每个请求上都调用了provider方法。 这就是我所做的。

@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范围的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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