ImportBeanDefinitionRegistrar+SPI簡化Spring開發

相信使用Spring的小夥伴會在開發中遇到配置各式各樣的bean的情況,例如配置數據庫,配置redis,配置AOP等。springboot已經很大程度上幫我們簡化了配置,但實際業務開發中是否有其無法滿足的地方呢?或者在傳統spring項目中如何簡化配置?如一些定製化的功能,再如去xml化。本文嘗試使用ImportBeanDefinitionRegistrar+SPI的方式來實現。後文會有更多的應用實例。

前置要求:需要你對spring的ImportBeanDefinitionRegistrar以及java的SPI機制有一定的瞭解,不太瞭解的可以先搜搜相關資源。兩者是動態擴展的利器,建議多研究。

首先定義一個註解EnableAutoRegistrar,這樣在@Configuration的類上引入@EnableAutoRegistrar註解來觸發我們的動態擴展。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ConfigurationRegistrar.class)
public @interface EnableAutoRegistrar {
}

這裏使用spring的Import機制,會自動注入ConfigurationRegistrar的bean,在ConfigurationRegistrar中我們實現ImportBeanDefinitionRegistrar接口以及其它一些aware接口來獲取上下文並實現動態定義bean。

public class ConfigurationRegistrar implements ImportBeanDefinitionRegistrar,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware {

    private ConfigurationRegisterHandler handler = ServiceBootstrap.loadPrimary(ConfigurationRegisterHandler.class);

    @Setter
    private ResourceLoader resourceLoader;
    @Setter
    private BeanFactory beanFactory;
    @Setter
    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        handler.registerBeanDefinitions(new RegisterBeanDefinitionContext()
                .setImportingClassMetadata(importingClassMetadata)
                .setRegistry(registry)
                .setResourceLoader(resourceLoader)
                .setBeanFactory(beanFactory)
                .setEnvironment(environment));
    }
}

這裏我們並沒有直接寫動態定義bean的處理實際邏輯,爲方便擴展,這裏用的是SPI機制,從META-INF/services下加載ConfigurationRegisterHandler來委託其處理。
ConfigurationRegisterHandler定義:

public interface ConfigurationRegisterHandler extends Ordered {

    /**
     * 處理註冊
     */
    void registerBeanDefinitions(RegisterBeanDefinitionContext context);

}

Ordered是爲方便排序按順序處理
其實現:

public class DefaultConfigurationRegisterHandler implements ConfigurationRegisterHandler {

    private static final String HANDLER_DIR = "META-INF/coding4j/";

    @Override
    public void registerBeanDefinitions(RegisterBeanDefinitionContext context) {
        final Multimap<String, ConfigurationRegisterHandler> handlerMap =
                ServiceBootstrap.loadAllOrdered(HANDLER_DIR, ConfigurationRegisterHandler.class);
        if (handlerMap != null) {
            // 同一類的只取第一個Handler處理,所有Handler會再排序處理
            handlerMap.keySet().stream()
                    .map(k -> handlerMap.get(k).iterator().next())
                    .sorted(Comparator.comparingInt(x -> x.getOrder()))
                    .forEach(x -> x.registerBeanDefinitions(context));
        }
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

這裏也只是SPI的入口,會取在META-INF/coding4j/中已經定義好的具體的Handler處理。
這樣利用SPI機制,我們只要在META-INF/coding4j/中定義ConfigurationRegisterHandler接口的實現,即可被發現並被引入使用。
詳細實例參考GitHub

一些應用:
Spring動態注入外部文件到classpath
Spring動態從配置文件定義bean
Spring自動裝配guava EventBus
Spring自定義註解定義AOP配置去xml
Spring自動配置Redis

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章