精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Spring Boot 插件化開發模式

開發 前端
插件化開發模式正在很多編程語言或技術框架中得以廣泛的應用實踐,比如大家熟悉的jenkins,docker可視化管理平臺rancher,以及日常編碼使用的編輯器idea,vscode等。

一、前言

插件化開發模式正在很多編程語言或技術框架中得以廣泛的應用實踐,比如大家熟悉的jenkins,docker可視化管理平臺rancher,以及日常編碼使用的編輯器idea,vscode等。

隨處可見的帶有熱插拔功能的插件,讓系統像插了翅膀一樣,大大提升了系統的擴展性和伸縮性,也拓展了系統整體的使用價值,那么為什么要使用插件呢?

1.1 使用插件的好處

1.1.1 模塊解耦

實現服務模塊之間解耦的方式有很多,但是插件來說,其解耦的程度似乎更高,而且更靈活,可定制化、個性化更好。

舉例來說,代碼中可以使用設計模式來選擇使用哪種方式發送短信給下單完成的客戶,問題是各個短信服務商并不一定能保證在任何情況下都能發送成功,怎么辦呢?這時候設計模式也沒法幫你解決這個問題,如果使用定制化插件的方式,結合外部配置參數,假設系統中某種短信發送不出去了,這時候就可以利用插件動態植入,切換為不同的廠商發短信了。

1.1.2 提升擴展性和開放性

以spring來說,之所以具備如此廣泛的生態,與其自身內置的各種可擴展的插件機制是分不開的,試想為什么使用了spring框架之后可以很方便的對接其他中間件,那就是spring框架提供了很多基于插件化的擴展點。

插件化機制讓系統的擴展性得以提升,從而可以豐富系統的周邊應用生態。

1.1.3 方便第三方接入

有了插件之后,第三方應用或系統如果要對接自身的系統,直接基于系統預留的插件接口完成一套適合自己業務的實現即可,而且對自身系統的侵入性很小,甚至可以實現基于配置參數的熱加載,方便靈活,開箱即用。

1.2 插件化常用實現思路

以java為例,這里結合實際經驗,整理一些常用的插件化實現思路:

  • spi機制;
  • 約定配置和目錄,利用反射配合實現;
  • springboot中的Factories機制;
  • java agent(探針)技術;
  • spring內置擴展點;
  • 第三方插件包,例如:spring-plugin-core;
  • spring aop技術。

二、Java常用插件實現方案

2.1 serviceloader方式

serviceloader是java提供的spi模式的實現。按照接口開發實現類,而后配置,java通過ServiceLoader來實現統一接口不同實現的依次調用。而java中最經典的serviceloader的使用就是Java的spi機制。

2.1.1 java spi

SPI全稱 Service Provider Interface ,是JDK內置的一種服務發現機制,SPI是一種動態替換擴展機制,比如有個接口,你想在運行時動態給他添加實現,你只需按照規范給他添加一個實現類即可。比如大家熟悉的jdbc中的Driver接口,不同的廠商可以提供不同的實現,有mysql的,也有oracle的,而Java的SPI機制就可以為某個接口尋找服務的實現。

下面用一張簡圖說明下SPI機制的原理。

圖片圖片

2.1.2 java spi 簡單案例

如下工程目錄,在某個應用工程中定義一個插件接口,而其他應用工程為了實現這個接口,只需要引入當前工程的jar包依賴進行實現即可,這里為了演示我就將不同的實現直接放在同一個工程下。

圖片圖片

定義接口:

public interface MessagePlugin {
 
    public String sendMsg(Map msgMap);
 
}

定義兩個不同的實現:

public class AliyunMsg implements MessagePlugin {

    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("aliyun sendMsg");
        return"aliyun sendMsg";
    }
}
publicclass TencentMsg implements MessagePlugin {

    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("tencent sendMsg");
        return"tencent sendMsg";
    }
}

在resources目錄按照規范要求創建文件目錄,并填寫實現類的全類名。

圖片圖片

自定義服務加載類:

public static void main(String[] args) {
        ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);
        Iterator<MessagePlugin> iterator = serviceLoader.iterator();
        Map map = new HashMap();
        while (iterator.hasNext()){
            MessagePlugin messagePlugin = iterator.next();
            messagePlugin.sendMsg(map);
        }
    }

