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