相信使用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