SpringBoot 插件化開發模式

来源:https://www.cnblogs.com/wandaren/archive/2023/07/02/17520764.html
-Advertisement-
Play Games

# 1、Java常用插件實現方案 ## 1.2、serviceloader方式 serviceloader是java提供的spi模式的實現。按照介面開發實現類,而後配置,java通過ServiceLoader來實現統一介面不同實現的依次調用。而java中最經典的serviceloader的使用就是J ...


1、Java常用插件實現方案

1.2、serviceloader方式

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

1.2.1、java spi

SPI全稱 Service Provider Interface ,是JDK內置的一種服務發現機制,SPI是一種動態替換擴展機制,比如有個介面,你想在運行時動態給他添加實現,你只需按照規範給他添加一個實現類即可。比如大家熟悉的jdbc中的Driver介面,不同的廠商可以提供不同的實現,有mysql的,也有oracle的,而Java的SPI機制就可以為某個介面尋找服務的實現。
下麵用一張簡圖說明下SPI機制的原理
image.png

1.2.2、java spi 簡單案例

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

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";
    }
}
public class TencentMsg implements MessagePlugin {
 
    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("tencent sendMsg");
        return "tencent sendMsg";
    }
}

在resources目錄按照規範要求創建文件目錄(META-INF/services),文件名為介面的全限定名,並填寫實現類的全限定類名。
image.png
自定義服務載入類

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的方式可以載入到不同介面的實現,業務中只需要根據自身的需求,結合配置參數的方式就可以靈活的控制具體使用哪一個實現。
image.png

1.2、自定義配置約定方式

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

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

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

1.2.1、添加配置文件

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

server:
  port: 8888
impl:
  name: com.wq.plugins.spi.MessagePlugin
  clazz:
    - com.wq.plugins.impl.AliyunMsg
    - com.wq.plugins.impl.TencentMsg

1.2.2、自定義配置文件載入類

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

package com.wq.propertie;

import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Arrays;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2023/7/1
 * @Author wandaren
 */
// 啟動類需要添加@EnableConfigurationProperties({ClassImpl.class})
@ConfigurationProperties("impl") 
public class ClassImpl {
   private String name;
   private String[] clazz;

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public String[] getClazz() {
      return clazz;
   }

   public void setClazz(String[] clazz) {
      this.clazz = clazz;
   }

   public ClassImpl(String name, String[] clazz) {
      this.name = name;
      this.clazz = clazz;
   }

   public ClassImpl() {
   }

   @Override
   public String toString() {
      return "ClassImpl{" +
              "name='" + name + '\'' +
              ", clazz=" + Arrays.toString(clazz) +
              '}';
   }
}

1.2.3、自定義測試介面

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

package com.wq.contorller;

import com.wq.plugins.spi.MessagePlugin;
import com.wq.propertie.ClassImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2023/7/1
 * @Author wandaren
 */
@RestController
public class HelloController {

    @Autowired
    private ClassImpl 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";
    }
}

1.2.4、啟動類

package com.wq;

import com.wq.propertie.ClassImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;


@EnableConfigurationProperties({ClassImpl.class})
@SpringBootApplication
public class DemoApplication {

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

}

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

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

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

  • 應用A定義服務介面(安裝到maven);
  • 應用B,C,D等實現介面(或者在應用內部實現相同的介面),dependency引用服務A;
  • 應用B,C,D打成jar,放到應用A約定的讀取目錄下;
  • 應用A載入約定目錄下的jar,通過反射載入目標方法;

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

  • 應用A定義介面
public interface MessagePlugin {
 
    public String sendMsg(Map msgMap);
 
}

安裝到本地maven倉庫
image.png

  • 應用B,C實現介面
        <dependency>
            <groupId>com.wq</groupId>
            <artifactId>spi-00</artifactId>
            <version>1</version>
        </dependency>
  • 應用B
public class AliyunMsg implements MessagePlugin {
 
    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("aliyun sendMsg");
        return "aliyun sendMsg";
    }
}

image.png

  • 應用C
public class TencentMsg implements MessagePlugin {
 
    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("tencent sendMsg");
        return "tencent sendMsg";
    }
}

image.png

  • 將應用B、C打成jar

image.png
在工程下創建一個lib目錄,並將依賴的jar放進去
image.png

1.3.2、新增讀取jar的工具類

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

package com.wq.utils;

import com.wq.propertie.ClassImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Component
public class ServiceLoaderUtils {
 
