一、前言
插件化開發(fā)模式正在很多編程語言或技術(shù)框架中得以廣泛的應(yīng)用實(shí)踐,比如大家熟悉的jenkins,docker可視化管理平臺rancher,以及日常編碼使用的編輯器idea,vscode等,隨處可見的帶有熱插拔功能的插件,讓系統(tǒng)像插了翅膀一樣,大大提升了系統(tǒng)的擴(kuò)展性和伸縮性,也拓展了系統(tǒng)整體的使用價值,那么為什么要使用插件呢?
1.1 使用插件的好處
1.1.1 模塊解耦
實(shí)現(xiàn)服務(wù)模塊之間解耦的方式有很多,但是插件來說,其解耦的程度似乎更高,而且更靈活,可定制化、個性化更好。
舉例來說,代碼中可以使用設(shè)計模式來選擇使用哪種方式發(fā)送短信給下單完成的客戶,問題是各個短信服務(wù)商并不一定能保證在任何情況下都能發(fā)送成功,怎么辦呢?這時候設(shè)計模式也沒法幫你解決這個問題,如果使用定制化插件的方式,結(jié)合外部配置參數(shù),假設(shè)系統(tǒng)中某種短信發(fā)送不出去了,這時候就可以利用插件動態(tài)植入,切換為不同的廠商發(fā)短信了。
1.1.2 提升擴(kuò)展性和開放性
以spring來說,之所以具備如此廣泛的生態(tài),與其自身內(nèi)置的各種可擴(kuò)展的插件機(jī)制是分不開的,試想為什么使用了spring框架之后可以很方便的對接其他中間件,那就是spring框架提供了很多基于插件化的擴(kuò)展點(diǎn)。
插件化機(jī)制讓系統(tǒng)的擴(kuò)展性得以提升,從而可以豐富系統(tǒng)的周邊應(yīng)用生態(tài)。
1.1.3 方便第三方接入
有了插件之后,第三方應(yīng)用或系統(tǒng)如果要對接自身的系統(tǒng),直接基于系統(tǒng)預(yù)留的插件接口完成一套適合自己業(yè)務(wù)的實(shí)現(xiàn)即可,而且對自身系統(tǒng)的侵入性很小,甚至可以實(shí)現(xiàn)基于配置參數(shù)的熱加載,方便靈活,開箱即用。
1.2 插件化常用實(shí)現(xiàn)思路
以java為例,這里結(jié)合實(shí)際經(jīng)驗(yàn),整理一些常用的插件化實(shí)現(xiàn)思路:
spi機(jī)制;
約定配置和目錄,利用反射配合實(shí)現(xiàn);
springboot中的Factories機(jī)制;
java agent(探針)技術(shù);
spring內(nèi)置擴(kuò)展點(diǎn);
第三方插件包,例如:spring-plugin-core;
spring aop技術(shù);
二、Java常用插件實(shí)現(xiàn)方案
serviceloader是java提供的spi模式的實(shí)現(xiàn)。按照接口開發(fā)實(shí)現(xiàn)類,而后配置,java通過ServiceLoader來實(shí)現(xiàn)統(tǒng)一接口不同實(shí)現(xiàn)的依次調(diào)用。而java中最經(jīng)典的serviceloader的使用就是Java的spi機(jī)制。
2.1.1 java spi
SPI全稱 Service Provider Interface ,是JDK內(nèi)置的一種服務(wù)發(fā)現(xiàn)機(jī)制,SPI是一種動態(tài)替換擴(kuò)展機(jī)制,比如有個接口,你想在運(yùn)行時動態(tài)給他添加實(shí)現(xiàn),你只需按照規(guī)范給他添加一個實(shí)現(xiàn)類即可。比如大家熟悉的jdbc中的Driver接口,不同的廠商可以提供不同的實(shí)現(xiàn),有mysql的,也有oracle的,而Java的SPI機(jī)制就可以為某個接口尋找服務(wù)的實(shí)現(xiàn)。
下面用一張簡圖說明下SPI機(jī)制的原理
2.1.2 java spi 簡單案例
如下工程目錄,在某個應(yīng)用工程中定義一個插件接口,而其他應(yīng)用工程為了實(shí)現(xiàn)這個接口,只需要引入當(dāng)前工程的jar包依賴進(jìn)行實(shí)現(xiàn)即可,這里為了演示我就將不同的實(shí)現(xiàn)直接放在同一個工程下;
定義接口
publicinterfaceMessagePlugin{ publicStringsendMsg(MapmsgMap); }
定義兩個不同的實(shí)現(xiàn)
publicclassAliyunMsgimplementsMessagePlugin{ @Override publicStringsendMsg(MapmsgMap){ System.out.println("aliyunsendMsg"); return"aliyunsendMsg"; } } publicclassTencentMsgimplementsMessagePlugin{ @Override publicStringsendMsg(MapmsgMap){ System.out.println("tencentsendMsg"); return"tencentsendMsg"; } }
在resources目錄按照規(guī)范要求創(chuàng)建文件目錄,并填寫實(shí)現(xiàn)類的全類名
自定義服務(wù)加載類
publicstaticvoidmain(String[]args){ ServiceLoaderserviceLoader=ServiceLoader.load(MessagePlugin.class); Iterator iterator=serviceLoader.iterator(); Mapmap=newHashMap(); while(iterator.hasNext()){ MessagePluginmessagePlugin=iterator.next(); messagePlugin.sendMsg(map); } }
運(yùn)行上面的程序后,可以看到下面的效果,這就是說,使用ServiceLoader的方式可以加載到不同接口的實(shí)現(xiàn),業(yè)務(wù)中只需要根據(jù)自身的需求,結(jié)合配置參數(shù)的方式就可以靈活的控制具體使用哪一個實(shí)現(xiàn)。
2.2 自定義配置約定方式
serviceloader其實(shí)是有缺陷的,在使用中必須在META-INF里定義接口名稱的文件,在文件中才能寫上實(shí)現(xiàn)類的類名,如果一個項(xiàng)目里插件化的東西比較多,那很可能會出現(xiàn)越來越多配置文件的情況。所以在結(jié)合實(shí)際項(xiàng)目使用時,可以考慮下面這種實(shí)現(xiàn)思路:
A應(yīng)用定義接口;
B,C,D等其他應(yīng)用定義服務(wù)實(shí)現(xiàn);
B,C,D應(yīng)用實(shí)現(xiàn)后達(dá)成SDK的jar;
A應(yīng)用引用SDK或者將SDK放到某個可以讀取到的目錄下;
A應(yīng)用讀取并解析SDK中的實(shí)現(xiàn)類;
在上文中案例基礎(chǔ)上,我們做如下調(diào)整;
2.2.1 添加配置文件
在配置文件中,將具體的實(shí)現(xiàn)類配置進(jìn)去
server: port:8081 impl: name:com.congge.plugins.spi.MessagePlugin clazz: -com.congge.plugins.impl.TencentMsg -com.congge.plugins.impl.AliyunMsg
2.2.2 自定義配置文件加載類
通過這個類,將上述配置文件中的實(shí)現(xiàn)類封裝到類對象中,方便后續(xù)使用;
importlombok.Getter; importlombok.Setter; importlombok.ToString; importorg.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("impl") @ToString publicclassClassImpl{ @Getter @Setter Stringname; @Getter @Setter String[]clazz; }
2.2.3 自定義測試接口
使用上述的封裝對象通過類加載的方式動態(tài)的在程序中引入
importcom.congge.config.ClassImpl; importcom.congge.plugins.spi.MessagePlugin; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.bind.annotation.GetMapping; importorg.springframework.web.bind.annotation.RestController; importjava.util.HashMap; @RestController publicclassSendMsgController{ @Autowired ClassImplclassImpl; //localhost:8081/sendMsg @GetMapping("/sendMsg") publicStringsendMsg()throwsException{ for(inti=0;i
2.2.4 啟動類
@EnableConfigurationProperties({ClassImpl.class}) @SpringBootApplication publicclassPluginApp{ publicstaticvoidmain(String[]args){ SpringApplication.run(PluginApp.class,args); } }
啟動工程代碼后,調(diào)用接口:localhost:8081/sendMsg,在控制臺中可以看到下面的輸出信息,即通過這種方式也可以實(shí)現(xiàn)類似serviceloader的方式,不過在實(shí)際使用時,可以結(jié)合配置參數(shù)進(jìn)行靈活的控制;
2.3 自定義配置讀取依賴jar的方式
更進(jìn)一步,在很多場景下,可能我們并不想直接在工程中引入接口實(shí)現(xiàn)的依賴包,這時候可以考慮通過讀取指定目錄下的依賴jar的方式,利用反射的方式進(jìn)行動態(tài)加載,這也是生產(chǎn)中一種比較常用的實(shí)踐經(jīng)驗(yàn)。
具體實(shí)踐來說,主要為下面的步驟:
應(yīng)用A定義服務(wù)接口;
應(yīng)用B,C,D等實(shí)現(xiàn)接口(或者在應(yīng)用內(nèi)部實(shí)現(xiàn)相同的接口);
應(yīng)用B,C,D打成jar,放到應(yīng)用A約定的讀取目錄下;
應(yīng)用A加載約定目錄下的jar,通過反射加載目標(biāo)方法;
在上述的基礎(chǔ)上,按照上面的實(shí)現(xiàn)思路來實(shí)現(xiàn)一下;
2.3.1 創(chuàng)建約定目錄
在當(dāng)前工程下創(chuàng)建一個lib目錄,并將依賴的jar放進(jìn)去
2.3.2 新增讀取jar的工具類
添加一個工具類,用于讀取指定目錄下的jar,并通過反射的方式,結(jié)合配置文件中的約定配置進(jìn)行反射方法的執(zhí)行;
@Component publicclassServiceLoaderUtils{ @Autowired ClassImplclassImpl; publicstaticvoidloadJarsFromAppFolder()throwsException{ Stringpath="E:\code-self\bitzpp\lib"; Filef=newFile(path); if(f.isDirectory()){ for(Filesubf:f.listFiles()){ if(subf.isFile()){ loadJarFile(subf); } } }else{ loadJarFile(f); } } publicstaticvoidloadJarFile(Filepath)throwsException{ URLurl=path.toURI().toURL(); //可以獲取到AppClassLoader,可以提到前面,不用每次都獲取一次 URLClassLoaderclassLoader=(URLClassLoader)ClassLoader.getSystemClassLoader(); //加載 //Methodmethod=URLClassLoader.class.getDeclaredMethod("sendMsg",Map.class); Methodmethod=URLClassLoader.class.getMethod("sendMsg",Map.class); method.setAccessible(true); method.invoke(classLoader,url); } publicvoidmain(String[]args)throwsException{ System.out.println(invokeMethod("hello"));; } publicStringdoExecuteMethod()throwsException{ Stringpath="E:\code-self\bitzpp\lib"; Filef1=newFile(path); Objectresult=null; if(f1.isDirectory()){ for(Filesubf:f1.listFiles()){ //獲取文件名稱 Stringname=subf.getName(); StringfullPath=path+""+name; //執(zhí)行反射相關(guān)的方法 //ServiceLoaderUtilsserviceLoaderUtils=newServiceLoaderUtils(); //result=serviceLoaderUtils.loadMethod(fullPath); Filef=newFile(fullPath); URLurlB=f.toURI().toURL(); URLClassLoaderclassLoaderA=newURLClassLoader(newURL[]{urlB},Thread.currentThread() .getContextClassLoader()); String[]clazz=classImpl.getClazz(); for(StringclaName: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; } //獲取實(shí)例 Objectobj=loadClass.newInstance(); Mapmap=newHashMap(); //獲取方法 Methodmethod=loadClass.getDeclaredMethod("sendMsg",Map.class); result=method.invoke(obj,map); if(Objects.nonNull(result)){ break; } }elseif(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; } //獲取實(shí)例 Objectobj=loadClass.newInstance(); Mapmap=newHashMap(); //獲取方法 Methodmethod=loadClass.getDeclaredMethod("sendMsg",Map.class); result=method.invoke(obj,map); if(Objects.nonNull(result)){ break; } } } if(Objects.nonNull(result)){ break; } } } returnresult.toString(); } publicObjectloadMethod(StringfullPath)throwsException{ Filef=newFile(fullPath); URLurlB=f.toURI().toURL(); URLClassLoaderclassLoaderA=newURLClassLoader(newURL[]{urlB},Thread.currentThread() .getContextClassLoader()); Objectresult=null; String[]clazz=classImpl.getClazz(); for(StringclaName:clazz){ Class>loadClass=classLoaderA.loadClass(claName); if(Objects.isNull(loadClass)){ continue; } //獲取實(shí)例 Objectobj=loadClass.newInstance(); Mapmap=newHashMap(); //獲取方法 Methodmethod=loadClass.getDeclaredMethod("sendMsg",Map.class); result=method.invoke(obj,map); if(Objects.nonNull(result)){ break; } } returnresult; } publicstaticStringinvokeMethod(Stringtext)throwsException{ Stringpath="E:\code-self\bitzpp\lib\miz-pt-1.0-SNAPSHOT.jar"; Filef=newFile(path); URLurlB=f.toURI().toURL(); URLClassLoaderclassLoaderA=newURLClassLoader(newURL[]{urlB},Thread.currentThread() .getContextClassLoader()); Class>product=classLoaderA.loadClass("com.congge.spi.MizptImpl"); //獲取實(shí)例 Objectobj=product.newInstance(); Mapmap=newHashMap(); //獲取方法 Methodmethod=product.getDeclaredMethod("sendMsg",Map.class); //執(zhí)行方法 Objectresult1=method.invoke(obj,map); //TODOAccordingtotherequirements,writetheimplementationcode. returnresult1.toString(); } publicstaticStringgetApplicationFolder(){ Stringpath=ServiceLoaderUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(); returnnewFile(path).getParent(); } }
2.3.3 添加測試接口
添加如下測試接口
@GetMapping("/sendMsgV2") publicStringindex()throwsException{ Stringresult=serviceLoaderUtils.doExecuteMethod(); returnresult; }
以上全部完成之后,啟動工程,測試一下該接口,仍然可以得到預(yù)期結(jié)果;
在上述的實(shí)現(xiàn)中還是比較粗糙的,實(shí)際運(yùn)用時,還需要做較多的優(yōu)化改進(jìn)以滿足實(shí)際的業(yè)務(wù)需要,比如接口傳入類型參數(shù)用于控制具體使用哪個依賴包的方法進(jìn)行執(zhí)行等;
三、SpringBoot中的插件化實(shí)現(xiàn)
在大家使用較多的springboot框架中,其實(shí)框架自身提供了非常多的擴(kuò)展點(diǎn),其中最適合做插件擴(kuò)展的莫過于spring.factories的實(shí)現(xiàn);
3.1 Spring Boot中的SPI機(jī)制
在Spring中也有一種類似與Java SPI的加載機(jī)制。它在META-INF/spring.factories文件中配置接口的實(shí)現(xiàn)類名稱,然后在程序中讀取這些配置文件并實(shí)例化,這種自定義的SPI機(jī)制是Spring Boot Starter實(shí)現(xiàn)的基礎(chǔ)。
3.2 Spring Factories實(shí)現(xiàn)原理
spring-core包里定義了SpringFactoriesLoader類,這個類實(shí)現(xiàn)了檢索META-INF/spring.factories文件,并獲取指定接口的配置的功能。在這個類中定義了兩個對外的方法:
loadFactories 根據(jù)接口類獲取其實(shí)現(xiàn)類的實(shí)例,這個方法返回的是對象列表;
loadFactoryNames 根據(jù)接口獲取其接口類的名稱,這個方法返回的是類名的列表;
上面的兩個方法的關(guān)鍵都是從指定的ClassLoader中獲取spring.factories文件,并解析得到類名列表,具體代碼如下:
publicstaticListloadFactoryNames(Class>factoryClass,ClassLoaderclassLoader){ StringfactoryClassName=factoryClass.getName(); try{ Enumeration urls=(classLoader!=null?classLoader.getResources(FACTORIES_RESOURCE_LOCATION): ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List result=newArrayList (); while(urls.hasMoreElements()){ URLurl=urls.nextElement(); Propertiesproperties=PropertiesLoaderUtils.loadProperties(newUrlResource(url)); StringfactoryClassNames=properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } returnresult; } catch(IOExceptionex){ thrownewIllegalArgumentException("Unabletoload["+factoryClass.getName()+ "]factoriesfromlocation["+FACTORIES_RESOURCE_LOCATION+"]",ex); } }
從代碼中我們可以知道,在這個方法中會遍歷整個ClassLoader中所有jar包下的spring.factories文件,就是說我們可以在自己的jar中配置spring.factories文件,不會影響到其它地方的配置,也不會被別人的配置覆蓋。
spring.factories的是通過Properties解析得到的,所以我們在寫文件中的內(nèi)容都是安裝下面這種方式配置的:
com.xxx.interface=com.xxx.classname
如果一個接口希望配置多個實(shí)現(xiàn)類,可以使用’,’進(jìn)行分割
3.3 Spring Factories案例實(shí)現(xiàn)
接下來看一個具體的案例實(shí)現(xiàn)來體驗(yàn)下Spring Factories的使用;
3.3.1 定義一個服務(wù)接口
自定義一個接口,里面添加一個方法;
publicinterfaceSmsPlugin{ publicvoidsendMessage(Stringmessage); }
3.3.2 定義2個服務(wù)實(shí)現(xiàn)
實(shí)現(xiàn)類1
publicclassBizSmsImplimplementsSmsPlugin{ @Override publicvoidsendMessage(Stringmessage){ System.out.println("thisisBizSmsImplsendMessage..."+message); } }
實(shí)現(xiàn)類2
publicclassSystemSmsImplimplementsSmsPlugin{ @Override publicvoidsendMessage(Stringmessage){ System.out.println("thisisSystemSmsImplsendMessage..."+message); } }
3.3.3 添加spring.factories文件
在resources目錄下,創(chuàng)建一個名叫:META-INF的目錄,然后在該目錄下定義一個spring.factories的配置文件,內(nèi)容如下,其實(shí)就是配置了服務(wù)接口,以及兩個實(shí)現(xiàn)類的全類名的路徑;
com.congge.plugin.spi.SmsPlugin= com.congge.plugin.impl.SystemSmsImpl, com.congge.plugin.impl.BizSmsImpl
3.3.4 添加自定義接口
添加一個自定義的接口,有沒有發(fā)現(xiàn),這里和java 的spi有點(diǎn)類似,只不過是這里換成了SpringFactoriesLoader去加載服務(wù);
@GetMapping("/sendMsgV3") publicStringsendMsgV3(Stringmsg)throwsException{ ListsmsServices=SpringFactoriesLoader.loadFactories(SmsPlugin.class,null); for(SmsPluginsmsService:smsServices){ smsService.sendMessage(msg); } return"success"; }
啟動工程之后,調(diào)用一下該接口進(jìn)行測試,localhost:8087/sendMsgV3?msg=hello,通過控制臺,可以看到,這種方式能夠正確獲取到系統(tǒng)中可用的服務(wù)實(shí)現(xiàn);
利用spring的這種機(jī)制,可以很好的對系統(tǒng)中的某些業(yè)務(wù)邏輯通過插件化接口的方式進(jìn)行擴(kuò)展實(shí)現(xiàn);
四、插件化機(jī)制案例實(shí)戰(zhàn)
結(jié)合上面掌握的理論知識,下面基于Java SPI機(jī)制進(jìn)行一個接近真實(shí)使用場景的完整的操作步驟;
4.1 案例背景
3個微服務(wù)模塊,在A模塊中有個插件化的接口;
在A模塊中的某個接口,需要調(diào)用插件化的服務(wù)實(shí)現(xiàn)進(jìn)行短信發(fā)送;
可以通過配置文件配置參數(shù)指定具體的哪一種方式發(fā)送短信;
如果沒有加載到任何插件,將走A模塊在默認(rèn)的發(fā)短信實(shí)現(xiàn);
4.1.1 模塊結(jié)構(gòu)
1、biz-pp,插件化接口工程;
2、bitpt,aliyun短信發(fā)送實(shí)現(xiàn);
3、miz-pt,tencent短信發(fā)送實(shí)現(xiàn);
4.1.2 整體實(shí)現(xiàn)思路
本案例完整的實(shí)現(xiàn)思路參考如下:
biz-pp定義服務(wù)接口,并提供出去jar被其他實(shí)現(xiàn)工程依賴;
bitpt與miz-pt依賴biz-pp的jar并實(shí)現(xiàn)SPI中的方法;
bitpt與miz-pt按照API規(guī)范實(shí)現(xiàn)完成后,打成jar包,或者安裝到倉庫中;
biz-pp在pom中依賴bitpt與miz-pt的jar,或者通過啟動加載的方式即可得到具體某個實(shí)現(xiàn);
4.2 biz-pp 關(guān)鍵代碼實(shí)現(xiàn)過程
4.2.1 添加服務(wù)接口
publicinterfaceMessagePlugin{ publicStringsendMsg(MapmsgMap); }
4.2.2 打成jar包并安裝到倉庫
這一步比較簡單就不展開了
4.2.3 自定義服務(wù)加載工具類
這個類,可以理解為在真實(shí)的業(yè)務(wù)編碼中,可以根據(jù)業(yè)務(wù)定義的規(guī)則,具體加載哪個插件的實(shí)現(xiàn)類進(jìn)行發(fā)送短信的操作;
importcom.congge.plugin.spi.MessagePlugin; importcom.congge.spi.BitptImpl; importcom.congge.spi.MizptImpl; importjava.util.*; publicclassPluginFactory{ publicvoidinstallPlugin(){ Mapcontext=newLinkedHashMap(); context.put("_userId",""); context.put("_version","1.0"); context.put("_type","sms"); ServiceLoaderserviceLoader=ServiceLoader.load(MessagePlugin.class); Iterator iterator=serviceLoader.iterator(); while(iterator.hasNext()){ MessagePluginmessagePlugin=iterator.next(); messagePlugin.sendMsg(context); } } publicstaticMessagePlugingetTargetPlugin(Stringtype){ ServiceLoader serviceLoader=ServiceLoader.load(MessagePlugin.class); Iterator iterator=serviceLoader.iterator(); List messagePlugins=newArrayList<>(); while(iterator.hasNext()){ MessagePluginmessagePlugin=iterator.next(); messagePlugins.add(messagePlugin); } MessagePlugintargetPlugin=null; for(MessagePluginmessagePlugin:messagePlugins){ booleanfindTarget=false; switch(type){ case"aliyun": if(messagePlugininstanceofBitptImpl){ targetPlugin=messagePlugin; findTarget=true; break; } case"tencent": if(messagePlugininstanceofMizptImpl){ targetPlugin=messagePlugin; findTarget=true; break; } } if(findTarget)break; } returntargetPlugin; } publicstaticvoidmain(String[]args){ newPluginFactory().installPlugin(); } }
4.2.4 自定義接口
@RestController publicclassSmsController{ @Autowired privateSmsServicesmsService; @Autowired privateServiceLoaderUtilsserviceLoaderUtils; //localhost:8087/sendMsg?msg=sendMsg @GetMapping("/sendMsg") publicStringsendMessage(Stringmsg){ returnsmsService.sendMsg(msg); } }
4.2.5 接口實(shí)現(xiàn)
@Service publicclassSmsService{ @Value("${msg.type}") privateStringmsgType; @Autowired privateDefaultSmsServicedefaultSmsService; publicStringsendMsg(Stringmsg){ MessagePluginmessagePlugin=PluginFactory.getTargetPlugin(msgType); MapparamMap=newHashMap(); if(Objects.nonNull(messagePlugin)){ returnmessagePlugin.sendMsg(paramMap); } returndefaultSmsService.sendMsg(paramMap); } }
4.2.6 添加服務(wù)依賴
在該模塊中,需要引入對具體實(shí)現(xiàn)的兩個工程的jar依賴(也可以通過啟動加載的命令方式)
org.springframework.boot spring-boot-starter-web com.congge biz-pt 1.0-SNAPSHOT com.congge miz-pt 1.0-SNAPSHOT org.projectlombok lombok
biz-pp的核心代碼實(shí)現(xiàn)就到此結(jié)束了,后面再具體測試的時候再繼續(xù);
4.3 bizpt 關(guān)鍵代碼實(shí)現(xiàn)過程
接下來就是插件化機(jī)制中具體的SPI實(shí)現(xiàn)過程,兩個模塊的實(shí)現(xiàn)步驟完全一致,挑選其中一個說明,工程目錄結(jié)構(gòu)如下:
4.3.1 添加對biz-app的jar的依賴
將上面biz-app工程打出來的jar依賴過來
com.congge biz-app 1.0-SNAPSHOT
4.3.2 添加MessagePlugin接口的實(shí)現(xiàn)
publicclassBitptImplimplementsMessagePlugin{ @Override publicStringsendMsg(MapmsgMap){ ObjectuserId=msgMap.get("userId"); Objecttype=msgMap.get("_type"); //TODO參數(shù)校驗(yàn) System.out.println("====userId:"+userId+",type:"+type); System.out.println("aliyunsendmessagesuccess"); return"aliyunsendmessagesuccess"; } }
4.3.3 添加SPI配置文件
按照前文的方式,在resources目錄下創(chuàng)建一個文件,注意文件名稱為SPI中的接口全名,文件內(nèi)容為實(shí)現(xiàn)類的全類名
com.congge.spi.BitptImpl
4.3.4 將jar安裝到倉庫中
完成實(shí)現(xiàn)類的編碼后,通過maven命令將jar安裝到倉庫中,然后再在上一步的biz-app中引入即可;
4.4 效果演示
啟動biz-app服務(wù),調(diào)用接口:localhost:8087/sendMsg?msg=sendMsg,可以看到如下效果
為什么會出現(xiàn)這個效果呢?因?yàn)槲覀冊趯?shí)現(xiàn)類配置了具體使用哪一種方式進(jìn)行短信的發(fā)送,而加載插件的時候正好能夠找到對應(yīng)的服務(wù)實(shí)現(xiàn),這樣的話就給當(dāng)前的業(yè)務(wù)提供了一個較好的擴(kuò)展點(diǎn)。
五、寫在文末
從當(dāng)前的趨勢來看,插件化機(jī)制的思想已經(jīng)遍布各種編程語言,框架,中間件,開源工具等領(lǐng)域,因此掌握插件化的實(shí)現(xiàn)機(jī)制對于當(dāng)下做程序?qū)崿F(xiàn),或架構(gòu)設(shè)計方面都有著很重要的意義,值得深入研究,本篇到此結(jié)束,感謝觀看!
審核編輯:劉清
-
SPI接口
+關(guān)注
關(guān)注
0文章
258瀏覽量
34288 -
JAVA語言
+關(guān)注
關(guān)注
0文章
138瀏覽量
20055 -
解耦控制
+關(guān)注
關(guān)注
0文章
29瀏覽量
10189 -
SpringBoot
+關(guān)注
關(guān)注
0文章
173瀏覽量
153
原文標(biāo)題:SpringBoot 插件化開發(fā)模式,強(qiáng)烈推薦!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論