【问题标题】:Dynamically defining which bean to autowire in Spring (using qualifiers)在 Spring 中动态定义要自动装配的 bean(使用限定符)
【发布时间】:2011-10-11 01:23:40
【问题描述】:

我有一个 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 变量中查找正确的国家,并将适当的限定符动态添加到自动连接请求中。
  3. 在那之后,Spring 将发挥它通常的魔力。如果添加了限定词,则必须予以考虑;如果没有,流程将照常进行。

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

谢谢。

【问题讨论】:

    标签: java spring


    【解决方案1】:

    创建您自己的注释,用于装饰实例变量或设置方法,然后是处理注释并注入通用代理的后处理器,该代理在运行时解析正确的实现并将调用委托给它。

    @Component
    public class TransactionService {
      @LocalizedResource
      private TransactionRules rules;
      //..
    }
    
    @Retention(RUNTIME)
    @Target({FIELD, METHOD})
    public @interface LocalizedResource {}
    

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

    1. 自省 bean 类以查找使用 @LocalizedResource 注释的实例变量或设置方法。将结果存储在由类名索引的缓存(只是一个映射)中。为此,您可以使用 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,以便使用适当的限定符将新的BeanDefinitions 添加到注册表中,每个实现一个区域感知接口。您可以通过以下命名约定来猜测实现的语言环境:如果一个支持语言环境的接口被称为 TransactionRules,那么在同一个包中的实现可能会被命名为 TransactionRules_ISOCODE。

    如果您负担不起这样的命名约定,您将需要某种类路径扫描 + 一种猜测给定实现的语言环境的方法(可能是实现类上的注释)。类路径扫描是可能的,但相当复杂和缓慢,所以尽量避免它。

    以下是所发生情况的摘要:

    1. 当应用程序启动时,将发现 TransactionRules 的实现,并为它们中的每一个创建 bean 定义,并使用与每个实现的语言环境相对应的限定符。这些 bean 的 bean 名称不相关,因为查找是基于类型和限定符执行的。
    2. 在执行期间,在线程局部变量中设置当前语言环境
    3. 查找您需要的 bean(例如 TransactionService)。后处理器将为每个 @LocalizedResource 实例字段或 setter 方法注入一个代理。
    4. 当调用 TransactionService 上的某个方法最终进入某个 TransactionRules 方法时,绑定到代理的调用处理程序会根据存储在线程局部变量中的值切换到正确的实现,然后将调用委托给该实现。

    不是很简单,但它确实有效。这实际上是 Spring 处理 @PersistenceContext 的方式,除了实现查找,这是您用例的附加功能。

    【讨论】:

    • 现在2017年了,难道没有更简单的方法来实现这一点吗??
    【解决方案2】:

    您可以提供一个配置类,该类将根据 ThreadLocal 值返回正确的 bean。这假设您使用的是 Spring 3。我做了一个小测试以确保在每个请求上都调用了提供程序方法。这就是我所做的。

    @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++ } );
        }
    }
    

    并引用了我的Controller中的值如下。

    @Resource( name="joel" )
    private List<String> joel;
    

    在提供者的实现中,您可以检查 ThreadLocal 的语言环境并返回正确的 TransactionRules 对象或类似的东西。 ScopedProxy 的东西是因为我正在注入一个控制器,它是单例范围的,而值是请求范围的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-15
      相关资源
      最近更新 更多