有没有办法让@Bean 创建的一些 bean 可区分?
Is there a way to make some beans created by @Bean distinguishable?
这是一个奇异的用例,因此需要一些耐心才能理解,并且可能需要奇异的解决方案。
The Context
我正在制作一个与 Spring 一起使用的库,它对上下文中存在的特定 bean 实例执行自动操作,由 @Bean
方法而不是 @ComponentScan
创建。如果可能的话,bean 应该不是通过类型来区分的,而是通过其他方式来区分的,最好是工厂方法上的注释 .
这是理想情况。例如。假设有 2 种豆子生产方法:
@Bean
public SomeType makeSome() {...}
@Bean
@Special
public SomeOtherType makeOther() {...}
这里,第二个 bean 是 special 因为创建它的方法上有 @Special
注释。但是任何使其可区分的机制都是一种选择。
然后,我想以某种方式只获得 特殊 豆子。
The Caveat
我知道如果所有 bean 都实现相同的接口,我可以按类型注入它们。但这应该尽可能透明地工作,尽可能少地改变现有应用程序。
The potential approaches
这是我想到的两种主要方法:
1) 参与注册 bean 的过程,并将 bean 实例透明地包装到某种容器中(我很确定这部分是可行的)。例如
public void registerBean(Object bean, ApplicationContext ctx) {
ctx.register(bean); //do the usual
ctx.register(new Wrapper(bean); //register it wrapped as well
}
然后,注入所有Wrapper
类型的bean。这里的问题显然是重复......或者我可以动态生成一个代理实例来实现一个 Wrapper
接口,所以它可以同时充当原始 bean 和 作为包装器。我确实说过我也可以接受异国情调的解决方案,不是吗?
2) Spring 已经将 bean candidates 与实际注册的 bean 区分开来(例如 @ComponentScan
可以通过包、注释等过滤候选者)。我希望能够参与这个过程并获得 candidate 描述符,这些描述符仍然包含一些有用的元数据(比如它们的工厂方法),这将允许我稍后区分那些 bean 实例。
看来你需要使用 @Qualifier
它提供了区分 bean 的功能:
@Bean
@Qualifier("special")
class MyBean {}
@Bean
class OtherBean {
@Qualifier("special")
private MyBean bean;
}
您可以在此处阅读更多信息:https://spring.io/blog/2014/11/04/a-quality-qualifier
UPD(明白你在说什么:))
您可能想看看 BeanDefinitionRegistryPostProcessor
这里是一个用法示例:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static java.util.Collections.unmodifiableMap;
/**
* This is hack to collect all beans of some type in single map without eager initialization of those beans
*
* Usage:
* 1. Register new bean of type {@link ServiceTrackingBeanPostProcessor} parametrized with class of
* beans you want to collect
* 2. Now you can inject {@link ServiceTracker} parametrized with your type anywhere
*
* @param <T> Located type
*/
public class ServiceTrackingBeanPostProcessor<T> implements BeanPostProcessor, BeanDefinitionRegistryPostProcessor {
private final ConcurrentMap<String, T> registeredBeans = new ConcurrentHashMap<>();
private final Class<T> clazz;
private final String beanName;
public ServiceTrackingBeanPostProcessor(Class<T> clazz) {
this.clazz = clazz;
beanName = "locatorFor" + clazz.getCanonicalName().replace('.', '_');
}
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return o;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
if (!clazz.isInstance(o)) {
return o;
}
registeredBeans.putIfAbsent(s, (T) o);
return o;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
AnnotatedGenericBeanDefinition def = new AnnotatedGenericBeanDefinition(Wrapper.class);
registry.registerBeanDefinition(beanName, def);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerSingleton(beanName, new Wrapper(unmodifiableMap(registeredBeans)));
}
private class Wrapper extends AbstractServiceTracker<T> {
public Wrapper(Map<String, T> services) {
super(services);
}
}
}
你只需要将检查条件从clazz.isInstance
更改为通过bean名称从应用程序上下文中获取beanDefinition,你可以获得几乎所有关于实例化和注释的信息
这是获取所有带有 @Special
注释的 bean 的一种方法。
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Special.class);
参考:
编辑:以上答案似乎仅在 class 注释为 @Special
时才有效,因此它不适用于您的情况。但是 this other answer 对同一个问题可能有效。它使用来自 ConfigurableListableBeanFactory 的元数据来识别其方法使用特定注释进行注释的任何 bean。
我认为如前所述,@Qualifier 注释是一种可行的方式。但我会展示不同的例子:
@Bean
public SomeType makeSome() {...}
@Bean
public SomeType makeSomeOther() {...}
在您想要此 bean 的组件 (@Service) 中,您可以:
@Autowired
@Qualifier("makeSome")
SomeType makeSomeBean;
如你所见,两个相同类型的bean可以通过bean名称来区分(Bean分配的名称与@Bean注解的方法命名相同)
这是一个奇异的用例,因此需要一些耐心才能理解,并且可能需要奇异的解决方案。
The Context
我正在制作一个与 Spring 一起使用的库,它对上下文中存在的特定 bean 实例执行自动操作,由 @Bean
方法而不是 @ComponentScan
创建。如果可能的话,bean 应该不是通过类型来区分的,而是通过其他方式来区分的,最好是工厂方法上的注释 .
这是理想情况。例如。假设有 2 种豆子生产方法:
@Bean
public SomeType makeSome() {...}
@Bean
@Special
public SomeOtherType makeOther() {...}
这里,第二个 bean 是 special 因为创建它的方法上有 @Special
注释。但是任何使其可区分的机制都是一种选择。
然后,我想以某种方式只获得 特殊 豆子。
The Caveat
我知道如果所有 bean 都实现相同的接口,我可以按类型注入它们。但这应该尽可能透明地工作,尽可能少地改变现有应用程序。
The potential approaches
这是我想到的两种主要方法:
1) 参与注册 bean 的过程,并将 bean 实例透明地包装到某种容器中(我很确定这部分是可行的)。例如
public void registerBean(Object bean, ApplicationContext ctx) {
ctx.register(bean); //do the usual
ctx.register(new Wrapper(bean); //register it wrapped as well
}
然后,注入所有Wrapper
类型的bean。这里的问题显然是重复......或者我可以动态生成一个代理实例来实现一个 Wrapper
接口,所以它可以同时充当原始 bean 和 作为包装器。我确实说过我也可以接受异国情调的解决方案,不是吗?
2) Spring 已经将 bean candidates 与实际注册的 bean 区分开来(例如 @ComponentScan
可以通过包、注释等过滤候选者)。我希望能够参与这个过程并获得 candidate 描述符,这些描述符仍然包含一些有用的元数据(比如它们的工厂方法),这将允许我稍后区分那些 bean 实例。
看来你需要使用 @Qualifier
它提供了区分 bean 的功能:
@Bean
@Qualifier("special")
class MyBean {}
@Bean
class OtherBean {
@Qualifier("special")
private MyBean bean;
}
您可以在此处阅读更多信息:https://spring.io/blog/2014/11/04/a-quality-qualifier
UPD(明白你在说什么:))
您可能想看看 BeanDefinitionRegistryPostProcessor
这里是一个用法示例:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static java.util.Collections.unmodifiableMap;
/**
* This is hack to collect all beans of some type in single map without eager initialization of those beans
*
* Usage:
* 1. Register new bean of type {@link ServiceTrackingBeanPostProcessor} parametrized with class of
* beans you want to collect
* 2. Now you can inject {@link ServiceTracker} parametrized with your type anywhere
*
* @param <T> Located type
*/
public class ServiceTrackingBeanPostProcessor<T> implements BeanPostProcessor, BeanDefinitionRegistryPostProcessor {
private final ConcurrentMap<String, T> registeredBeans = new ConcurrentHashMap<>();
private final Class<T> clazz;
private final String beanName;
public ServiceTrackingBeanPostProcessor(Class<T> clazz) {
this.clazz = clazz;
beanName = "locatorFor" + clazz.getCanonicalName().replace('.', '_');
}
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return o;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
if (!clazz.isInstance(o)) {
return o;
}
registeredBeans.putIfAbsent(s, (T) o);
return o;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
AnnotatedGenericBeanDefinition def = new AnnotatedGenericBeanDefinition(Wrapper.class);
registry.registerBeanDefinition(beanName, def);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerSingleton(beanName, new Wrapper(unmodifiableMap(registeredBeans)));
}
private class Wrapper extends AbstractServiceTracker<T> {
public Wrapper(Map<String, T> services) {
super(services);
}
}
}
你只需要将检查条件从clazz.isInstance
更改为通过bean名称从应用程序上下文中获取beanDefinition,你可以获得几乎所有关于实例化和注释的信息
这是获取所有带有 @Special
注释的 bean 的一种方法。
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Special.class);
参考:
编辑:以上答案似乎仅在 class 注释为 @Special
时才有效,因此它不适用于您的情况。但是 this other answer 对同一个问题可能有效。它使用来自 ConfigurableListableBeanFactory 的元数据来识别其方法使用特定注释进行注释的任何 bean。
我认为如前所述,@Qualifier 注释是一种可行的方式。但我会展示不同的例子:
@Bean
public SomeType makeSome() {...}
@Bean
public SomeType makeSomeOther() {...}
在您想要此 bean 的组件 (@Service) 中,您可以:
@Autowired
@Qualifier("makeSome")
SomeType makeSomeBean;
如你所见,两个相同类型的bean可以通过bean名称来区分(Bean分配的名称与@Bean注解的方法命名相同)