聊聊自定義實現的SPI如何與spring進行整合

linyb極客之路發表於2021-11-10

前言

上一篇文章主要聊聊如何實現一個帶有攔截器功能的SPI。今天就來聊聊自定義的SPI如何與spring整合。

思考:我們實現的SPI要整合spring哪些東西?或者我們要利用spring的哪些特性實現我們哪些東西?

spring除了被大家熟知的IOC和AOP之外,還有它也提供了很豐富的擴充套件點,比如各種後置處理器,今天我們就聊聊大家相對熟悉的話題,如何通過自定義註解把SPI注入到spring容器中

整合思路

1、自定義註解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Activate {

    String value() default "";
}

2、自定義bean定義掃描器

public class ActivateClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {


    public ActivateClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }


    @SneakyThrows
    @Override
    protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
        super.registerBeanDefinition(definitionHolder, registry);
        Class clz = Class.forName(definitionHolder.getBeanDefinition().getBeanClassName());
        Activate activate = AnnotationUtils.findAnnotation(clz,Activate.class);
        if(ObjectUtils.isNotEmpty(activate) && StringUtils.isNotBlank(activate.value())){
            String activateName = getEnvironment().resolvePlaceholders(activate.value());
            registry.registerBeanDefinition(activateName,definitionHolder.getBeanDefinition());
        }
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return super.isCandidateComponent(beanDefinition) && beanDefinition.getMetadata()
                .hasAnnotation(Activate.class.getName());
    }

3、定義ImportBeanDefinitionRegistrar

public class SpiRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Set<String> basePackages = this.getBasePackages(importingClassMetadata);
        String[] packages = {};
        SpiBeanUtils.registerActivateInstances(registry,environment,basePackages.toArray(packages));
    }

4、自定義enabled註解

@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Import(SpiRegister.class)
public @interface EnableSpi {

  
    String[] value() default {};

 
    String[] basePackages() default {};

  
    Class<?>[] basePackageClasses() default {};
}

示例演示

1、在需要注入到spring容器的類上加上@Activate註解
@Activate("hello-mysql")
public class SpringMysqlDialect implements SpringSqlDialect {

    @Autowired
    private MysqlDialectService mysqlDialectService;

    @Override
    public String dialect() {
        return mysqlDialectService.dialect();
    }


}
2、啟動類上加上掃描SPI範圍註解
@SpringBootApplication(scanBasePackages = "com.github.lybgeek")
@EnableSpi(basePackages = "com.github.lybgeek")
public class SpiTestApplication implements ApplicationRunner 
3、利用getBeansOfType進行驗證
 applicationContext.getBeansOfType(SpringSqlDialect.class)
                .forEach((beanName,bean) -> System.out.println(beanName + "-->" + bean));

列印結果如下

hello-mysql-->com.github.lybgeek.dialect.mysql.SpringMysqlDialect@433348bc

說明已經注入到spring容器中

總結

把專案的服務託管給spring ioc容器,可以算是與spring整合比較基礎的動作,本文演示也是相對基礎的一環,spring 強大的地方,在於它的擴充套件性,在spring bean的生命週期中,基本上隨處可見擴充套件點,感興趣的朋友後續可以自行體會驗證

demo連結

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework-spring

相關文章