Spring Plugin 的使用

使用场景

Spring Plugin 更类似于Java设计模式里的策略模式,通过约定的某种策略指定执行类。比如我们的发短信业务,由于短信提供商可能并不只有一家,我们就可以使用Spring Plugin来更灵活的实现这个功能。

案例

引入依赖

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

代码

  1. 定义短信接口
import org.springframework.plugin.core.Plugin;

/**
 * 短信插件
 */
public interface SmsPlugin extends Plugin<SmsType> {
    /**
     * 发送短信
     *
     * @param phone   手机号
     * @param content 短信内容
     */
    void sendSms(String phone, String content);
}
  1. 枚举策略(这也是策略模式常用的手段)
public enum SmsType {
    /**
     * 阿里云渠道发送短信
     */
    A_LI_YUN("aliyun", "阿里云渠道发送短信"),
    /**
     * 腾讯云渠道发送短信
     */
    TX_YUN("tengxunyun", "腾讯云渠道发送短信");
    
    private final String smsChannel;
    private final String desc;
    
    SmsType(String smsChannel, String desc) {
        this.smsChannel = smsChannel;
        this.desc = desc;
    }
    
    public String getSmsChannel() {
        return smsChannel;
    }
    
    public String getDesc() {
        return desc;
    }
}
  1. 接口实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(2)
public class AliYunSmsPluginProvider implements SmsPlugin {
    
    private static final Logger log = LoggerFactory.getLogger(AliYunSmsPluginProvider.class);
    
    @Override
    public boolean supports(SmsType smsType) {
        return smsType == SmsType.A_LI_YUN;
    }
    
    @Override
    public void sendSms(String phone, String content) {
        log.info("通过阿里云渠道 给phone:[{}]发送短信:[{}]成功", phone, content);
    }
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class TenXunYunSmsPluginProvider implements SmsPlugin {

    private static final Logger log = LoggerFactory.getLogger(TenXunYunSmsPluginProvider.class);

    @Override
    public boolean supports(SmsType smsType) {
        return smsType == SmsType.TX_YUN;
    }

    @Override
    public void sendSms(String phone, String content) {
        log.info("通过腾讯云渠道 给phone:[{}]发送短信:[{}]成功", phone, content);
    }
}
  1. 配置

在启动类上或者配置类,注册自己定义的plugin

import com.jcl.plugin.sms.SmsPlugin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.plugin.core.config.EnablePluginRegistries;

@SpringBootApplication
@EnablePluginRegistries(value = {SmsPlugin.class})
public class SpringPluginApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringPluginApplication.class, args);
    }

}
  1. 测试
import com.jcl.plugin.sms.SmsPlugin;
import com.jcl.plugin.sms.SmsType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.plugin.core.PluginRegistry;

@SpringBootTest
class SpringPluginApplicationTest {
    @Autowired
    private PluginRegistry<SmsPlugin, SmsType> pluginRegistry;

    @Test
    void test() {
        SmsPlugin plugin = pluginRegistry.getRequiredPluginFor(SmsType.A_LI_YUN);
        plugin.sendSms("1895****705", "测试");
    }
}
2022-09-18 10:47:57.625  INFO 18636 --- [           main] c.j.plugin.sms.AliYunSmsPluginProvider   : 通过阿里云渠道 给phone:[1895****705]发送短信:[测试]成功

源码简析

先看启动类里的注解@EnablePluginRegistries

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({PluginRegistriesBeanDefinitionRegistrar.class})
public @interface EnablePluginRegistries {
    Class<? extends Plugin<?>>[] value();
}

再进PluginRegistriesBeanDefinitionRegistrar

public class PluginRegistriesBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	private static final Logger LOG = LoggerFactory.getLogger(PluginRegistriesBeanDefinitionRegistrar.class);

	/*
	 * (non-Javadoc)
	 * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
	 */
	@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;
		}

		Class<?>[] types = (Class<?>[]) annotationAttributes.get("value");

		for (Class<?> type : types) {

			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();

			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);
	}
}

对比大框架里的源码来说,这点代码实在是太好拿捏了!

annotationAttributes里是从注解里获取的plugin类

在这里插入图片描述

定义一个类型为PluginRegistryFactoryBean的bean,这是一个FactoryBean
在这里插入图片描述

到最后被更名了
在这里插入图片描述

总结

  1. 配置@EnablePluginRegistries注解,可以配置多个Class类型
  2. 每一个Class类型都创建一个类型为PluginRegistry(具体实现类为OrderAwarePluginRegistry)的Bean,将此Class类型的所有Bean都添加到此PluginRegistry对象中
  3. 后续可以通过每一个PluginRegistry对象获取到其内部的所有Plugin对象。
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值