• Spring Plugin插件系统入门


    前言

    今天在学习swagger源码时,发现其中使用到了spring-plugin组件,github地址
    这个组件很小众,在其他框架中也使用不多,它被称为最小的插件系统。

    简单使用

    maven依赖

    <dependency>
       <groupId>org.springframework.plugin</groupId>
       <artifactId>spring-plugin-core</artifactId>
       <version>2.0.0.RELEASE</version>
    </dependency>
    

    定义接口

    import org.springframework.plugin.core.Plugin;
    
    public interface SmsService extends Plugin<String> {
    
      void sendSms(String mobile);
    }
    

    定义一个短信服务接口,接口必须继承Plugin接口

    接口实现类

    public class SmsServiceImpl implements SmsService {
    
      @Override
      public boolean supports(String s) {
        return s.startsWith("139");
      }
    
      @Override
      public void sendSms(String mobile) {
        if (supports(mobile)) {
          System.out.println("SmsServiceImpl 发送短信成功:" + mobile);
        }
      }
    }
    
    public class SmsServiceImpl2 implements SmsService {
    
      @Override
      public boolean supports(String s) {
        return s.startsWith("138");
      }
    
      @Override
      public void sendSms(String mobile) {
        if (supports(mobile)) {
          System.out.println("SmsServiceImpl2 发送短信成功:" + mobile);
        }
      }
    }
    

    定义两个短信服务实现类,一个支持发送139开头的手机号,一个支持发送138开头的手机号。

    Spring配置

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.plugin.core.config.EnablePluginRegistries;
    
    @Configuration
    @EnablePluginRegistries(SmsService.class)
    public class BeanConfig {
    
      @Bean
      public SmsServiceImpl smsService() {
        return new SmsServiceImpl();
      }
    
      @Bean
      public SmsServiceImpl2 smsService2() {
        return new SmsServiceImpl2();
      }
    }
    

    主要在于EnablePluginRegistries注解,用来注入PluginRegistry

    客户端

    import java.util.List;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.plugin.core.PluginRegistry;
    
    public class Client {
    
      public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        PluginRegistry<SmsService, String> registry = context.getBean(PluginRegistry.class);
        List<SmsService> plugins = registry.getPlugins();
        for (SmsService plugin : plugins) {
          plugin.sendSms("1391xxxxxxxx");
          plugin.sendSms("1381xxxxxxxx");
        }
      }
    
    }
    

    spring-plugin会帮我们向IOC容器自动注入PluginRegistry,我们通过它来获取plugin。

    源码分析

    spring-plugin组件其实很小,一共也没有几个类

    入口在于EnablePluginRegistries注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    @Import(PluginRegistriesBeanDefinitionRegistrar.class)
    public @interface EnablePluginRegistries {
    
    	/**
    	 * The {@link Plugin} types to register {@link PluginRegistry} instances for. The registries will be named after the
    	 * uncapitalized plugin type extended with {@code Registry}. So for a plugin interface {@code SamplePlugin} the
    	 * exposed bean name will be {@code samplePluginRegistry}. This can be used on the client side to make sure you get
    	 * the right {@link PluginRegistry} injected by using the {@link Qualifier} annotation and referring to that bean
    	 * name. If the auto-generated bean name collides with one already in your application you can use the
    	 * {@link Qualifier} annotation right at the plugin interface to define a custom name.
    	 * 
    	 * @return
    	 */
    	Class<? extends Plugin<?>>[] value();
    }
    

    跟进去PluginRegistriesBeanDefinitionRegistrar

    public class PluginRegistriesBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    	private static final Logger LOG = LoggerFactory.getLogger(PluginRegistriesBeanDefinitionRegistrar.class);
    
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    		//获取注解的各种属性
    		Map<String, Object> annotationAttributes = importingClassMetadata
    				.getAnnotationAttributes(EnablePluginRegistries.class.getName());
    
    		if (annotationAttributes == null) {
    			LOG.info("No EnablePluginRegistries annotation found on type {}!", importingClassMetadata.getClassName());
    			return;
    		}
    		//获取value属性值,这里就是SmsService接口	
    		Class<?>[] types = (Class<?>[]) annotationAttributes.get("value");
    
    		for (Class<?> type : types) {
    			//定义一个类型为PluginRegistryFactoryBean的bean,这是一个FactoryBean
    			BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PluginRegistryFactoryBean.class);
    			builder.addPropertyValue("type", type);
    
    			RootBeanDefinition beanDefinition = (RootBeanDefinition) builder.getBeanDefinition();
    			beanDefinition.setTargetType(getTargetType(type));
    
    			Qualifier annotation = type.getAnnotation(Qualifier.class);
    
    			// If the plugin interface has a Qualifier annotation, propagate that to the bean definition of the registry
    			if (annotation != null) {
    				AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);
    				qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());
    				beanDefinition.addQualifier(qualifierMetadata);
    			}
    
    			// Default
    			String beanName = annotation == null //
    					? StringUtils.uncapitalize(type.getSimpleName() + "Registry") //
    					: annotation.value();
                            //最终的名称为 smsServiceRegistry
    			registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    		}
    	}
    
    	/**
    	 * Returns the target type of the {@link PluginRegistry} for the given plugin type.
    	 *
    	 * @param pluginType must not be {@literal null}.
    	 * @return
    	 */
    	private static ResolvableType getTargetType(Class<?> pluginClass) {
    
    		Assert.notNull(pluginClass, "Plugin type must not be null!");
    
    		ResolvableType delimiterType = ResolvableType.forClass(Plugin.class, pluginClass).getGeneric(0);
    		ResolvableType pluginType = ResolvableType.forClass(pluginClass);
    
    		return ResolvableType.forClassWithGenerics(OrderAwarePluginRegistry.class, pluginType, delimiterType);
    	}
    }
    

    继续看PluginRegistry是如何被创建的

    /**
     * {@link FactoryBean} to create {@link PluginRegistry} instances. Wraps a {@link BeanListFactoryBean}.
     *
     * @author Oliver Gierke
     */
    public class PluginRegistryFactoryBean<T extends Plugin<S>, S> extends AbstractTypeAwareSupport<T>
    		implements FactoryBean<PluginRegistry<T, S>> {
    
    	/*
    	 * (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#getObject()
    	 */
    	@NonNull
    	public OrderAwarePluginRegistry<T, S> getObject() {
    		return OrderAwarePluginRegistry.of(getBeans());
    	}
    
    	/*
    	 * (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
    	 */
    	@NonNull
    	public Class<?> getObjectType() {
    		return OrderAwarePluginRegistry.class;
    	}
    
    	/*
    	 * (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#isSingleton()
    	 */
    	public boolean isSingleton() {
    		return true;
    	}
    }
    

    最终的实现类为OrderAwarePluginRegistry。

    参考

    springfox 源码分析(三) 初探Spring Plugin插件系统
    spring plugin

  • 相关阅读:
    软件架构模式
    经济学基础
    使用vue-cli3新建一个项目,并写好基本配置
    vue+iview+less实现主题切换功能
    ivew table组件二次封装,解决slot-scope跨组件传递的问题
    vue-cli3使用less全局变量,不用每个组件引入less文件(亲测有效)
    vscode开发vue项目使用eslint+prettier格式化:保存时自动执行lint进行修复(升级篇,保存时可格式化模板和css)
    切换子路由时,父路由的组件会重新渲染
    更换路由时页面实现左右滑动的效果
    div设置为inline-block后,两个div之间有空隙
  • 原文地址:https://www.cnblogs.com/strongmore/p/15248730.html
Copyright © 2020-2023  润新知