来源:blog.csdn.net/zhangcongyi420/article/details/131139599
举例来说,代码中可以使用设计模式来选择使用哪种方式发送短信给下单完成的客户,问题是各个短信服务商并不一定能保证在任何情况下都能发送成功,怎么办呢?这时候设计模式也没法帮你解决这个问题,如果使用定制化插件的方式,结合外部配置参数,假设系统中某种短信发送不出去了,这时候就可以利用插件动态植入,切换为不同的厂商发短信了。
spi机制; 约定配置和目录,利用反射配合实现; springboot中的Factories机制; java agent(探针)技术; spring内置扩展点; 第三方插件包,例如:spring-plugin-core; spring aop技术;
public interface MessagePlugin {
public String sendMsg(Map msgMap);
}
public class AliyunMsg implements MessagePlugin {
public String sendMsg(Map msgMap) {
System.out.println("aliyun sendMsg");
return "aliyun sendMsg";
}
}
public class TencentMsg implements MessagePlugin {
public String sendMsg(Map msgMap) {
System.out.println("tencent sendMsg");
return "tencent sendMsg";
}
}
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);
}
}
A应用定义接口; B,C,D等其他应用定义服务实现; B,C,D应用实现后达成SDK的jar; A应用引用SDK或者将SDK放到某个可以读取到的目录下; A应用读取并解析SDK中的实现类;
server :
port : 8081
impl:
name : com.congge.plugins.spi.MessagePlugin
clazz :
- com.congge.plugins.impl.TencentMsg
- com.congge.plugins.impl.AliyunMsg
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("impl")
@ToString
public class ClassImpl {
@Getter
@Setter
String name;
@Getter
@Setter
String[] clazz;
}
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;
public class SendMsgController {
ClassImpl classImpl;
//localhost:8081/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";
}
}
({ClassImpl.class})
public class PluginApp {
public static void main(String[] args) {
SpringApplication.run(PluginApp.class,args);
}
}
应用A定义服务接口; 应用B,C,D等实现接口(或者在应用内部实现相同的接口); 应用B,C,D打成jar,放到应用A约定的读取目录下; 应用A加载约定目录下的jar,通过反射加载目标方法;
@Component
public class 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;
}
}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 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();
return new File(path).getParent();
}
}
"/sendMsgV2") (
public String index() throws Exception {
String result = serviceLoaderUtils.doExecuteMethod();
return result;
}
loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表; loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表;
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) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
com.xxx.interface=com.xxx.classname 如果一个接口希望配置多个实现类,可以使用’,’进行分割
public interface SmsPlugin {
public void sendMessage(String message);
}
public class BizSmsImpl implements SmsPlugin {
public void sendMessage(String message) {
System.out.println("this is BizSmsImpl sendMessage..." + message);
}
}
public class SystemSmsImpl implements SmsPlugin {
public void sendMessage(String message) {
System.out.println("this is SystemSmsImpl sendMessage..." + message);
}
}
\ =
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl
public String sendMsgV3(String msg) throws Exception{
List<SmsPlugin> smsServices= SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
for(SmsPlugin smsService : smsServices){
smsService.sendMessage(msg);
}
return "success";
}
3个微服务模块,在A模块中有个插件化的接口; 在A模块中的某个接口,需要调用插件化的服务实现进行短信发送; 可以通过配置文件配置参数指定具体的哪一种方式发送短信; 如果没有加载到任何插件,将走A模块在默认的发短信实现;
biz-pp定义服务接口,并提供出去jar被其他实现工程依赖; bitpt与miz-pt依赖biz-pp的jar并实现SPI中的方法; bitpt与miz-pt按照API规范实现完成后,打成jar包,或者安装到仓库中; biz-pp在pom中依赖bitpt与miz-pt的jar,或者通过启动加载的方式即可得到具体某个实现;
public interface MessagePlugin {
public String sendMsg(Map msgMap);
}
import com.congge.plugin.spi.MessagePlugin;
import com.congge.spi.BitptImpl;
import com.congge.spi.MizptImpl;
import java.util.*;
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 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();
}
}
public class SmsController {
private SmsService smsService;
private ServiceLoaderUtils serviceLoaderUtils;
//localhost:8087/sendMsg?msg=sendMsg
public String sendMessage(String msg){
return smsService.sendMsg(msg);
}
}
public class SmsService {
private String msgType;
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);
}
}
<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>
<dependencies>
<dependency>
<groupId>com.congge</groupId>
<artifactId>biz-app</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
public class BitptImpl implements MessagePlugin {
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";
}
}
com.congge.spi.BitptImpl
对编程、职场感兴趣的同学,可以链接我,微信:coder301 拉你进入“程序员交流群”。