Spring Boot динамически внедряет bean-компоненты через ImportBeanDefinitionRegistrar

Java

Читая исходный код Spring Boot, я увидел, что ImportBeanDefinitionRegistrar широко используется в Spring Boot для реализации динамического внедрения bean-компонентов. Это мощный интерфейс расширения в Spring. В этой статье пойдет речь о его использовании.

Использование в весенней загрузке

В соответствующей автоматической настройке встроенного контейнера Spring Boot есть класс ServletWebServerFactoryAutoConfiguration. Часть кода для этого класса выглядит следующим образом:

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

    // ...
    
    /**
     * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
     * {@link ImportBeanDefinitionRegistrar} for early registration.
     */
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        // 实现BeanFactoryAware的方法,设置BeanFactory
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }

        // 注册一个WebServerFactoryCustomizerBeanPostProcessor
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }

        // 检查并注册Bean
        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
            // 检查指定类型的Bean name数组是否存在,如果不存在则创建Bean并注入到容器中
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }
    }
}

В этом классе автоматической настройки в основном показано основное использование ImportBeanDefinitionRegistrar. Здесь интерфейс в основном используется для регистрации BeanDefinition.

BeanPostProcessorsRegistrar реализовал ImportBeanDefinitionRegistrar Interfaces и интерфейс Bunterfactoryaware. При этом интерфейс реализован BEANFFORTORYAWARARARAWARE для разоблачения конфигурационного объекта весной.

Реализация метода registerBeanDefinitions используется для динамического внедрения бинов, куда внедряются WebServerFactoryCustomizerBeanPostProcessor и ErrorPageRegistrarBeanPostProcessor.

После краткого понимания примера использования в Spring Boot давайте подытожим метод использования и реализуем аналогичную функцию самостоятельно.

ImportBeanDefinitionRegistrar использует

Spring официально реализует механизм динамического внедрения @Component, @Service и других аннотаций через ImportBeanDefinitionRegistrar.

Когда многие сторонние фреймворки интегрируют Spring, они будут сканировать указанные классы через этот интерфейс, а затем регистрировать их в контейнере Spring. Например, интерфейс Mapper в Mybatis и интерфейс FeignClient в springCloud представляют собой настраиваемую логику регистрации, реализованную через этот интерфейс.

Все классы, реализующие этот интерфейс, будут обрабатываться ConfigurationClassPostProcessor.ConfigurationClassPostProcessor реализует интерфейс BeanFactoryPostProcessor, поэтому динамически зарегистрированные компоненты в ImportBeanDefinitionRegistrar инициализируются до компонентов, которые от них зависят, и также могут обрабатываться такими механизмами, как aop и валидатор.

Основные шаги:

  • Реализовать интерфейс ImportBeanDefinitionRegistrar;
  • Реализовать конкретную инициализацию класса через registerBeanDefinitions;
  • Используйте @Import в классе конфигурации, аннотированном @Configuration, чтобы импортировать класс реализации;

Простой пример

Здесь реализована очень простая операция: настройка аннотации @Mapper (не реализация Mapper в Mybatis), реализация функций, аналогичных @Component, и добавление классов аннотаций @Mapper будут автоматически загружены в контейнер Spring.

Сначала создайте аннотацию @mapper.

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Mapper {
}

UserMapper создает классы для использования примечания @Mapper.

@Mapper
public class UserMapper {
}

Определите класс реализации MapperAutoConfigureRegistrar для ImportBeanDefinitionRegistrar. Если вам нужно получить некоторые данные в Spring, вы можете реализовать некоторые интерфейсы Aware, которые реализуют ResourceLoaderAware.

public class MapperAutoConfigureRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        MapperBeanDefinitionScanner scanner = new MapperBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.registerFilters();
        scanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
        scanner.doScan("com.secbro2.learn.mapper");
    }

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

В приведенном выше коде объект ResourceLoader получается с помощью метода setResourceLoader интерфейса ResourceLoaderAware.

В методе registerBeanDefinitions используйте класс реализации класса ClassPathBeanDefinitionScanner для сканирования и получения компонентов, которые необходимо зарегистрировать.

Реализация MapperBeanDefinitionScanner выглядит следующим образом:

public class MapperBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public MapperBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    protected void registerFilters() {
        addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        return super.doScan(basePackages);
    }
}

MapperBeanDefinitionScanner наследует дочерний ClassPathBeanDefinitionScanner и сканирует классы, аннотированные @Mapper.

Параметр метода addIncludeFilter, указанный в MapperBeanDefinitionScanner, — это AnnotationTypeFilter, содержащий Mapper.

Конечно, вы также можете указать типы, которые не загружаются через excludeFilters. Эти два метода предоставляются их родительским классом ClassPathScanningCandidateComponentProvider.

После завершения приведенного выше определения выполняется последний шаг операции импорта. Создайте класс автоматической конфигурации MapperAutoConfig и введите собственный регистратор через @Import.

@Configuration
@Import(MapperAutoConfigureRegistrar.class)
public class MapperAutoConfig {
}

Пока написана функция всего кода, а ниже написан юнит-тест.

@RunWith(SpringRunner.class)
@SpringBootTest
public class MapperAutoConfigureRegistrarTest {

    @Autowired
    UserMapper userMapper;

    @Test
    public void contextLoads() {
        System.out.println(userMapper.getClass());
    }
}

Выполните код модульного теста, вы обнаружите, что печатается следующий журнал:

class com.secbro2.learn.mapper.UserMapper

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

резюме

Конечно, UserMapper здесь — это не интерфейс, и реализация здесь — это не форма реализации в Mybatis. Простой пример для демонстрации функциональности. Следует отметить, что в статье упоминаются два экземпляра реализации, первый — реализация в Spring Boot, а второй — наш экземпляр Mapper. Показаны операции регистрации двух разных методов, но весь процесс использования последователен Читатели должны внимательно следить за вкусом и расширять на этой основе более сложные функции.

Оригинальная ссылка: "Spring Boot динамически внедряет bean-компоненты через ImportBeanDefinitionRegistrar


Программа Новые Горизонты: Захватывающие и растущие нельзя пропустить

程序新视界-微信公众号