网站建设注意哪些事项,濮阳做网站优化,枣庄做网站公司,常用的开发环境有哪几种一、背景顾名思义#xff0c;插件化开发就是将某个功能代码封装为一个插件模块#xff0c;通过插件中心的配置来下载、激活、禁用、或者卸载#xff0c;主程序无需再次重启即可获取新的功能#xff0c;从而实现快速集成。当然#xff0c;实现这样的效果#xff0c;必须遵…一、背景顾名思义插件化开发就是将某个功能代码封装为一个插件模块通过插件中心的配置来下载、激活、禁用、或者卸载主程序无需再次重启即可获取新的功能从而实现快速集成。当然实现这样的效果必须遵守一些插件接口的标准不能与已有的功能冲突。目前能支持插件化开发的成熟框架很多但本文仅从思路的实现角度从0到1实现简单的插件化开发框架。二、实现思路思路定义插件接口 - 实现插件接口 - 通过反射机制加载插件 - 调用插件方法。开发语言支持反射机制的所有高级语言均可实现插件式开发或有 FFI 调用 Native 函数的编程语言。三、Java 通过反射机制实现插件化开发1、创建插件接口定义插件接口一个执行方法package service;/*** 通用插件接口** author yushanma* since 2023/3/5 16:36*/
public interface IPluginService {/*** 执行插件*/public void run();
}
2、实现插件接口package impl;
import service.IPluginService;/*** 打印插件** author yushanma* since 2023/3/5 16:37*/
public class MyPrinterPlugin implements IPluginService {Overridepublic void run() {System.out.println(执行插件方法...);}
}
3、插件中心管理与加载插件。Step 1、插件实体类封装package entity;import lombok.Data;/*** 插件实体类** author yushanma* since 2023/3/5 16:44*/
Data
public class PluginEntity {/*** 插件名*/private String pluginName;/*** 插件路径*/private String jarPath;/*** 字节码名字*/private String className;
}
需要获取插件名、插件实现的Jar包路径、字节码路径Step 2、通过反射机制实现插件实例化package loader;import entity.PluginEntity;
import exception.PluginException;
import lombok.Data;
import lombok.NoArgsConstructor;
import service.IPluginService;import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 插件管理器** author yushanma* since 2023/3/5 16:44*/
Data
NoArgsConstructor
public class PluginManager {private MapString, Class? clazzMap new HashMap();public PluginManager(ListPluginEntity plugins) throws PluginException {initPlugins(plugins);}public void initPlugin(PluginEntity plugin) throws PluginException {try {//URL url new URL(file: plugin.getJarPath());URL url new File(plugin.getJarPath()).toURI().toURL();URLClassLoader classLoader new URLClassLoader(new URL[]{url});Class? clazz classLoader.loadClass(plugin.getClassName());clazzMap.put(plugin.getClassName(), clazz);} catch (Exception e) {throw new PluginException(plugin plugin.getPluginName() init error: e.getMessage());}}public void initPlugins(ListPluginEntity plugins) throws PluginException {for (PluginEntity plugin : plugins) {initPlugin(plugin);}}public IPluginService getInstance(String className) throws PluginException {Class? clazz clazzMap.get(className);Object instance null;try {instance clazz.newInstance();} catch (Exception e) {throw new PluginException(plugin className instantiate error, e.getMessage());}return (IPluginService) instance;}
}
Step 3、通过 XML 文件来配置管理插件 dependencygroupIdorg.dom4j/groupIdartifactIddom4j/artifactIdversion2.1.1/version/dependencypackage conf;import entity.PluginEntity;
import exception.PluginException;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.File;
import java.util.ArrayList;
import java.util.List;/*** 解析 XML 插件配置** author yushanma* since 2023/3/5 16:44*/
public class PluginXmlParser {public static ListPluginEntity getPluginList() throws PluginException {ListPluginEntity list new ArrayList();SAXReader saxReader new SAXReader();Document document null;try {document saxReader.read(new File(src/main/resources/plugin.xml));} catch (Exception e) {throw new PluginException(read plugin.xml error, e.getMessage());}Element root document.getRootElement();List? plugins root.elements(plugin);for (Object pluginObj : plugins) {Element pluginEle (Element) pluginObj;PluginEntity plugin new PluginEntity();plugin.setPluginName(pluginEle.elementText(name));plugin.setJarPath(pluginEle.elementText(jar));plugin.setClassName(pluginEle.elementText(class));list.add(plugin);}return list;}}
!-- plugin.xml --
?xml version1.0 encodingUTF-8?
pluginspluginname测试插件/namejarplugins/PrinterPlugin-1.0-SNAPSHOT.jar/jarclassimpl.MyPrinterPlugin/class/pluginpluginname测试插件/namejarplugins/PrinterPlugin-1.0-SNAPSHOT.jar/jarclassimpl.MyPrinterPlugin/class/plugin
/pluginsStep 4、解析 XML 文件并加载插件package loader;import conf.PluginXmlParser;
import entity.PluginEntity;
import exception.PluginException;
import service.IPluginService;import java.util.List;/*** 插件加载器** author yushanma* since 2023/3/5 16:44*/
public class PluginLoader {public void run() throws PluginException {// 从配置文件加载插件ListPluginEntity pluginList PluginXmlParser.getPluginList();PluginManager pluginManager new PluginManager(pluginList);for (PluginEntity plugin : pluginList) {IPluginService pluginService pluginManager.getInstance(plugin.getClassName());System.out.println(开始执行[ plugin.getPluginName() ]插件...);// 调用插件pluginService.run();System.out.println([ plugin.getPluginName() ]插件执行完成);}// 动态加载插件
// PluginEntity plugin new PluginEntity();
// plugin.setPluginName();
// plugin.setJarPath();
// plugin.setClassName();
// pluginManager.initPlugin(plugin);
// IPluginService pluginService pluginManager.getInstance();
// pluginService.run();}
}
4、测试效果import exception.PluginException;
import loader.PluginLoader;/*** desc** author yushanma* since 2023/3/5 16:44*/
public class DemoMain {public static void main(String[] args) throws PluginException {PluginLoader loader new PluginLoader();loader.run();}
}
四、Rust 通过 libloader 库实现插件化开发通过 libloader 库可以调用动态链接库函数需要 FFI 支持。Step 1、创建 libcargo new --lib mydll// 有参数没有返回值
#[no_mangle]
pub fn println(str: str) {println!({}, str);
}// 有参数有返回值
#[no_mangle]
pub fn add(a: usize, b: usize) - usize {a b
}// 没有参数没有返回值
#[no_mangle]
pub fn print_hello() {println!(Hello);
}// 字符串类型
#[no_mangle]
pub fn return_str(s1: str) - str{s1
}
Step 2、toml 配置编译类型[package]
name mydll
version 0.1.0
edition 2021# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]# rlibRust库这是cargo new默认的种类只能被Rust调用
# dylibRust规范的动态链接库windows上编译成.dlllinux上编译成.so也只能被Rust调用
# cdylib满足C语言规范的动态链接库windows上编译成.dlllinux上编译成.so可以被其他语言调用
# staticlib静态库windows上编译成.liblinux上编译成.a可以被其他语言调用[lib]
crate-type [cdylib]
Step 3、编译为 dllcargo build可以看到所有的函数都被正常导出具体原理请参考https://fasterthanli.me/articles/so-you-want-to-live-reload-rustStep 4、动态加载 dlluse cstr::cstr;
use libloader::*;
use std::{ffi::CStr,os::raw::c_char};fn main() {get_libfn!(dll/mydll.dll, println, println, (), s: str);println(你好);get_libfn!(dll/mydll.dll, add, add, usize, a: usize, b: usize);println!( 1 2 {}, add(1, 2));get_libfn!(dll/mydll.dll, print_hello, print_hello, bool);print_hello();get_libfn!(dll/mydll.dll,return_str, return_str,*const c_char, s: *const c_char);let str unsafe { CStr::from_ptr(return_str(cstr!(你好 ).as_ptr())) };print!(out {}, str.to_str().unwrap());
}
五、C# 通过反射机制实现插件化开发Step 1、定义插件接口namespace PluginInterface
{public interface IPlugin{// 获取插件名字public string GetName();// 获取插件所提供的功能列表public string[] GetFunction();// 执行插件某个功能public bool Execute(string fn);}}
Step 2、实现插件接口using PluginInterface;
using System;
using System.Linq;namespace MyPlugin
{public class PrinterPlugin : IPlugin{private static readonly string PLUGIN_NAME PrinterPlugin;// 获取插件名字public string GetName(){return PLUGIN_NAME;}// 获取插件所提供的功能列表public string[] GetFunction(){return PrinterFunc.FuncDics.Keys.ToArray();}// 执行插件某个功能public bool Execute(string fn){return PrinterFunc.Run(fn);}// 传参功能public static object PrintLabel(string sn){Console.WriteLine($打印标签{sn}...DONE);return true;}}
}
using System;
using System.Collections.Generic;namespace MyPlugin
{// 封装打印机支持的功能internal class PrinterFunc{// 功能字典public static Dictionarystring, Funcbool FuncDics new Dictionarystring, Funcbool{{PrintPhoto,PrintPhoto },{PrintDoc,PrintDoc }};// 执行某个功能public static bool Run(string name){if (!FuncDics.ContainsKey(name)){return false;}return (bool)FuncDics[name].Invoke();}// 打印照片public static bool PrintPhoto(){Console.WriteLine(打印照片...DONE);return true;}// 打印文档public static bool PrintDoc(){Console.WriteLine(打印文档...DONE);return true;}}}
Step 3、通过反射实例化插件using PluginInterface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;namespace CLI.Loader
{public class PluginLoader{// 初始化时加载插件public PluginLoader(){LoadPlugin();}public Dictionarystring, IPlugin ListName new Dictionarystring, IPlugin();// 加载所有插件public void LoadPlugin(){try{// 清除所有插件缓存ListName.Clear();// 插件文件夹string fileName D:\\AwsomeWorkSpace\\CLI\\Plugins\\net5.0\\;// 获取所有插件文件DirectoryInfo info new DirectoryInfo(fileName);FileInfo[] files info.GetFiles();foreach (FileInfo file in files){if (!file.FullName.EndsWith(.dll)){continue;}// 通过反射机制创建插件实例Assembly assembly Assembly.LoadFile(file.FullName);Type[] types assembly.GetTypes();foreach (Type type in types){// 如果某些类实现了预定义的插件接口则认为该类适配与主程序(是主程序的插件)if (type.GetInterface(IPlugin) ! null){// 创建该类实例IPlugin plugin assembly.CreateInstance(type.FullName) as IPlugin;if (plugin null){throw new Exception(插件错误);}ListName.Add(plugin.GetName(), plugin);// 调用插件的某个传参方法MethodInfo printLabel type.GetMethod(PrintLabel);object res printLabel.Invoke(plugin, parameters: new object[] { HQ31122222222222 });Console.WriteLine(res?.ToString());// 调用插件内部的 Execute 方法MethodInfo execute type.GetMethod(Execute);res execute.Invoke(plugin, parameters: new object[] { PrintPhoto });Console.WriteLine(res?.ToString());res execute.Invoke(plugin, parameters: new object[] { PrintDoc });Console.WriteLine(res?.ToString());}}}}catch (Exception e){Console.WriteLine(e.Message);}}// 插件启动public void Start(){Console.WriteLine( 插件中心 );Console.WriteLine(1--加载插件列表);Console.WriteLine(2--重新刷新插件);int switchVal int.Parse(Console.ReadLine());switch (switchVal){case 1:GetPluginList();break;case 2:LoadPlugin();break; ;}}// 加载插件列表public void GetPluginList(){Console.WriteLine(--------插件列表--------);foreach (var VARIABLE in ListName.Keys){Console.WriteLine($----{VARIABLE});}Console.WriteLine(--------请输入插件名--------);GetPluginFunc(Console.ReadLine());}// 加载插件功能public void GetPluginFunc(string pluginName){if (!ListName.ContainsKey(pluginName)){return;}IPlugin plugin ListName[pluginName];string[] funcList plugin.GetFunction();for (int i 0; i funcList.Length; i){Console.WriteLine(funcList[i]);plugin.Execute(funcList[i]);}}}
}
ok可以看到插件化开发的实现并不复杂但是其中用到的反射机制会消耗部分性能并且 dll 也会存在一些逆向工程或者反向注入等信安问题需要谨慎使用。当然框架的完善更是任重道远的过程。六、.NET 6/7 导出非托管函数能力环境Visual Studio 2022 / .NET7参考https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/docsStep 1、创建类库项目dotnet new classlib -o mydll -f net6.0Step 2、配置 AOT NativeProject SdkMicrosoft.NET.SdkPropertyGroupTargetFrameworknet7.0/TargetFrameworkImplicitUsingsenable/ImplicitUsingsNullableenable/NullablePublishAottrue/PublishAot/PropertyGroup/Project
Step 3、导出非托管函数using System.Runtime.InteropServices;
using Seagull.BarTender.Print;namespace ClassLibrary1
{public class Class1{// 无参数有返回值[UnmanagedCallersOnly(EntryPoint IsOk)]public static bool IsOk(){return true;}// 有参数无返回值[UnmanagedCallersOnly(EntryPoint MyPrinter)]public static void MyPrinter(IntPtr pString){try{if (pString ! IntPtr.Zero){string str new(Marshal.PtrToStringAnsi(pString));Console.WriteLine(str);}}catch (Exception e){Console.WriteLine( Exception e.Message);}}// 有参数有返回值[UnmanagedCallersOnly(EntryPoint MyConcat)]public static IntPtr MyConcat(IntPtr pString1, IntPtr pString2){string concat ;try{if (pString1 ! IntPtr.Zero pString2 ! IntPtr.Zero){string str1 new(Marshal.PtrToStringAnsi(pString1));string str2 new(Marshal.PtrToStringAnsi(pString2));concat string.Concat(str1, str2);}}catch (Exception e){concat e.Message;}return Marshal.StringToHGlobalAnsi(concat);}// 无参数无返回值[UnmanagedCallersOnly(EntryPoint PrintHello)]public static void PrintHello(){Console.WriteLine( Hello);}}
}Step 4、查看导出结果dotnet publish /p:NativeLibShared /p:SelfContainedtrue -r win-x64 -c release可以看到 native 、publish 文件夹里面的 dll 文件函数正常导出最后一个是默认导出的函数。