運行上面的程序后,可以看到下面的效果,這就是說,使用ServiceLoader的方式可以加載到不同接口的實現,業務中只需要根據自身的需求,結合配置參數的方式就可以靈活的控制具體使用哪一個實現。

圖片圖片

2.2 自定義配置約定方式

serviceloader其實是有缺陷的,在使用中必須在META-INF里定義接口名稱的文件,在文件中才能寫上實現類的類名,如果一個項目里插件化的東西比較多,那很可能會出現越來越多配置文件的情況。所以在結合實際項目使用時,可以考慮下面這種實現思路:

  • A應用定義接口;
  • B,C,D等其他應用定義服務實現;
  • B,C,D應用實現后達成SDK的jar;
  • A應用引用SDK或者將SDK放到某個可以讀取到的目錄下;
  • A應用讀取并解析SDK中的實現類。

在上文中案例基礎上,我們做如下調整。

2.2.1 添加配置文件

在配置文件中,將具體的實現類配置進去。

server :
  port : 8081
impl:
  name : com.congge.plugins.spi.MessagePlugin
  clazz :
    - com.congge.plugins.impl.TencentMsg
    - com.congge.plugins.impl.AliyunMsg
2.2.2 自定義配置文件加載類

通過這個類,將上述配置文件中的實現類封裝到類對象中,方便后續使用。

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("impl")
@ToString
publicclass ClassImpl {
    @Getter
    @Setter
    String name;

    @Getter
    @Setter
    String[] clazz;
}
2.2.3 自定義測試接口

使用上述的封裝對象通過類加載的方式動態的在程序中引入。

import com.congge.config.ClassImpl;
import com.congge.plugins.spi.MessagePlugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
publicclass SendMsgController {

    @Autowired
    ClassImpl classImpl;

    //localhost:8081/sendMsg
    @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";
    }

}
2.2.4 啟動類
@EnableConfigurationProperties({ClassImpl.class})
@SpringBootApplication
public class PluginApp {
 
    public static void main(String[] args) {
        SpringApplication.run(PluginApp.class,args);
    }
 
}

啟動工程代碼后,調用接口:localhost:8081/sendMsg,在控制臺中可以看到下面的輸出信息,即通過這種方式也可以實現類似serviceloader的方式,不過在實際使用時,可以結合配置參數進行靈活的控制。

圖片

2.3 自定義配置讀取依賴jar的方式

更進一步,在很多場景下,可能我們并不想直接在工程中引入接口實現的依賴包,這時候可以考慮通過讀取指定目錄下的依賴jar的方式,利用反射的方式進行動態加載,這也是生產中一種比較常用的實踐經驗。

具體實踐來說,主要為下面的步驟:

  • 應用A定義服務接口;
  • 應用B,C,D等實現接口(或者在應用內部實現相同的接口);
  • 應用B,C,D打成jar,放到應用A約定的讀取目錄下;
  • 應用A加載約定目錄下的jar,通過反射加載目標方法。

在上述的基礎上,按照上面的實現思路來實現一下。

2.3.1 創建約定目錄

在當前工程下創建一個lib目錄,并將依賴的jar放進去。

圖片

2.3.2 新增讀取jar的工具類

添加一個工具類,用于讀取指定目錄下的jar,并通過反射的方式,結合配置文件中的約定配置進行反射方法的執行。

@Component
publicclass ServiceLoaderUtils {

    @Autowired
    ClassImpl classImpl;


    public static void loadJarsFromAppFolder() throws Exception {
        String path = "E:\\code-self\\bitzpp\\lib";
        File f = new File(path);
        if (f.isDirectory()) {
            for (File subf : f.listFiles()) {
                if (subf.isFile()) {
                    loadJarFile(subf);
                }
            }
        } else {
            loadJarFile(f);
        }
    }

    public static void loadJarFile(File path) throws Exception {
        URL url = path.toURI().toURL();
        // 可以獲取到AppClassLoader,可以提到前面,不用每次都獲取一次
        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        // 加載
        //Method method = URLClassLoader.class.getDeclaredMethod("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 method=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;
                        }
                        //獲取實例
                        Object obj = loadClass.newInstance();
                        Map map = new HashMap();
                        //獲取方法
                        Method method=loadClass.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 method=loadClass.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 method=product.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();
        returnnew File(path).getParent();
    }



}
2.3.3 添加測試接口

添加如下測試接口:

@GetMapping("/sendMsgV2")
public String index() throws Exception {
    String result = serviceLoaderUtils.doExecuteMethod();
    return result;
}