    @Autowired
    ClassImpl classImpl;
 
 
    public static void loadJarsFromAppFolder() throws Exception {
        String path = "/Users/wandaren/develop/study/spi-00/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 = "/Users/wandaren/develop/study/spi-00/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;
                //執行反射相關的方法
                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("spi-01-1.jar")){
                        if(!claName.equals("com.wq.plugins.impl.AliyunMsg")){
                            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;
                        }
                    }else if(name.equals("spi-02-1.jar")){
                        if(!claName.equals("com.wq.plugins.impl.TencentMsg")){
                            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 = "/Users/wandaren/develop/study/spi-00/lib/spi-01-1.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.wq.plugins.impl.AliyunMsg");
        //獲取實例
        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();
        return new File(path).getParent();
    }
 
 
 
}

1.3.3、添加測試介面

    @Autowired
    private ServiceLoaderUtils serviceLoaderUtils;

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

以上全部完成之後,啟動工程,測試一下該介面,仍然可以得到預期結果;
image.png
在上述的實現中還比較粗糙的,實際運用時,還需要做較多的優化改進以滿足實際的業務需要,比如介面傳入類型參數用於控制具體使用哪個依賴包的方法進行執行等;

2、SpringBoot中的插件化實現

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

2.1、 Spring Boot中的SPI機制

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

2.2、 Spring Factories實現原理

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

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

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

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

從代碼中我們可以知道,在這個方法中會遍歷整個ClassLoader中所有jar包下的spring.factories文件,就是說我們可以在自己的jar中配置spring.factories文件,不會影響到其它地方的配置,也不會被別人的配置覆蓋。
spring.factories的是通過Properties解析得到的,所以我們在寫文件中的內容都是安裝下麵這種方式配置的:

com.xxx.interface=com.xxx.classname

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

2.3、Spring Factories案例實現

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

2.3.1、定義一個服務介面

自定義一個介面,裡面添加一個方法;

public interface SmsPlugin {
 
    public void sendMessage(String message);
 
}

2.3.2、 定義2個服務實現

實現類1

package com.wq.plugin.impl;

import com.wq.plugin.SmsPlugin;

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

實現類2

package com.wq.plugin.impl;

import com.wq.plugin.SmsPlugin;

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

2.3.3、 添加spring.factories文件

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

com.wq.plugin.SmsPlugin=\
com.wq.plugin.impl.BizSmsImpl,\
com.wq.plugin.impl.SystemSmsImpl

2.3.4、 添加自定義介面

添加一個自定義的介面,有沒有發現,這裡和java 的spi有點類似,只不過是這裡換成了SpringFactoriesLoader去載入服務;

package com.wq.controller;

import com.wq.plugin.SmsPlugin;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2023/7/2
 * @Author wandaren
 */
@RestController
public class SmsController {
    @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";
    }
}

image.png
啟動工程之後,調用一下該介面進行測試,localhost:8080/sendMsgV3?msg=hello,通過控制台,可以看到,這種方式能夠正確獲取到系統中可用的服務實現;
image.png
利用spring的這種機制,可以很好的對系統中的某些業務邏輯通過插件化介面的方式進行擴展實現;

3、插件化機制案例實戰

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

3.1、 案例背景

  • 3個微服務模塊,在A模塊中有個插件化的介面;
  • 在A模塊中的某個介面,需要調用插件化的服務實現進行簡訊發送;
  • 可以通過配置文件配置參數指定具體的哪一種方式發送簡訊;
  • 如果沒有載入到任何插件,將走A模塊在預設的發簡訊實現;

3.1.1、 模塊結構

1、spi-00,插件化介面工程;
2、spi-01,aliyun簡訊發送實現;
3、spi-02,tncent簡訊發送實現;

3.1.2、 整體實現思路

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

  • spi-00定義服務介面,並提供出去jar被其他實現工程依賴;
  • spi-01與spi-02依賴spi-00的jar並實現SPI中的方法;
  • spi-01與spi-02按照API規範實現完成後,打成jar包,或者安裝到倉庫中;
  • spi-00在pom中依賴spi-01與的jar,spi-02或者通過啟動載入的方式即可得到具體某個實現;

3.2、spi-00添加服務介面

3.2.1、 添加服務介面

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

3.2.2、 打成jar包並安裝到倉庫

idea執行install
image.png

3.3、spi-01與spi-02實現

maven引入spi-00依賴坐標

<dependencies>
        <dependency>
            <groupId>com.wq</groupId>
            <artifactId>spi-00</artifactId>
            <version>1</version>
        </dependency>
    </dependencies>

