【问题标题】:Get bean from ApplicationContext by qualifier通过限定符从 ApplicationContext 中获取 bean
【发布时间】:2016-01-14 19:51:02
【问题描述】:

鉴于此代码:

public interface Service {}

@Component
@Qualifier("NotWanted")
public class NotWantedService implements Service {}

@Component
@Qualifier("Wanted")
public class WantedService implements Service {}

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(NotWantedService.class);
ctx.register(WantedService.class);
ctx.refresh()

我现在该怎么做:

ctx.getBean(Service.class)

以某种方式只能获得带有@Qualifier("Wanted") 的那个而不是带有@Qualifier("NotWanted") 的那个?我特别询问是否可以使用getBean 来实现,而不是注入到一个类中,然后将其用作一种代理。

【问题讨论】:

  • 为什么不按名称获取 bean,因为您使用的是名称常量,即 ctx.getBean("Wanted")
  • @aux 假设您有 50 个地方可以使用ctx.getBean("service1"),现在您想将其更改为ctx.getBean("service2")。那是50个变化。更改限定符仅更改为 2 个 bean 定义(service1service2)。还有其他情况——比如我想获得多个Service 实例,它们是Wanted。它们不能都具有相同的 bean 名称。
  • 好的,我明白了。那么如何引入你自己的“注册表”bean,它包含对你的 bean 的引用并用于通过不同的参数进行查找,比如 spring-data-rest 中的Repositories?还是包装豆?
  • @aux 请参阅问题中的最后一句话。我想知道我是否想取消间接性。换句话说,我有一个可行的解决方案,试图看看是否有更好的解决方案(阅读:更少的代码)。

标签: spring


【解决方案1】:

你可以使用

BeanFactoryAnnotationUtils.qualifiedBeanOfType(ctx.getBeanFactory(), Service.class, "Wanted")

使用ctx.getBeanFactory() 而不是ctx 本身很重要,因为'qualifiedBeanOfType' 方法只能解析ConfigurableListenableBeanFactory 的限定符。

【讨论】:

  • 当我使用application.run() 获取ctx 时,如何获取ctx.getBeanFactory()
  • @UnixAgain 你试过new AnnotationConfigApplicationContext() ctx 的实例吗?
  • @UnixAgain 使用 applicationContext.getAutowireCapableBeanFactory()
  • @AbhishekChatterjee 谢谢。我终于找到原因了。我的 bean 类是作为单例实现的,所以实例是相同的。我的解决方案的更多详细信息发布在此问题下。
  • @TuGordoBello 谢谢。我终于找到原因了。我的 bean 类是作为单例实现的,所以实例是相同的。我的解决方案的更多详细信息发布在此问题下。
【解决方案2】:

@Qualifier 注解的目的不是在通过 ApplicationContext 获取 bean 时使用它。但是由于某些原因您需要此类或类似的功能,我建议一种解决方法。

创建@Wanted@NotWanted注解:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
        ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Wanted {
}

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
            ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotWanted {
}

使用这些新注释来注释您的 bean 类:

@Component
@NotWanted
public class NotWantedService implements Service {}

@Component
@Wanted
public class WantedService implements Service {}

然后,您应该在可以访问 ApplicationContext 的地方添加 2 个方法:

ApplicationContext applicationContext;

private <T> Collection<T>  getBeansByTypeAndAnnotation(Class<T> clazz, Class<? extends Annotation> annotationType){
    Map<String, T> typedBeans = applicationContext.getBeansOfType(clazz);
    Map<String, Object> annotatedBeans = applicationContext.getBeansWithAnnotation(annotationType);
    typedBeans.keySet().retainAll(annotatedBeans.keySet());
    return typedBeans.values();
}

private <T> Optional<T> getBeanByTypeAndAnnotation(Class<T> clazz, Class<? extends Annotation> annotationType) {
    Collection<T> beans = getBeansByTypeAndAnnotation(clazz, annotationType);
    return beans.stream().findFirst();
}