以上全部完成之后,啟動工程,測試一下該接口,仍然可以得到預期結果。

圖片圖片

在上述的實現中還是比較粗糙的,實際運用時,還需要做較多的優化改進以滿足實際的業務需要,比如接口傳入類型參數用于控制具體使用哪個依賴包的方法進行執行等。

三、SpringBoot中的插件化實現

在大家使用較多的springboot框架中,其實框架自身提供了非常多的擴展點,其中最適合做插件擴展的莫過于spring.factories的實現。

3.1 Spring Boot中的SPI機制

在Spring中也有一種類似與Java SPI的加載機制。它在META-INF/spring.factories文件中配置接口的實現類名稱,然后在程序中讀取這些配置文件并實例化,這種自定義的SPI機制是Spring Boot Starter實現的基礎。

3.2 Spring Factories實現原理

spring-core包里定義了SpringFactoriesLoader類,這個類實現了檢索META-INF/spring.factories文件,并獲取指定接口的配置的功能。在這個類中定義了兩個對外的方法:

  • loadFactories 根據接口類獲取其實現類的實例,這個方法返回的是對象列表;
  • loadFactoryNames 根據接口獲取其接口類的名稱,這個方法返回的是類名的列表。

上面的兩個方法的關鍵都是從指定的ClassLoader中獲取spring.factories文件,并解析得到類名列表,具體代碼如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        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) {
        thrownew 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.interface=com.xxx.classname

如果一個接口希望配置多個實現類,可以使用’,’進行分割。

3.3 Spring Factories案例實現

接下來看一個具體的案例實現來體驗下Spring Factories的使用。

3.3.1 定義一個服務接口

自定義一個接口,里面添加一個方法:

public interface SmsPlugin {
 
    public void sendMessage(String message);
 
}
3.3.2 定義2個服務實現

實現類1

public class BizSmsImpl implements SmsPlugin {
 
    @Override
    public void sendMessage(String message) {
        System.out.println("this is BizSmsImpl sendMessage..." + message);
    }
}

實現類2

public class SystemSmsImpl implements SmsPlugin {
 
    @Override
    public void sendMessage(String message) {
        System.out.println("this is SystemSmsImpl sendMessage..." + message);
    }
}
3.3.3 添加spring.factories文件

在resources目錄下,創建一個名叫:META-INF的目錄,然后在該目錄下定義一個spring.factories的配置文件,內容如下,其實就是配置了服務接口,以及兩個實現類的全類名的路徑。

com.congge.plugin.spi.SmsPlugin=\
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl
3.3.4 添加自定義接口

添加一個自定義的接口,有沒有發現,這里和java 的spi有點類似,只不過是這里換成了SpringFactoriesLoader去加載服務。

@GetMapping("/sendMsgV3")
public String sendMsgV3(String msg) throws Exception{
    List<SmsPlugin> smsServices= SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
    for(SmsPlugin smsService : smsServices){
        smsService.sendMessage(msg);
    }
    return "success";
}

啟動工程之后,調用一下該接口進行測試,localhost:8087/sendMsgV3?msg=hello,通過控制臺,可以看到,這種方式能夠正確獲取到系統中可用的服務實現。

圖片圖片

利用spring的這種機制,可以很好的對系統中的某些業務邏輯通過插件化接口的方式進行擴展實現。

四、插件化機制案例實戰

結合上面掌握的理論知識,下面基于Java SPI機制進行一個接近真實使用場景的完整的操作步驟。

4.1 案例背景

  • 3個微服務模塊,在A模塊中有個插件化的接口;
  • 在A模塊中的某個接口,需要調用插件化的服務實現進行短信發送;
  • 可以通過配置文件配置參數指定具體的哪一種方式發送短信;
  • 如果沒有加載到任何插件,將走A模塊在默認的發短信實現。
4.1.1 模塊結構

1、biz-pp,插件化接口工程;

2、bitpt,aliyun短信發送實現;

3、miz-pt,tencent短信發送實現。

4.1.2 整體實現思路

本案例完整的實現思路參考如下:

  • biz-pp定義服務接口,并提供出去jar被其他實現工程依賴;
  • bitpt與miz-pt依賴biz-pp的jar并實現SPI中的方法;
  • bitpt與miz-pt按照API規范實現完成后,打成jar包,或者安裝到倉庫中;
  • biz-pp在pom中依賴bitpt與miz-pt的jar,或者通過啟動加載的方式即可得到具體某個實現。

4.2 biz-pp 關鍵代碼實現過程

4.2.1 添加服務接口
public interface MessagePlugin {
 
    public String sendMsg(Map msgMap);
 
}
4.2.2 打成jar包并安裝到倉庫

這一步比較簡單就不展開了。

4.2.3 自定義服務加載工具類

這個類,可以理解為在真實的業務編碼中,可以根據業務定義的規則,具體加載哪個插件的實現類進行發送短信的操作。

import com.congge.plugin.spi.MessagePlugin;
import com.congge.spi.BitptImpl;
import com.congge.spi.MizptImpl;

import java.util.*;

publicclass PluginFactory {

    public void installPlugin(){
        Map context = new LinkedHashMap();
        context.put("_userId","");
        context.put("_version","1.0");
        context.put("_type","sms");
        ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);
        Iterator<MessagePlugin> iterator = serviceLoader.iterator();
        while (iterator.hasNext()){
            MessagePlugin messagePlugin = iterator.next();
            messagePlugin.sendMsg(context);
        }
    }

    public static MessagePlugin getTargetPlugin(String type){
        ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);
        Iterator<MessagePlugin> iterator = serviceLoader.iterator();
        List<MessagePlugin> 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();
    }


}
4.2.4 自定義接口
@RestController
publicclass SmsController {

    @Autowired
    private SmsService smsService;

    @Autowired
    private ServiceLoaderUtils serviceLoaderUtils;

    //localhost:8087/sendMsg?msg=sendMsg
    @GetMapping("/sendMsg")
    public String sendMessage(String msg){
        return smsService.sendMsg(msg);
    }

}
4.2.5 接口實現
@Service
publicclass SmsService {

    @Value("${msg.type}")
    private String msgType;

    @Autowired
    private 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);
    }
}
4.2.6 添加服務依賴

