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

时间:2011-10-11 01:23:41

标签: java spring

我有一个Java EE + Spring应用程序,它支持XML配置的注释。豆子总是有原型范围。

我现在的应用程序业务规则取决于用户请求所在的国家/地区。所以我会有这样的事情(请记住,这个例子大大简化了):

@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变量中查找正确的国家/地区,并动态地将相应的限定符添加到自动连线请求中。
  3. 在此之后,Spring会尽其所能。如果添加了限定符,则必须考虑这一点;如果没有,流程将照常进行。
  4. 我是在正确的道路上吗?对我有什么想法吗?

    感谢。

2 个答案:

答案 0 :(得分:4)

创建自己的注释,用于装饰实例变量或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代码中搜索对此classe的引用来查找有关其工作原理的示例。
  2. 如果bean存在这样的字段或方法,请使用下面描述的InvocationHandler创建代理,并将当前BeanFactory传递给它(bean后处理器必须是ApplicationContextAware)。在实例变量中注入该代理,或者使用代理实例调用setter方法。
  3. 以下是将用于创建本地化资源的代理的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方法的方法时,绑定到代理的调用处理程序会根据存储在thread-local变量中的值切换到正确的实现,然后将调用委托给该实现。
    5. 不是很琐碎,但它确实有效。这实际上是Spring处理@PersistenceContext的方式,除了实现查找,这是用例的附加功能。

答案 1 :(得分:0)

您可以提供一个Configuration类,它将根据ThreadLocal值返回正确的bean。这假设您使用的是Spring 3.我做了一些测试,以确保在每个请求上调用了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作用域,而值是请求作用域。