Пользовательский загрузчик компонентов Spring Boot

Я использую JDBI в тандеме с Spring Boot. Я следовал этому руководству, в результате чего мне пришлось создать класс: JdbiConfig в котором , для каждого дао, необходимого в контексте приложения, вы должны добавить:

@Bean
public SomeDao someDao(Jdbi jdbi) {
    return jdbi.onDemand(SomeDao.class);
}

Мне было интересно, есть ли способ в Spring Boot создать собственный процессор для создания bean-компонентов и поместить их в контекст приложения. У меня есть две идеи о том, как это может работать:

  1. Аннотируйте DAO пользовательской аннотацией @JdbiDao и напишите что-нибудь, чтобы подобрать их. Я попытался просто вручную внедрить их в запуск приложения, но проблема в том, что они могут не загрузиться вовремя для внедрения, поскольку они не распознаются во время сканирования класса.
  2. Создайте класс JdbiDao, который мог бы расширять любой интерфейс репозитория. Затем аннотируйте интерфейсы стандартными @Repository и создайте пользовательский процессор для их загрузки посредством Jdbi#onDemand.

Это две мои идеи, но я не знаю, как это сделать. Я застрял в ручном создании bean-компонента? Это было решено раньше?


person JDrost1818    schedule 30.04.2020    source источник
comment
BeanFactoryPostProcessor?   -  person Eugene    schedule 27.05.2021
comment
Вы уже проверили проект spring-data-jdbc и не охватывает ли он то, что вы будете использовать с jdbi? Возможно, проще (с точки зрения разработки и тестирования) придерживаться экосистемы Spring.   -  person Nico Van Belle    schedule 28.05.2021


Ответы (1)


Стратегия состоит в том, чтобы сканировать ваш путь к классам на предмет интерфейса dao, а затем зарегистрировать их как bean-компоненты.

Нам нужно: BeanDefinitionRegistryPostProcessor для регистрации дополнительного определения компонента и FactoryBean, чтобы создать экземпляр компонента jdbi dao.

  1. Отметьте свой интерфейс дао с помощью @JdbiDao
@JdbiDao
public interface SomeDao {
}
  1. Определите FactoryBean для создания jdbi dao
public class JdbiDaoBeanFactory implements FactoryBean<Object>, InitializingBean {

    private final Jdbi jdbi;
    private final Class<?> jdbiDaoClass;
    private volatile Object jdbiDaoBean;

    public JdbiDaoBeanFactory(Jdbi jdbi, Class<?> jdbiDaoClass) {
        this.jdbi = jdbi;
        this.jdbiDaoClass = jdbiDaoClass;
    }

    @Override
    public Object getObject() throws Exception {
        return jdbiDaoBean;
    }

    @Override
    public Class<?> getObjectType() {
        return jdbiDaoClass;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        jdbiDaoBean = jdbi.onDemand(jdbiDaoClass);
    }
}
  1. Сканировать classpath для @JdbiDao аннотированных интерфейсов:
public class JdbiBeanFactoryPostProcessor
        implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware, BeanClassLoaderAware, BeanFactoryAware {

    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;
    private Environment environment;
    private ClassLoader classLoader;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                // By default, scanner does not accept regular interface without @Lookup method, bypass this
                return true;
            }
        };
        scanner.setEnvironment(environment);
        scanner.setResourceLoader(resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(JdbiDao.class));
        List<String> basePackages = AutoConfigurationPackages.get(beanFactory);
        basePackages.stream()
                .map(scanner::findCandidateComponents)
                .flatMap(Collection::stream)
                .forEach(bd -> registerJdbiDaoBeanFactory(registry, bd));
    }

    private void registerJdbiDaoBeanFactory(BeanDefinitionRegistry registry, BeanDefinition bd) {
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd;
        Class<?> jdbiDaoClass;
        try {
            jdbiDaoClass = beanDefinition.resolveBeanClass(classLoader);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        beanDefinition.setBeanClass(JdbiDaoBeanFactory.class);
        // Add dependency to your `Jdbi` bean by name
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("jdbi"));
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(jdbiDaoClass));

        registry.registerBeanDefinition(jdbiDaoClass.getName(), beanDefinition);
    }
}
  1. Импортируйте наш JdbiBeanFactoryPostProcessor
@SpringBootApplication
@Import(JdbiBeanFactoryPostProcessor.class)
public class Application {
}
person Mạnh Quyết Nguyễn    schedule 29.05.2021
comment
Это работает! Я сузился до этого, но не знал, как добавить bean-компонент JDBI в качестве аргумента при заводской регистрации. Благодарю вас! - person JDrost1818; 30.05.2021
comment
@ JDrost1818 уже поздно, но рад, что это помогает - person Mạnh Quyết Nguyễn; 30.05.2021