3.3.1、spi-01

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

3.3.2、spi-02

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

image.png

3.3.3、將spi-01與spi-02打成jar

idea執行install

3.4、spi-00添加服務依賴與實現

3.4.1、添加服務依賴

  <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-boot.version>2.7.4</spring-boot.version>

    </properties>
	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>com.wq</groupId>
            <artifactId>spi-01</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>com.wq</groupId>
            <artifactId>spi-02</artifactId>
            <version>1</version>
        </dependency>
    </dependencies>

3.4.2、自定義服務載入工具類

package com.wq.spi;

import java.util.*;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2023/7/2
 * @Author wandaren
 */
public class 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 AliyunMsg){
                        targetPlugin = messagePlugin;
                        findTarget = true;
                        break;
                    }
                case "tencent":
                    if (messagePlugin instanceof TencentMsg){
                        targetPlugin = messagePlugin;
                        findTarget = true;
                        break;
                    }
                default: break;
            }
            if(findTarget) {
                break;
            }
        }
        return targetPlugin;
    }

    public static void main(String[] args) {
        new PluginFactory().installPlugin();
    }
}

3.4.3、介面實現

package com.wq.service;

import com.wq.spi.MessagePlugin;
import com.wq.spi.PluginFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service
public class 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);
    }
}
package com.wq.service;

import com.wq.spi.MessagePlugin;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2023/7/2
 * @Author wandaren
 */
@Service
public class DefaultSmsService implements MessagePlugin {
    @Override
    public String sendMsg(Map msgMap) {
        return "DefaultSmsService--------";
    }
}

3.4.4、測試controller

package com.wq.controller;

import com.wq.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SmsController {
 
    @Autowired
    private SmsService smsService;

 
    @GetMapping("/sendMsg")
    public String sendMessage(String msg){
        return smsService.sendMsg(msg);
    }
 
}

3.4.5、測試

通過修改配置application.yml中msg.type的值切換不同實現

msg:
#  type: tencent
  type: aliyun

image.png


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文以 `React`、`Vue` 為例,介紹下主流的渲染模式以及在主流框架中如何實現上述的渲染模式。 ## 前置知識介紹 看渲染模式之前我們先看下幾個主流框架所提供的相關能力,瞭解的可跳到下個章節。 ### 掛載組件到 DOM 節點 這是主流框架最基本的能力,就是將組件渲染到指定的 `DOM` 節 ...
  • 關鍵字 abstractassertbooleanbreakbyte case catch char class const continue default do double else enum extends final finally float for goto if implementi ...
  • 不說廢話,直接上乾貨: (註意大小寫:object為對象,Object為類) 1,object.getClass()它是Object類的實例方法,返回一個對象運行時的類的Class對象,換句話說,它返回的是對象具體類型的類對象。 2,Object.class 這是java語言的一種語法糖,用來返回一 ...
  • # 前言 最近針對java項目的部署方式進行整理,jenkins/tomcat/windows工具/linux腳本/web部署平臺等等 發現war包通過tomcat部署比較繁瑣,等待時間長,配置規則複雜對於小白很不友好,也難以接入到自定義的部署工具/平臺中 之前開發的Jar包部署平臺是servlet ...
  • 數組索引是指在`numpy`數組中引用特定元素的方法。`numpy`的數組索引又稱為`fancy indexing`,比其他編程語言的索引強大很多。 # 1. 選取數據 numpy的索引除了像其他語言一樣選擇一個元素,還可以間隔著選取多個元素,也可以用任意的順序選取元素。 比如一維數組: ```py ...
  • urllib+BeautifulSoup爬取並解析2345天氣王歷史天氣數據 網址:[東城歷史天氣查詢_歷史天氣預報查詢_2345天氣預報](https://tianqi.2345.com/wea_history/71445.htm) ![image-20230702161423470](https ...
  • # 數列分段 Section II ## 題目描述 對於給定的一個長度為N的正整數數列 $A_{1\sim N}$,現要將其分成 $M$($M\leq N$)段,並要求每段連續,且每段和的最大值最小。 關於最大值最小: 例如一數列 $4\ 2\ 4\ 5\ 1$ 要分成 $3$ 段。 將其如下分段: ...
  • 本文通過閱讀Spring源碼,分析Bean實例化流程。 # Bean實例化入口 上一篇文章已經介紹,Bean實例化入口在AbstractApplicationContext類的finishBeanFactoryInitialization方法: ```java protected void fini ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...