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

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