Есть ли способ сделать некоторые bean-компоненты, созданные @Bean, различимыми?

Это экзотический вариант использования, поэтому для понимания требуется некоторое терпение и могут потребоваться экзотические решения.

Контекст

Я создаю библиотеку для использования с Spring, которая выполняет автоматические действия над конкретными экземплярами bean-компонентов, присутствующими в контексте, созданными методами @Bean, а не @ComponentScan. По возможности бины должны быть различимы не по типу, а по другим признакам, желательно по аннотациям фабричного метода.

Вот идеальный случай. Например. скажем, есть 2 метода производства бобов:

@Bean
public SomeType makeSome() {...}

@Bean
@Special
public SomeOtherType makeOther() {...}

Здесь второй компонент является особым из-за аннотации @Special в методе, который его создал. Но возможен любой механизм, позволяющий сделать его различимым.

Затем я хочу каким-то образом получить только специальные компоненты.

Предостережение

Я знаю, что если бы все bean-компоненты реализовывали один и тот же интерфейс, я мог бы внедрить их по типу. Но это должно работать максимально прозрачно, требуя как можно меньше изменений в существующем приложении.

Потенциальные подходы

Вот два широких подхода, которые я имею в виду:

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
}

Затем введите все все bean-компоненты типа Wrapper. Проблема здесь, очевидно, в дублировании... В качестве альтернативы я мог бы сгенерировать экземпляр прокси на лету, который реализовал бы интерфейс Wrapper, чтобы он мог одновременно действовать как исходный bean-компонент и как обертка. Я ведь сказал, что согласен и с экзотическими решениями, не так ли?

2) Spring уже отличает кандидатов bean-компонентов от зарегистрированных bean-компонентов (например, @ComponentScan может фильтровать кандидатов по пакету, аннотациям и т. д.). Я надеюсь, возможно, подключиться к этому процессу и получить дескрипторы кандидатов, которые все еще содержат некоторые полезные метаданные (например, их фабричный метод), которые позволят мне позже различать эти экземпляры компонентов.


person kaqqao    schedule 05.02.2018    source источник
comment
Вы пробовали @Qualifier?   -  person chrylis -cautiouslyoptimistic-    schedule 05.02.2018
comment
@chrylis Qualifier сделает одиночный компонент различимым, но мне нужно, чтобы вся группа бобов была различима и вводилась вместе, не зная идентификатора каждого. Так что не применимо.   -  person kaqqao    schedule 05.02.2018
comment
@kaqqao Вы можете применить квалификатор к нескольким компонентам, а затем внедрить квалифицированную коллекцию.   -  person chrylis -cautiouslyoptimistic-    schedule 05.02.2018
comment
Как насчет самостоятельного сканирования компонентов и глубокого сканирования заводских методов на наличие дополнительных аннотаций. Затем вы обогащаете кандидатов в компоненты найденной аннотацией или оборачиваете их соответствующими прокси?   -  person Martin Frey    schedule 06.02.2018
comment
@chrylis В сочетании с тем фактом, что Qualifier можно использовать в качестве мета-аннотации, это действительно правильный подход! Спасибо, что привели меня к этому.   -  person kaqqao    schedule 06.02.2018
comment
Я согласился пометить свой вопрос как дубликат и опубликую свои окончательные выводы по исходному вопросу.   -  person kaqqao    schedule 06.02.2018


Ответы (3)


Похоже, вам нужно использовать @Qualifier, он обеспечивает функциональность для различения компонентов:

@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 на получение beanDefinition из контекста приложения по имени компонента, где вы можете получить практически любую информацию об инстанцировании и аннотациях.

person smt    schedule 05.02.2018
comment
Это точно совпадает с моим первоначальным ходом мыслей. И это, вероятно, сработает (поэтому я проголосовал за это). Но я, к счастью, нашел более простое решение, которое я опубликую на вопрос, который помечен как дубликат. - person kaqqao; 06.02.2018
comment
Изначально я собирался удалить вопрос, но этот ответ был настолько полезен для многих целей, что его нельзя было потерять. Я приму это как ответ, потому что это, безусловно, может решить мою проблему. Спасибо еще раз! - person kaqqao; 06.02.2018

Вот один из способов получить все bean-компоненты с аннотацией @Special.

Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Special.class);

Ссылка: https://stackoverflow.com/a/14236573/1490322

РЕДАКТИРОВАТЬ: приведенный выше ответ работает только тогда, когда класс аннотирован @Special, поэтому он не будет работать для вашего сценария. Но другой ответ на тот же вопрос может сработать. Он использует метаданные из ConfigurableListableBeanFactory для идентификации любых bean-компонентов, чьи методы были аннотированы определенной аннотацией.

person Jose Martinez    schedule 05.02.2018
comment
Разве это не предполагает, что классы компонентов будут аннотированы с помощью @Special? Важно не изменять классы, а только фабричные методы. Я проверю это. - person kaqqao; 05.02.2018
comment
Я думаю, вы правы. Интересно, почему за этот ответ проголосовали так много раз. На этот вопрос есть принятый ответ, который может сработать. - person Jose Martinez; 05.02.2018

Я думаю, как уже упоминалось, аннотация @Qualifier - это способ пойти сюда. Но я покажу другой пример:

@Bean
public SomeType makeSome() {...}

@Bean
public SomeType makeSomeOther() {...}

в вашем компоненте (@Service), где вы хотите этот bean-компонент, вы можете:

@Autowired
@Qualifier("makeSome")
SomeType makeSomeBean;

Как вы видите, два bean-компонента одного типа можно отличить по имени bean-компонента (Bean назначается с тем же именем, что и аннотированный метод @Bean с именем)

person Pavlo Morozov    schedule 05.02.2018