在該模塊中,需要引入對具體實現的兩個工程的jar依賴(也可以通過啟動加載的命令方式)。

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--依賴具體的實現-->
    <dependency>
        <groupId>com.congge</groupId>
        <artifactId>biz-pt</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>com.congge</groupId>
        <artifactId>miz-pt</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

</dependencies>

biz-pp的核心代碼實現就到此結束了,后面再具體測試的時候再繼續。

4.3 bizpt 關鍵代碼實現過程

接下來就是插件化機制中具體的SPI實現過程,兩個模塊的實現步驟完全一致,挑選其中一個說明,工程目錄結構如下:

圖片

4.3.1 添加對biz-app的jar的依賴

將上面biz-app工程打出來的jar依賴過來。

<dependencies>
    <dependency>
        <groupId>com.congge</groupId>
        <artifactId>biz-app</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
4.3.2 添加MessagePlugin接口的實現
public class BitptImpl implements MessagePlugin {
 
    @Override
    public 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";
    }
}
4.3.3 添加SPI配置文件

按照前文的方式,在resources目錄下創建一個文件,注意文件名稱為SPI中的接口全名,文件內容為實現類的全類名。

com.congge.spi.BitptImpl
4.3.4 將jar安裝到倉庫中

完成實現類的編碼后,通過maven命令將jar安裝到倉庫中,然后再在上一步的biz-app中引入即可。

4.4 效果演示

啟動biz-app服務,調用接口:localhost:8087/sendMsg?msg=sendMsg,可以看到如下效果。

圖片

為什么會出現這個效果呢?因為我們在實現類配置了具體使用哪一種方式進行短信的發送,而加載插件的時候正好能夠找到對應的服務實現,這樣的話就給當前的業務提供了一個較好的擴展點。

圖片

五、寫在文末

從當前的趨勢來看,插件化機制的思想已經遍布各種編程語言,框架,中間件,開源工具等領域,因此掌握插件化的實現機制對于當下做程序實現,或架構設計方面都有著很重要的意義,值得深入研究,本篇到此結束,感謝觀看!

責任編輯:武曉燕 來源: 蘇三說技術
相關推薦

2025-07-01 09:21:33

2025-01-02 11:20:47

2025-07-04 09:31:56

2023-07-10 08:44:00

2025-02-11 07:55:45

2019-08-21 14:34:41

2022-12-23 08:28:42

策略模式算法

2021-05-07 07:03:33

Spring打包工具

2017-08-02 14:44:06