现在您可以使用它们通过注释获取 bean 或一个 bean,然后输入如下:

Collection<Service> services = getBeansByTypeAndAnnotation(Service.class, Wanted.class);

Service service = getBeanByTypeAndAnnotation(Service.class, Wanted.class);

这可能不是解决问题的最佳方式。但由于我们无法通过限定符从 ApplicationContext 获取 bean 并输入“out of box”,这是执行此操作的方法之一。

【讨论】:

  • 谢谢@Rozart - 我已经试过了。不幸的是,这并没有考虑到@Qualifier。取bean的名字,用@Bean(name="abc")指定,不一样。
  • 这与@Bean(name="abc") 相同,但语义不同。不能有两个命名相同的 bean,但可以有多个具有相同限定的 bean。也就是说,我想要 10 种不同的服务符合“通缉”,但根本不称为“通缉”。
  • 我已经更新了答案。也许这会对你有所帮助。 :)
  • 谢谢罗扎特。您能否澄清一下:“通过 ApplicationContext 获取 bean 时使用 @Qualifier 注释的目的不是。”?目前可能不支持,但为什么会在逻辑上不一致?
  • 这正是我所说的“这不是@Qualifier 的目的”的意思——目前不支持。从逻辑上讲,IMO 是一致的,但尚不支持。希望我的回答对你有所帮助。 ;)
【解决方案3】:

如果你想从上下文中获取 bean 而不是注入,更好的方法是在 @Component 注释中定义 bean 名称并从上下文中通过名称获取它。在大多数情况下,@Qualifier 用于注入。

【讨论】:

    【解决方案4】:

    就我而言,我有两个合格的一类豆,例如

    @Configuration
    public class AConfig {
        @Bean(name = "a")
        public Hello hello1() {
            return new Hello();
        }
    
        @Bean(name = "b")
        public Hello hello2() {
            return new Hello();
        }
    }
    

    然后我可以通过

    ApplicationContext context = SpringApplication.run(AutoConfiguration.class); 
    var aHello = context.getBean("a", Hello.class); 
    var bHello = context.getBean("b", Hello.class);
    

    这是最简单的方法。或者您可以执行以下相同的操作:

    var aHello = context.getBeansOfType(Hello.class).getBean("a");
    var bHello = context.getBeansOfType(Hello.class).getBean("b");
    

    或者您也可以像@Dmitry Ovchinnikov 所说的那样:

    BeanFactoryAnnotationUtils.qualifiedBeanOfType(ctx.getBeanFactory(), Service.class, "Wanted")
    

    在这种情况下,ctx.getBeanFactory() 可以替换为 context.getAutowireCapableBeanFactory()

    【讨论】:

    • context.get() 方法给了我一个BeanDefinitionOverrideException
    • @bart-kosmala 抱歉是context.getBean()
    • @UnixAgain,您提供 bean 名称,而不是 bean 限定符。您使用 org.springframework.beans.factory.annotation.Qualifier 注解指定限定符。
    • 您的解决方案看起来很有趣,虽然我不知道 AutoConfiguration.class 应该是什么(尝试了一些依赖项,尽管我无法导入该类)
    【解决方案5】:

    在 Spring 中最接近的规范方法是使用实​​用程序类 BeanFactoryAnnotationUtils ... 但遗憾的是,这只适用于直接使用 @Qualifier 注释值参数(因此参数是字符串)。

    @Rozart is recommending 是最好的方法,真正类似的东西应该在 BeanFactoryAnnotationUtils 中。我只在有人登陆这里并且确实想直接使用@Qualifier(以及随之而来的所有bean别名)的情况下包含上述答案。

    我建议在 Spring 提交功能请求(我会,但我认为他们可能厌倦了我的窃听 :-))。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 2015-07-01
      • 2019-07-22
      • 2021-05-21
      相关资源
      最近更新 更多