Java 动态定义要在Spring中自动关联的bean(使用限定符)

Java 动态定义要在Spring中自动关联的bean(使用限定符),java,spring,Java,Spring,我有一个JavaEE+Spring应用程序,它支持注释而不是XML配置。bean始终具有原型范围 我现在在我的应用程序中有了业务规则,这些规则取决于用户请求来自的国家。所以我会有这样的想法(请记住,这个例子被大大简化了): 我正在寻找一种方法,使自动布线机制根据当前请求的国家自动注入正确的bean(本例中为美国或加拿大)。国家/地区将存储在ThreadLocal变量中,并且它将在每个请求中更改。对于所有没有自己特定规则的国家来说,也会有一个全球等级 我想我必须定制Spring决定如何创建它将注入

我有一个JavaEE+Spring应用程序,它支持注释而不是XML配置。bean始终具有原型范围

我现在在我的应用程序中有了业务规则,这些规则取决于用户请求来自的国家。所以我会有这样的想法(请记住,这个例子被大大简化了):

我正在寻找一种方法,使自动布线机制根据当前请求的国家自动注入正确的bean(本例中为美国或加拿大)。国家/地区将存储在ThreadLocal变量中,并且它将在每个请求中更改。对于所有没有自己特定规则的国家来说,也会有一个全球等级

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

  • 在Spring实例化对象之前,必须调用我自己的自定义代码
  • 如果我检测到被请求的接口有多个实现,我将在ThreadLocal变量中查找正确的国家,并将动态地向autowire请求添加适当的限定符
  • 在那之后,春天将施展它所有的魔法。如果添加了限定符,则必须将其考虑在内;如果没有,流程将照常进行
  • 我走对了吗?我有什么想法吗


    谢谢。

    您可以提供一个配置类,该类将根据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++ } );
        }
    }
    
    @配置
    公共类应用程序配置
    {
    专用静态整数计数器=0;
    @Bean(name=“joel”)
    @范围(value=“request”,proxyMode=ScopedProxyMode.TARGET\u类)
    列表getJoel()
    {
    返回Arrays.asList(新字符串[]{“Joel”+counter++});
    }
    }
    
    并引用控制器中的值,如下所示

    @Resource( name="joel" )
    private List<String> joel;
    
    @Resource(name=“joel”)
    私人名单乔尔;
    

    在提供程序的实现中,可以检查ThreadLocal的区域设置,并返回正确的TransactionRules对象或类似的内容。ScopedProxy的东西是因为我注入了一个控制器,它是单例范围的,而值是请求范围的。

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

    @Component
    public class TransactionService {
      @LocalizedResource
      private TransactionRules rules;
      //..
    }
    
    @Retention(RUNTIME)
    @Target({FIELD, METHOD})
    public @interface LocalizedResource {}
    
    以下是bean后处理器中的
    postProcessBeforeInitialization(bean,beanName)
    方法的算法:

  • 内省bean类,以查找用@LocalizedResource注释的实例变量或setter方法。将结果存储在按类名编制索引的缓存(只是一个映射)中。为此,您可以使用Spring的
    InjectionMetadata
    。您可以通过在spring代码中搜索对该类的引用来查找它如何工作的示例
  • 如果bean存在这样的字段或方法,则使用下面描述的调用处理程序创建代理,并将其传递给当前的BeanFactory(bean后处理器必须是ApplicationContextAware)。将该代理注入实例变量中,或使用代理实例调用setter方法
  • 下面是用于创建本地化资源的代理的调用处理程序

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

    下一步是自动检测给定接口的实现,这些实现依赖于本地,并使用与区域设置对应的限定符注册它们。为此,您可以实现
    BeanDefinitionRegistryPostProcessor
    BeanFactoryPostProcessor
    ,以便使用适当的限定符向注册表添加新的
    BeanDefinition
    s,每种语言环境感知接口实现一个限定符。您可以通过以下命名约定猜测实现的区域设置:如果区域设置感知接口称为TransactionRules,则实现可以在同一个包中命名为TransactionRules_ISOCODE

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

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

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

  • 不是很琐碎,但它是有效的。这实际上是Spring处理@PersistenceContext的方式,除了实现查找,这是用例的一个附加功能。

    现在是2017年,难道没有更简单的方法来实现这一点吗??
    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
      }
    }