Spring Boot開發注解

2021-10-18 12:04:22

Spring BootJava開發

2021-10-18 10:36:31

Spring Boot插件Jar

2025-05-12 04:01:00

2017-03-06 15:43:33

Springboot啟動

2016-10-14 14:16:28

Spring BootJava應用

2018-05-25 16:32:45

Spring BootJava開發

2016-11-03 09:59:38

kotlinjavaspring

2025-06-06 01:00:00

Spring場景范式

2023-10-15 22:40:25

插件JIB

2025-06-27 02:44:00

2024-12-03 08:00:00

點贊
收藏

51CTO技術棧公眾號

天天色天天爱天天射综合| 国产成人精品亚洲午夜麻豆| 精品一区二区亚洲| 一级黄色香蕉视频| 成人黄色网址| 成人av综合一区| 欧美在线视频a| 免费黄色国产视频| 99久久免费精品国产72精品九九| 亚洲精品一区二区三区不卡| 亚洲图色一区二区三区| 精品久久久久久国产91| 日韩精品欧美在线| 99免费在线视频| 美女精品一区| 欧美巨猛xxxx猛交黑人97人| 黄色在线观看av| 伊人亚洲精品| 色悠悠久久综合| 国产一二三四区在线观看| 天天爽夜夜爽夜夜爽| 麻豆一区二区三| 97国产在线视频| 日本黄色免费片| 亚洲自拍都市欧美小说| 欧美一二区视频| 日韩免费高清在线| 黑人极品ⅴideos精品欧美棵| 国产精品美女一区二区| 国产一区二区在线网站| 国产免费黄色大片| 奇米888四色在线精品| 97香蕉久久夜色精品国产| 国精品无码一区二区三区| 狠狠做六月爱婷婷综合aⅴ | 青青国产在线观看| 欧美成人高清| 久久伊人91精品综合网站| 成人黄色免费网址| 天天躁日日躁狠狠躁欧美巨大小说| 欧美日本韩国一区| www.天天射.com| 厕沟全景美女厕沟精品| 亚洲国产美国国产综合一区二区| 黄频视频在线观看| 在线观看免费高清完整| 久久免费美女视频| 麻豆传媒一区| 手机在线不卡av| 高清不卡一二三区| 99久久免费国| www.av在线.com| 国产综合色产在线精品| 国产精品视频一| 中文字幕 人妻熟女| 男人的天堂成人在线| 91国产在线精品| 日韩字幕在线观看| 夜夜精品视频| 欧美一区二区视频97| 精品91久久久| 亚洲精品影视| 热久久视久久精品18亚洲精品| 精品国产免费观看| 欧美一级播放| 热re99久久精品国产66热| 91青青草视频| 麻豆国产精品官网| 91沈先生作品| 亚洲成人黄色片| 成人黄色国产精品网站大全在线免费观看 | 成人免费91在线看| 高h震动喷水双性1v1| 成人精品gif动图一区| 国产午夜精品一区| 精品av中文字幕在线毛片| 国产拍揄自揄精品视频麻豆| 亚州欧美一区三区三区在线| 久久精品视频免费看| 亚洲精品中文在线| 国产欧美日韩小视频| 欧美magnet| 欧美色倩网站大全免费| 亚洲精品乱码久久久久久动漫| 久久在线观看| 日韩成人久久久| 日韩一区二区三区四区视频| 亚洲欧美综合| 欧美与黑人午夜性猛交久久久| 18国产免费视频| 国产乱码精品一区二区三区av | 国模精品视频一区二区| 午夜影院免费在线观看| 久久成人麻豆午夜电影| 国产一区二区无遮挡| 成a人片在线观看www视频| 亚洲欧美国产77777| 缅甸午夜性猛交xxxx| 国产一区二区三区朝在线观看| 欧美一区二区三区视频在线| 伦理片一区二区| 国产一区网站| 久久久久久久久久久网站| 一级片在线观看免费| 国产精品一区二区在线看| 免费成人在线观看av| 精品国产白色丝袜高跟鞋| 精品日韩视频在线观看| 亚洲成熟丰满熟妇高潮xxxxx| 国产欧美日韩电影| 亚洲免费视频观看| 久久综合加勒比| 免费在线观看一区二区三区| 俄罗斯精品一区二区三区| 91社区在线| 岛国av午夜精品| 九九九九九九九九| 欧美禁忌电影网| 欧美黑人巨大精品一区二区| 在线免费观看av片| 91毛片在线观看| 国产一区 在线播放| 国产精品无码久久久久| 精品国产乱码久久久久久1区2区| 三级在线观看免费大全| 全国精品久久少妇| 免费观看成人在线| 国产免费拔擦拔擦8x高清在线人| 91麻豆精品国产91久久久使用方法| 国产精品无码一区二区三区| 激情亚洲成人| 99久久无色码| 羞羞电影在线观看www| 欧美精品一卡二卡| 日韩不卡av在线| 久久深夜福利| 精品综合久久| 麻豆免费在线| 亚洲成人av资源网| 国产亚洲欧美久久久久| 国产精品1区二区.| 久久精品国产精品亚洲精品色| 成人国产综合| 亚洲一级黄色av| aaa在线视频| 久久午夜国产精品| 男女午夜激情视频| 你懂的在线观看一区二区| 久久久久一本一区二区青青蜜月| 国产成人三级在线播放 | 国产精品25p| 欧美精品一区二区在线观看| 国产亚洲第一页| 成人在线综合网| www.亚洲视频.com| 国产精品视屏| 日本久久久久久久久久久| 飘雪影院手机免费高清版在线观看| 亚洲成a人v欧美综合天堂下载 | 精品少妇v888av| av免费观看网址| 一区二区高清免费观看影视大全 | 男女视频网站在线观看| 国产一区二区三区亚洲| 97视频国产在线| 日韩一区二区三区中文字幕| 日韩欧美亚洲成人| a资源在线观看| 久久精品国产一区二区| 国产又粗又爽又黄的视频| 日韩免费成人| 97在线免费观看视频| 日韩一二三四| 欧美日韩国产大片| 青青操国产视频| 粉嫩av一区二区三区粉嫩| 亚洲美免无码中文字幕在线 | 亚洲欧美中文日韩在线| 亚洲av综合一区| 日韩美女精品在线| 国模无码视频一区| 三级亚洲高清视频| 综合一区中文字幕| julia中文字幕一区二区99在线| 91精品国产色综合久久不卡98| 免费理论片在线观看播放老| 欧美色综合久久| 欧美亚洲日本在线| 久久婷婷综合激情| 一级黄色片国产| 亚洲麻豆av| 亚洲精品在线观看免费| 伊人久久大香线蕉av超碰| 日本三级韩国三级久久| 大片免费在线观看| 亚洲码在线观看| 国产高清在线免费| 色综合久久中文综合久久牛| 欧美性猛交xxxxx少妇| 91丨九色丨蝌蚪富婆spa| 国产成人美女视频| 夜夜精品视频| 国产三级中文字幕| 嫩草影视亚洲| 国产精品日韩一区二区免费视频| 亚洲天堂一区二区| 久久久久久国产| www免费网站在线观看| 精品国产乱码久久久久久浪潮| 在线观看亚洲国产| 色综合一区二区| 一区二区三区免费高清视频| 国产精品私人自拍| 免费看黄色aaaaaa 片| 国产麻豆成人传媒免费观看| 999在线免费视频| 国产欧美在线| 国产一级做a爰片久久毛片男| 成人在线国产| 免费精品视频一区| 成人午夜大片| 成人中文字幕在线观看| 天堂久久午夜av| 日本国产高清不卡| 成人观看网址| 久久99国产精品自在自在app| aiai在线| 亚洲图片制服诱惑| 亚洲 国产 欧美 日韩| 精品国产网站在线观看| 国产模特av私拍大尺度| 欧美日韩精品一区视频| 中文字幕日本视频| 色综合中文综合网| av图片在线观看| 天天爽夜夜爽夜夜爽精品视频| 国产乱国产乱老熟300| 中文字幕在线不卡一区二区三区| 日韩一级av毛片| 久久综合九色综合97婷婷女人| 挪威xxxx性hd极品| 成人午夜激情在线| 野战少妇38p| 粉嫩13p一区二区三区| 乳色吐息在线观看| 国产精品影视在线| 精品人妻一区二区乱码| 国产精品亚洲一区二区三区在线 | 亚洲高清国产拍精品26u| 国产精品久久久久久久久久尿| 精品国产免费人成网站| 欧洲午夜精品久久久| 92国产精品| 国产精品www色诱视频| 国产精品美女午夜爽爽| 国产欧美va欧美va香蕉在| 日日夜夜亚洲精品| 91探花福利精品国产自产在线| 高清久久精品| av电影成人| 麻豆精品99| 欧美日韩精品综合| 青青草综合网| 免费国产成人看片在线| 欧美日韩国产欧| 亚洲中文字幕无码av永久| 国产欧美三级| youjizzxxxx18| 国产尤物一区二区在线| 波多野结衣三级视频| 99久久99久久精品免费看蜜桃| 国产精品无码一区二区三区免费| 久久久99精品久久| 天天操夜夜操av| 亚洲一线二线三线视频| 日韩精品在线免费视频| 欧美制服丝袜第一页| a天堂在线观看视频| 亚洲国产精品人人爽夜夜爽| 九色蝌蚪在线| 久久天天躁日日躁| www.youjizz.com在线| 日韩免费在线免费观看| 四虎国产精品免费久久| 国产精品 日韩| 国产成人一区二区三区影院| 午夜在线视频免费观看| 亚洲激情欧美| 超碰在线97免费| 国产成人av资源| 成人乱码一区二区三区av| 中文字幕一区二区日韩精品绯色| 欧美国产在线看| 色婷婷综合久久久久中文| 国产精品久久久久久久免费| 亚洲精品动漫100p| 免费黄色网页在线观看| 97免费在线视频| 四虎地址8848精品| 欧美凹凸一区二区三区视频 | 分分操这里只有精品| 青青草国产精品亚洲专区无| 免费看三级黄色片| 国产欧美精品一区二区色综合| 午夜少妇久久久久久久久| 色婷婷激情综合| www.五月激情| 中文字幕日韩欧美| 午夜激情在线播放| 97人人模人人爽视频一区二区| 成人久久一区| 日韩在线综合网| 国产精品1区二区.| 中文字幕91视频| 黑人与娇小精品av专区| 国产叼嘿视频在线观看| 日韩在线视频观看| av高清一区| 久草热久草热线频97精品| 欧美伊人久久| 国产高潮免费视频| 91看片淫黄大片一级在线观看| 精品99久久久久成人网站免费| 欧美日韩国产精品成人| 男女污视频在线观看| 韩国三级电影久久久久久| 欧美经典一区| 中文字幕一区二区三区在线乱码| 天堂成人国产精品一区| 一级特黄a大片免费| 夜夜嗨av一区二区三区| 99久久久国产精品无码免费| 在线激情影院一区| 影音成人av| 青青草成人网| 日韩精品国产欧美| 人妻体内射精一区二区| 精品福利一区二区| 日批视频免费播放| 欧美激情乱人伦| 超碰97久久| 亚洲精品蜜桃久久久久久| 成人小视频免费观看| 黄色在线观看免费| 精品久久久久一区二区国产| 午夜羞羞小视频在线观看| 亚洲综合中文字幕68页| 天堂美国久久| 日韩a一级欧美一级| 中文字幕欧美一| 国产精品国产av| 久久国产精彩视频| 欧美日本三级| www.日本三级| 波多野结衣中文一区| 日韩免费在线视频观看| 亚洲激情在线观看| 深夜成人在线| 视频一区二区精品| 久久精品国产秦先生| 国产一二三四区| 精品少妇一区二区三区日产乱码 | 天堂一区在线观看| 亚洲欧美在线高清| 精品人妻少妇嫩草av无码专区| 久99久在线视频| 成人黄色av网址| 欧美精品色婷婷五月综合| 久久久精品蜜桃| 亚洲一卡二卡在线观看| 久久精品小视频| 亚洲一级大片| 久久久免费视频网站| 国产欧美日韩中文久久| 91一区二区视频| 韩剧1988免费观看全集| 制服丝袜日韩| 成人综合久久网| 伊人开心综合网| 香蕉视频黄色片| 国产精品91久久久| 在线中文字幕亚洲| 国产一级二级视频| 欧美日韩美女一区二区| 色呦呦呦在线观看| 欧美日韩综合久久| 国产一区二区三区黄视频 | 中文字幕在线免费看线人 | 亚洲香蕉在线视频| 久久久免费精品| av资源久久| 国产精品二区视频| 欧美日韩亚洲视频一区| 久cao在线| 久久精品国产第一区二区三区最新章节 | 最新在线中文字幕| 欧美黑人性生活视频| 日韩电影在线视频| www.com日本| 欧美日韩国产一区|