做网站复杂吗,天元建设集团有限公司联系电话,济南seo外包公司,wordpress站点标题添加插件化常用的实现思路
spi机制#xff0c;Service Provider Interface #xff0c;是JDK内置的一种服务发现机制#xff0c;SPI是一种动态替换扩展机制约定配置和目录#xff0c;利用反射配合实现springboot中的Factories机制Java agent#xff08;探针#xff09;技术S…插件化常用的实现思路
spi机制Service Provider Interface 是JDK内置的一种服务发现机制SPI是一种动态替换扩展机制约定配置和目录利用反射配合实现springboot中的Factories机制Java agent探针技术Spring内置扩展点第三方插件包例如spring-plugin-corespring aop技术
SPI实现插件化案例
一、目录结构如下
二、自定义接口及其实现类
/**接口
*/
public interface MessagePlugin{public String sendMsg(Map msgMap);
}/**AliyunMsg实现类
*/
public class AliyunMsg implements MessagePlugin{Overridepublic String sendMsg(Map msgMap){System.out.println(aliyun sendMsg);return aliyun sendMsg;}
}/**TencentMsg实现类
*/
public class TencentMsg implements MessagePlugin {Overridepublic String sendMsg(Map msgMap) {System.out.println(tencent sendMsg);return tencent sendMsg;}
}三、在resources目录下按照规范要求创建文件目录并填写实现类的全类名
文件目录规范为接口所在的包全类名。
四、自定义服务加载类
使用ServiceLoader的方式可以加载到不同接口的实现业务中只需要根据自身的需求结合配置参数的方式就可以灵活的控制具体使用哪一个实现。
public static void main(String[] args){ServiceLoaderMessagePlugin serviceLoader ServiceLoader.load(MessagePlugin.class);InteratorMessagePlugin iterator serviceLoader.iterator();Map map new HashMap();while(iterator.hasNext()){MessagePlugin messagePlugin iterator.next();messagePlugin.sendMsg(map);}
}注意serviceloader有缺陷使用中必须在META-INF里定义接口名称的文件在文件中才能写上实现类的类名。 如果一个项目里插件化的东西太多可能会出现越来越多配置文件。
所以对上面的缺陷调整
1、配置文件中将具体的实现类配置进去
server:port: 8081impl:name: com.michael.plugins.spi.MessagePluginclazz: - com.michael.plugins.impl.TencentMsg- com.michale.plugins.impl.AliyunMsg
2、自定义配置文件加载类
通过该类将上述配置文件中的实现类封装到类对象中方便后续使用
ConfigurationProperties(impl)
ToString
public class ClassImpl{GetterSetterString name;GetterSetterString[] clazz;
}3、自定义测试接口
RestController
public class SendMsgController{AutowiredClassImpl classImpl;GetMapping(/sendMsg)public String sendMsg() throws Exception{for(int i 0;i classImpl.getClazz().length; i){Class pluginClass Class.forName(classImpl.getClazz()[i]);MessagePlugin messagePlugin (MessagePlugin) pluginClass.newInstance();messagePlugin.sendMsg(new HashMap());}return success;}
}4、启动类
EnableConfigurationProperties({ClassImpl.class})
SpringBootApplication
public class PluginApp{public static void main(String[] args) {SpringApplication.run(PluginApp.class,args);}
}启动工程代码后调用接口localhost:8081/sendMsg在控制台中可以看到下面的输出信息即通过这种方式也可以实现类似serviceloader的方式不过在实际使用时可以结合配置参数进行灵活的控制
在很多场景下可能我们并不想直接在工程中引入接口实现的依赖包这时候可以考虑通过读取指定目录下的依赖jar的方式利用反射的方式进行动态加载这也是生产中一种比较常用的实践经验
继续调整
1、创建约定目录
在当前工程下创建一个lib目录并将依赖jar放进去
2、新增读取jar的工具类
添加一个工具类用于读取指定目录下的jar通过反射的方式结合配置文件中的约定配置进行反射方法的执行
Component
public class ServiceLoaderUtils{AutowiredClassImpl classImpl;public static void loadJarFromAppFolder() throws Exception{String path E:\\code-self\\bitzpp\\lib;File f new File(path);if(f.isDirectory()){for(File subf:f.listFiles()){loadJarFile(subf);}}else{loadJarFile(f);}}public static void loadJarFile(File path) throws Exception{URL url path.toURI().toURL();URLClassLoader classLoader (URLClassLoader).ClassLoader.getSystemClassLoader();//加载//Method method URLClassLoader.class.getDeclareMethod(sendMsg,Map.class);Method method URLClassLoader.class.getMethod(sendMsg,Map.class);method.setAccessible(true);method.invoke(classLoader,url);}public void main(String[] args) throws Exception{System.out.println(invokeMethod(hello));;}public String doExecuteMethod() throws Exception{String path E:\\code-self\\bitzpp\\lib;File f1 new File(path);Object result null;if (f1.isDirectory()) {for (File subf : f1.listFiles()) {//获取文件名称String name subf.getName();String fullPath path \\ name;//执行反射相关的方法//ServiceLoaderUtils serviceLoaderUtils new ServiceLoaderUtils();//result serviceLoaderUtils.loadMethod(fullPath);File f new File(fullPath);URL urlB f.toURI().toURL();URLClassLoader classLoaderA new URLClassLoader(new URL[]{urlB}, Thread.currentThread().getContextClassLoader());String[] clazz classImpl.getClazz();for(String claName : clazz){if(name.equals(biz-pt-1.0-SNAPSHOT.jar)){if(!claName.equals(com.congge.spi.BitptImpl)){continue;}Class? loadClass classLoaderA.loadClass(claName);if(Objects.isNull(loadClass)){continue;}//获取实例Object obj loadClass.newInstance();Map map new HashMap();//获取方法Method methodloadClass.getDeclaredMethod(sendMsg,Map.class);result method.invoke(obj,map);if(Objects.nonNull(result)){break;}}else if(name.equals(miz-pt-1.0-SNAPSHOT.jar)){if(!claName.equals(com.congge.spi.MizptImpl)){continue;}Class? loadClass classLoaderA.loadClass(claName);if(Objects.isNull(loadClass)){continue;}//获取实例Object obj loadClass.newInstance();Map map new HashMap();//获取方法Method methodloadClass.getDeclaredMethod(sendMsg,Map.class);result method.invoke(obj,map);if(Objects.nonNull(result)){break;}}}if(Objects.nonNull(result)){break;}}}return result.toString();}public Object loadMethod(String fullPath) throws Exception{File f new File(fullPath);URL urlB f.toURI().toURL();URLClassLoader classLoaderA new URLClassLoader(new URL[]{urlB}, Thread.currentThread().getContextClassLoader());Object result null;String[] clazz classImpl.getClazz();for(String claName : clazz){Class? loadClass classLoaderA.loadClass(claName);if(Objects.isNull(loadClass)){continue;}//获取实例Object obj loadClass.newInstance();Map map new HashMap();//获取方法Method methodloadClass.getDeclaredMethod(sendMsg,Map.class);result method.invoke(obj,map);if(Objects.nonNull(result)){break;}}return result;}public static String invokeMethod(String text) throws Exception{String path E:\\code-self\\bitzpp\\lib\\miz-pt-1.0-SNAPSHOT.jar;File f new File(path);URL urlB f.toURI().toURL();URLClassLoader classLoaderA new URLClassLoader(new URL[]{urlB}, Thread.currentThread().getContextClassLoader());Class? product classLoaderA.loadClass(com.congge.spi.MizptImpl);//获取实例Object obj product.newInstance();Map map new HashMap();//获取方法Method methodproduct.getDeclaredMethod(sendMsg,Map.class);//执行方法Object result1 method.invoke(obj,map);// TODO According to the requirements , write the implementation code.return result1.toString();}public static String getApplicationFolder() {String path ServiceLoaderUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath();return new File(path).getParent();}}3、添加测试接口
GetMapping(/sendMsgV2)
public String index() throws Exception{String result serviceLoaderUtils.doExecuteMethod();return result;
}以上全部完成之后启动工程测试一下该接口仍然可以得到预期结果
SpringBoot插件化实现
其实框架自身提供了非常多的扩展点其中最适合做插件扩展的莫过于spring.factories的实现
在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称然后在程序中读取这些配置文件并实例化这种自定义的SPI机制是Spring Boot Starter实现的基础。
spring-core包里定义了SpringFactoriesLoader类这个类实现了检索META-INF/spring.factories文件并获取指定接口的配置的功能。在这个类中定义了两个对外的方法
loadFactories 根据接口类获取其实现类的实例这个方法返回的是对象列表loadFactoryNames 根据接口获取其接口类的名称这个方法返回的是类名的列表
上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件并解析得到类名列表具体代码如下
public static ListString loadFactoryNames(Class? factoryClass, ClassLoader classLoader) {String factoryClassName factoryClass.getName();try {EnumerationURL urls (classLoader ! null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));ListString result new ArrayListString();while (urls.hasMoreElements()) {URL url urls.nextElement();Properties properties PropertiesLoaderUtils.loadProperties(new UrlResource(url));String factoryClassNames properties.getProperty(factoryClassName);result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));}return result;}catch (IOException ex) {throw new IllegalArgumentException(Unable to load [ factoryClass.getName() ] factories from location [ FACTORIES_RESOURCE_LOCATION ], ex);}
}在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件就是说我们可以在自己的jar中配置spring.factories文件不会影响到其它地方的配置也不会被别人的配置覆盖 spring.factories的是通过Properties解析得到的所以我们在写文件中的内容都是安装下面这种方式配置的
com.xxx.interfacecom.xxx.classname如果一个接口希望配置多个实现类可以使用’,’进行分割
一、定义一个服务接口以及两个服务实现类
public interface SmsPlugin{public void sendMessage(String message);
}public class BizSmsImpl implements SmsPlugin {Overridepublic void sendMessage(String message) {System.out.println(this is BizSmsImpl sendMessage... message);}
}public class SystemSmsImpl implements SmsPlugin {Overridepublic void sendMessage(String message) {System.out.println(this is SystemSmsImpl sendMessage... message);}
}二、添加spring.factories文件
在resources目录下创建一个名叫META-INF的目录然后在该目录下定义一个spring.factories的配置文件内容如下其实就是配置了服务接口以及两个实现类的全类名的路径
com.congge.plugin.spi.SmsPlugin\
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl三、添加自定义接口
这里和java 的spi有点类似只不过是这里换成了SpringFactoriesLoader去加载服务
GetMapping(/sendMsgV3)
public String sendMsgV3(String msg) throws Exception{ListSmsPlugin smsServices SpringFactoriesLoader.loadFactories();for(SmsPlugin smsService : smsServices){smsService.sendMessage(msg);}return success;
}启动工程之后调用一下该接口进行测试localhost:8087/sendMsgV3?msghello通过控制台可以看到这种方式能够正确获取到系统中可用的服务实现 利用spring的这种机制可以很好的对系统中的某些业务逻辑通过插件化接口的方式进行扩展实现
SpringBoot扩展接口
可扩展接口的启动调用顺序
一、ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer 这是整个spring容器在刷新之前初始化ConfigurableApplicationContext的回调接口
容器刷新之前调用此类的initialize方法。这个点允许被用户自己扩展。用户可以在整个spring容器还没被初始化之前做一些事情。可以想到的场景可能为在最开始激活一些配置或者利用这时候class还没被类加载器加载的时机进行动态字节码注入等操作。
扩展方式此时spring容器还没被初始化所以想要自己扩展有三种方式
在启动类中用springApplication.addInitializers(new TestApplicationContextInitializer())加入配置文件中context.initializer.classescom.example.demo.TestApplicationContextInitializerSpring SPI扩展在spring.factories中加入org.springframework.context.ApplicationContextInitializercom.example.demo.TestApplicationContextInitializer
public class TestApplicationContextInitializer implements ApplicationContextInitializer{Overridepublic void initialize(ConfigurableApplicationContext applicationContext){System.out.println([ApplicationContextInitializer]);}
}二、BeanDefinitionRegistryPostProcessor
org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor 这个接口在读取项目中的beanDefinition之后执行提供一个补充的扩展点
使用场景你可以在这里动态注册自己的beanDefinition可以加载classpath之外的bean
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { System.out.println([BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry); } Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println([BeanDefinitionRegistryPostProcessor] postProcessBeanFactory); }
} 三、BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanFactoryPostProcessor
这个接口是beanFactory的扩展接口调用时机在spring在读取beanDefinition信息之后实例化bean之前 用户可以通过实现这个扩展接口来自行处理一些东西比如修改已经注册的beanDefinition的元信息。
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println([BeanFactoryPostProcessor]); }
} 四、InstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
该接口继承了BeanPostProcess接口区别如下 BeanPostProcess接口只在bean的初始化阶段进行扩展注入spring上下文前后 InstantiationAwareBeanPostProcessor接口在此基础上增加了3个方法把可扩展的范围增加了实例化阶段和属性注入阶段。
postProcessBeforeInstantiation实例化bean之前相当于new这个bean之前postProcessAfterInstantiation实例化bean之后相当于new这个bean之后postProcessPropertyValuesbean已经实例化完成在属性注入时阶段触发Autowired,Resource等注解原理基于此方法实现postProcessBeforeInitialization初始化bean之前相当于把bean注入spring上下文之前postProcessAfterInitialization初始化bean之后相当于把bean注入spring上下文之后
使用场景这个扩展点非常有用 无论是写中间件和业务中都能利用这个特性。比如对实现了某一类接口的bean在各个生命期间进行收集或者对某个类型的bean进行统一的设值等等。
public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println([TestInstantiationAwareBeanPostProcessor] before initialization beanName); return bean; } Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println([TestInstantiationAwareBeanPostProcessor] after initialization beanName); return bean; } Override public Object postProcessBeforeInstantiation(Class? beanClass, String beanName) throws BeansException { System.out.println([TestInstantiationAwareBeanPostProcessor] before instantiation beanName); return null; } Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { System.out.println([TestInstantiationAwareBeanPostProcessor] after instantiation beanName); return true; } Override public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { System.out.println([TestInstantiationAwareBeanPostProcessor] postProcessPropertyValues beanName); return pvs; } 五、SmartInstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor
该扩展接口有3个触发点方法
public class TestSmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor { Override public Class? predictBeanType(Class? beanClass, String beanName) throws BeansException { System.out.println([TestSmartInstantiationAwareBeanPostProcessor] predictBeanType beanName); return beanClass; } Override public Constructor?[] determineCandidateConstructors(Class? beanClass, String beanName) throws BeansException { System.out.println([TestSmartInstantiationAwareBeanPostProcessor] determineCandidateConstructors beanName); return null; } Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { System.out.println([TestSmartInstantiationAwareBeanPostProcessor] getEarlyBeanReference beanName); return bean; }
} 六、BeanFactoryAware
org.springframework.beans.factory.BeanFactoryAware
这个类只有一个触发点发生在bean的实例化之后注入属性之前也就是Setter之前。这个类的扩展点方法为setBeanFactory可以拿到BeanFactory这个属性。
使用场景为你可以在bean实例化之后但还未初始化之前拿到 BeanFactory在这个时候可以对每个bean作特殊化的定制。也或者可以把BeanFactory拿到进行缓存日后使用。
public class TestBeanFactoryAware implements BeanFactoryAware { Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println([TestBeanFactoryAware] beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName()); }
} 七、ApplicationContextAwareProcessor
org.springframework.context.support.ApplicationContextAwareProcessor 该类本身并没有扩展点但是该类内部却有6个扩展点可供实现 这些类触发的时机在bean实例化之后初始化之前 可以看到该类用于执行各种驱动接口在bean实例化之后属性填充之后通过执行以上红框标出的扩展接口来获取对应容器的变量。所以这里应该来说是有6个扩展点这里就放一起来说了
八、BeanNameAware
org.springframework.beans.factory.BeanNameAware
可以看到这个类也是Aware扩展的一种触发点在bean的初始化之前也就是postProcessBeforeInitialization之前这个类的触发点方法只有一个setBeanName
使用场景为用户可以扩展这个点在初始化bean之前拿到spring容器中注册的的beanName来自行修改这个beanName的值。
public class NormalBeanA implements BeanNameAware{ public NormalBeanA() { System.out.println(NormalBean constructor); } Override public void setBeanName(String name) { System.out.println([BeanNameAware] name); }
} 九、PostConstruct
javax.annotation.PostConstruct 这个并不算一个扩展点其实就是一个标注。其作用是在bean的初始化阶段如果对一个方法标注了PostConstruct会先调用这个方法。这里重点是要关注下这个标准的触发点这个触发点是在postProcessBeforeInitialization之后InitializingBean.afterPropertiesSet之前。
使用场景用户可以对某一方法进行标注来进行初始化某一个属性
public class NormalBeanA { public NormalBeanA() { System.out.println(NormalBean constructor); } PostConstruct public void init(){ System.out.println([PostConstruct] NormalBeanA); }
} 十、InitializingBean
org.springframework.beans.factory.InitializingBean
这个类顾名思义也是用来初始化bean的。InitializingBean接口为bean提供了初始化方法的方式它只包括afterPropertiesSet方法凡是继承该接口的类在初始化bean的时候都会执行该方法。这个扩展点的触发时机在postProcessAfterInitialization之前。
使用场景用户实现此接口来进行系统启动的时候一些业务指标的初始化工作。
public class NormalBeanA implements InitializingBean{ Override public void afterPropertiesSet() throws Exception { System.out.println([InitializingBean] NormalBeanA); }
} 十一、FactoryBean
org.springframework.beans.factory.FactoryBean
一般情况下Spring通过反射机制利用bean的class属性指定支线类去实例化bean在某些情况下实例化Bean过程比较复杂如果按照传统的方式则需要在bean中提供大量的配置信息。配置方式的灵活性是受限的这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口用户可以通过实现该接口定制实例化Bean的逻辑。
FactoryBean接口对于Spring框架来说占用重要的地位Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂bean的细节给上层应用带来了便利。从Spring3.0开始FactoryBean开始支持泛型即接口声明改为FactoryBean的形式
使用场景用户可以扩展这个类来为要实例化的bean作一个代理比如为该对象的所有的方法作一个拦截在调用前后输出一行log模仿ProxyFactoryBean的功能。
public class TestFactoryBean implements FactoryBeanTestFactoryBean.TestFactoryInnerBean { Override public TestFactoryBean.TestFactoryInnerBean getObject() throws Exception { System.out.println([FactoryBean] getObject); return new TestFactoryBean.TestFactoryInnerBean(); } Override public Class? getObjectType() { return TestFactoryBean.TestFactoryInnerBean.class; } Override public boolean isSingleton() { return true; } public static class TestFactoryInnerBean{ }
} 十二、SmartInitializingSingleton
org.springframework.beans.factory.SmartInitializingSingleton
这个接口中只有一个方法afterSingletonsInstantiated其作用是是 在spring容器管理的所有单例对象非懒加载对象初始化完成之后调用的回调接口。其触发时机为postProcessAfterInitialization之后。
使用场景用户可以扩展此接口在对所有单例对象初始化完毕后做一些后置的业务处理。
public class TestSmartInitializingSingleton implements SmartInitializingSingleton { Override public void afterSingletonsInstantiated() { System.out.println([TestSmartInitializingSingleton]); }
} 十三、CommandLineRunner
org.springframework.boot.CommandLineRunner
这个接口也只有一个方法run(String… args)触发时机为整个项目启动完毕后自动执行。如果有多个CommandLineRunner可以利用Order来进行排序。
使用场景用户扩展此接口进行启动项目之后一些业务的预处理。
public class TestCommandLineRunner implements CommandLineRunner { Override public void run(String... args) throws Exception { System.out.println([TestCommandLineRunner]); }
} 十四、DisposableBean
org.springframework.beans.factory.DisposableBean
这个扩展点也只有一个方法destroy()其触发时机为当此对象销毁时会自动执行这个方法。比如说运行applicationContext.registerShutdownHook时就会触发这个方法。
public class NormalBeanA implements DisposableBean { Override public void destroy() throws Exception { System.out.println([DisposableBean] NormalBeanA); }
} 十五、ApplicationListener
org.springframework.context.ApplicationListener
准确的说这个应该不算springspringboot当中的一个扩展点ApplicationListener可以监听某个事件的event触发时机可以穿插在业务方法执行过程中用户可以自定义某个业务事件。
但是spring内部也有一些内置事件这种事件可以穿插在启动调用中。我们也可以利用这个特性来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。
接下来罗列下spring主要的内置事件
插件化机制案例实战
案例背景
3个微服务模块在A模块中有个插件化的接口在A模块中的某个接口需要调用插件化的服务实现进行短信发送可以通过配置文件配置参数指定具体的哪一种方式发送短信如果没有加载到任何插件将走A模块在默认的发短信实现
模块结构
biz-pp插件化接口工程定义服务接口并提供出去jar被其他实现工程依赖bitptaliyun短信发送实现依赖biz-pp的jar并实现SPI中的方法按照API规范实现完成后打成jar包或者安装到仓库中miz-pttencent短信发送实现依赖biz-pp的jar并实现SPI中的方法按照API规范实现完成后打成jar包或者安装到仓库中
biz-pp在pom中依赖bitpt与miz-pt的jar或者通过启动加载的方式即可得到具体某个实现
一、biz-app添加服务接口并打成jar安装到仓库
public interface MessagePlugin{public String sendMsg(Map msgMap);
}自定义服务加载工具类
/**这个类可以理解为在真实的业务编码中可以根据业务定义的规则具体加载哪个插件的实现类进行发送短信的操作
*/
public class PluginFactory {public void installPlugin(){Map context new LinkedHashMap();context.put(_userId,);context.put(_version,1.0);context.put(_type,sms);ServiceLoaderMessagePlugin serviceLoader ServiceLoader.load(MessagePlugin.class);IteratorMessagePlugin iterator serviceLoader.iterator();while (iterator.hasNext()){MessagePlugin messagePlugin iterator.next();messagePlugin.sendMsg(context);}}public static MessagePlugin getTargetPlugin(String type){ServiceLoaderMessagePlugin serviceLoader ServiceLoader.load(MessagePlugin.class);IteratorMessagePlugin iterator serviceLoader.iterator();ListMessagePlugin messagePlugins new ArrayList();while (iterator.hasNext()){MessagePlugin messagePlugin iterator.next();messagePlugins.add(messagePlugin);}MessagePlugin targetPlugin null;for (MessagePlugin messagePlugin : messagePlugins) {boolean findTarget false;switch (type) {case aliyun:if (messagePlugin instanceof BitptImpl){targetPlugin messagePlugin;findTarget true;break;}case tencent:if (messagePlugin instanceof MizptImpl){targetPlugin messagePlugin;findTarget true;break;}}if(findTarget) break;}return targetPlugin;}public static void main(String[] args) {new PluginFactory().installPlugin();}}自定义Controller接口、Service
RestController
public class SmsController {Autowiredprivate SmsService smsService;Autowiredprivate ServiceLoaderUtils serviceLoaderUtils;//localhost:8087/sendMsg?msgsendMsgGetMapping(/sendMsg)public String sendMessage(String msg){return smsService.sendMsg(msg);}}Service
public class SmsService {Value(${msg.type})private String msgType;Autowiredprivate DefaultSmsService defaultSmsService;public String sendMsg(String msg) {MessagePlugin messagePlugin PluginFactory.getTargetPlugin(msgType);Map paramMap new HashMap();if(Objects.nonNull(messagePlugin)){return messagePlugin.sendMsg(paramMap);}return defaultSmsService.sendMsg(paramMap);}
}在该模块中需要引入对具体实现的两个工程的jar依赖也可以通过启动加载的命令方式
dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--依赖具体的实现--dependencygroupIdcom.congge/groupIdartifactIdbiz-pt/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdcom.congge/groupIdartifactIdmiz-pt/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency/dependencies二、插件化具体实现
dependenciesdependencygroupIdcom.congge/groupIdartifactIdbiz-app/artifactIdversion1.0-SNAPSHOT/version/dependency
/dependencies添加MessagePlugin接口的实现
public class BitptImpl implements MessagePlugin {Overridepublic String sendMsg(Map msgMap) {Object userId msgMap.get(userId);Object type msgMap.get(_type);//TODO 参数校验System.out.println( userId : userId ,type : type);System.out.println(aliyun send message success);return aliyun send message success;}
}按照前文的方式在resources目录下创建一个文件注意文件名称为SPI中的接口全名文件内容为实现类的全类名
com.congge.spi.BitptImpl完成实现类的编码后通过maven命令将jar安装到仓库中然后再在上一步的biz-app中引入即可
启动biz-app服务调用接口localhost:8087/sendMsg?msgsendMsg
为什么会出现这个效果呢因为我们在实现类配置了具体使用哪一种方式进行短信的发送而加载插件的时候正好能够找到对应的服务实现这样的话就给当前的业务提供了一个较好的扩展点。