# mugen **Repository Path**: llyb120/mugen ## Basic Information - **Project Name**: mugen - **Description**: 插件引擎,允许基于内核规则编写自己的插件部署,并对已上线的插件进行热更新或热修复,为开发提供无限的可能。 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 2 - **Created**: 2021-10-24 - **Last Updated**: 2025-01-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # M·U·G·E·N

#### 介绍 插件引擎,允许基于内核规则编写自己的插件部署,并对已上线的插件进行热更新或热修复,为开发提供无限的可能。
#### 快速开始 1. pom文件中加入远程仓库地址 ```xml gitee https://gitee.com/llyb120/commonlib/raw/master/ ``` 2. pom文件中加入依赖 ```xml com.github.llyb120 mugen 0.0.1 ``` 3. pom文件中加入编译插件 ```xml org.apache.maven.plugins maven-compiler-plugin 3.3 1.8 1.8 -parameters ``` 4. 初始化一个PluginRunner ```java PluginOption option = PluginOption .builder() //插件所工作的目录,该目录下的所有文件都会被当做插件加载 .pluginDir("D:\\work\\mugen\\target\\test-classes\\com\\github\\llyb120\\mugen\\test") //插件文件的相对于插件目录的基础包名 .basePackage("com.github.llyb120.mugen.test") .build(); PluginRunner runner = new PluginRunner(option); ``` 5. 书写一个简单的插件 在类名上添加@Plugin注解,在需要对外暴露的方法中添加@Executable注解,如下是将foo方法暴露为一个名字为foo的插件入口 ```java @Plugin public class Test { @Executable("foo") public void foo(){ System.out.println("bar"); } } ``` 6. 运行插件 ```java runner.execute("foo"); //will print "bar" ```
#### 加载器 针对不同类型的插件,可以自定义加载器进行加载,例如对加密插件实现解密功能,或者对插件进行licence授权。 加载器需实现PluginFileLoader接口,该接口包含两个方法,match指明了何时使用该加载器,analyze返回解析后的结果。 以下是一个zip插件的简单实现 ```java public class ZipFileLoader implements PluginFileLoader{ @Override public boolean match(String extName) { return extName.equals("zip"); } @Override public Map analyze(PluginOption option, List files) { Map ret = new HashMap<>(); try{ for (File file : files) { @Cleanup FileInputStream fis = new FileInputStream(file); ZipInputStream zin = new ZipInputStream(fis); ZipEntry entry = null; while((entry=zin.getNextEntry())!=null){ if(entry.isDirectory()||entry.getName().equals("..\\")) continue; if(!entry.getName().endsWith(".class")){ continue; } String className = option.getBasePackage() + "." + (entry.getName()) .replace(".class", "") .replaceAll("\\\\|/", ".");; byte[] bytes = IoUtil.readBytes(zin, false); ByteClass byteclass = ByteClass.builder() .className(className) .bytes(bytes) .build(); ret.put(byteclass.getClassName(), byteclass); } } } catch (Exception e){ e.printStackTrace(); } return ret; } } ``` 在构建PluginOption的时候添加该加载器 ```java PluginOption option = PluginOption .builder() .pluginDir("D:\\work\\mugen\\target\\test-classes\\com\\github\\llyb120\\mugen\\test") .basePackage("com.github.llyb120.mugen.test") .loader(new ZipFileLoader()) .build(); ```
#### 依赖注入 使用@Resource注解可在插件类中注入需要的bean,大体功能同spring的@Resource。 ```java @Plugin public class Test { @Resource PluginRunner runner; //default bean @Resource String test; //abc @Executable("foo") public void foo(){ System.out.println("bar"); } } ``` 需要注入的bean可在PluginOption中使用inject进行配置,可以对指定类型进行注入,如果类型出现冲突,则可以指定名称进行注入。 ```java PluginOption option = PluginOption .builder() .pluginDir("D:\\work\\mugen\\target\\test-classes\\com\\github\\llyb120\\mugen\\test") .basePackage("com.github.llyb120.mugen.test") .inject("test", "abc") .inject(Test.class, new Test()) .build(); ```
#### 链路器/锚点 链路器会追踪@Executable的调用链,使用者可以在调用链上插入别的方法,只需要在方法上添加@Hook即可 ```java @Executable("/test/t1") public void t1(int a){ //do anything t2(); } public void t2(){ //do anything t3(); } @Executable("/test/c3") public void t3(){ //do anything } @Hook("/test/c1->*->!/test/c3") //从/test/c1开始,经过任何调用,到/test/c3之前,!只能存在于最后,表示调用之前 //@Hook("/test/c1->*->/test/c3") //从/test/c1开始,经过任何调用,到/test/c3之后 //@Hook("/test/c1->/test/c3") //从/test/c1开始,到/test/c3之后 //@Hook("*->/test/c3") //从任何步骤开始,到/test/c3之后 //@Hook("/test/c1->*") //从/test/c1开始,调用任何步骤 public void test(PluginChainContext context){ //读取当时上下文中的a参数 int olda = (int) context.getParameters().get("a"); //修改该参数,并继续调用链 context.getParameters().put("a", olda + 100); } ```
#### 自定义链路器 实现PluginTracker接口可自定义链路器 ```java public class DefaultTracker implements PluginTracker{ private volatile Map patternMap = new ConcurrentHashMap<>(); @Override public boolean canTrace(String hookValue, String str) { Pattern p = patternMap.computeIfAbsent(hookValue, k -> { return Pattern.compile("^" + hookValue.replace("*->", "(.+->)?").replace("*", "((?!!).+)") + "$"); }); return p.matcher(str).find(); } } ``` 将其注册到PluginOption中 ```java option.tracker("custom", new DefaultTracker()); ``` 在@Hook中指定自定义的链路器 ```java @Plugin public class Test { @Executable("foo") public void foo(){ System.out.println("bar"); } @Hook(value = "*->!foo", tracker = "custom") public void test(){ System.out.println(123); } } ```
#### 热响应 如果有新插件被添加,或是修复旧插件的bug,只需要调用refresh方法即可,无需重启应用 ```java runner.refresh() ```