使用场景
Spring Plugin 更类似于Java设计模式里的策略模式,通过约定的某种策略指定执行类。比如我们的发短信业务,由于短信提供商可能并不只有一家,我们就可以使用Spring Plugin
来更灵活的实现这个功能。
案例
引入依赖
<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 SmsPlugin extends Plugin<SmsType> {
/**
* 发送短信
*
* @param phone 手机号
* @param content 短信内容
*/
void sendSms(String phone, String content);
}
- 枚举策略(这也是策略模式常用的手段)
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;
}
}
- 接口实现
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);
}
}
- 配置
在启动类上或者配置类,注册自己定义的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);
}
}
- 测试
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
到最后被更名了
总结
- 配置@EnablePluginRegistries注解,可以配置多个Class类型
- 每一个Class类型都创建一个类型为PluginRegistry(具体实现类为OrderAwarePluginRegistry)的Bean,将此Class类型的所有Bean都添加到此PluginRegistry对象中
- 后续可以通过每一个PluginRegistry对象获取到其内部的所有Plugin对象。