字节码导入方案
由于安装插件需要发送字节码,所以我们需要在插件的代码里填入字节码,这就使得我们在开发过程中,插件是插件,远端执行的字节码是字节码,割裂开了项目的一致性。
为了解决在插件开发过程中的割裂性,即插件与远端执行的代码在一个项目中较难同时存在(插件可以使用Java 8编译而远端不一定支持Java 8);需要有一套解决方案。
首先考虑过通过Gradle自动替换代码生成Jar,但是略显复杂;采用了下方的基于虚拟宏的方式:
- 远端代码需要在remote包下编写
- remote包不参与插件jar包编译
- remote包独立使用java 1.5编译版本,并作为独立资源文件放入/remote/下,并且修改后缀名为
.bytecode
防止本地加载jar时被误加载 - 插件可以动态通过macro工具类完成虚拟宏操作
目前提供虚拟宏如下:
宏 | 指令格式 | 描述 | 示例 |
---|---|---|---|
bytecode | bytecode className | 获取remote包的类字节码,注意不需要再传入remote包名 | bytecode FineJDBCUtils |
MacroLoader
首先需要实现的虚拟宏的加载器,加载器从/macro/
目录下加载对应的远端字节码,代码如下:
package top.evalexp.higan.plugins.util;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class MacroLoader {
interface MacroProcessor {
String processMacro(String args);
}
private static final Map<String, MacroProcessor> macroProcessors = new HashMap<>();
static {
macroProcessors.put("bytecode", args -> {
try {
ByteArrayOutputStream outputStream;
try (InputStream inputStream = MacroLoader.class.getResourceAsStream(String.format("/macro/%s.bytecode", args.replace('.', '/')))) {
outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
if (inputStream != null) {
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
}
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception e) {
return null;
}
});
}
public static String loadMacro(String macro) {
StringBuilder marcoOrder = new StringBuilder();
StringBuilder marcoArgs = new StringBuilder();
boolean isOrder = true;
for (int i = 0; i < macro.length(); i++) {
if (macro.charAt(i) == ' ') {
isOrder = false;
continue;
}
if (isOrder) {
marcoOrder.append(macro.charAt(i));
} else {
marcoArgs.append(macro.charAt(i));
}
}
return macroProcessors.containsKey(marcoOrder.toString()) ? macroProcessors.get(marcoOrder.toString()).processMacro(marcoArgs.toString()) : null;
}
}
帆软JDBC导出示例
可见帆软JDBC导出#插件代码,其中使用到的语句为
private static final String pluginBytes = MacroLoader.loadMacro("bytecode FineJDBCUtils");
Gradle自处理
想要MacroLoader能正常使用,就必须要配合Gradle编译自处理。
Remote编译
需要添加一个任务:
tasks.register('compileRemote', JavaCompile) {
group 'build'
source = fileTree('src/main/java') {
include 'remote/**'
}
sourceCompatibility = JavaVersion.VERSION_1_5
targetCompatibility = JavaVersion.VERSION_1_5
def outDir = layout.buildDirectory.dir("classes/main/remote")
destinationDirectory = outDir
classpath = sourceSets.main.compileClasspath
doLast {
file(outDir.get()).eachFileRecurse { file ->
if (file.name.endsWith(".class")) {
file.renameTo(file.absolutePath.substring(0, file.absolutePath.length() - 6) + ".bytecode")
}
}
}
}
然后在资源处理中依赖并加入资源文件中:
tasks.processResources {
dependsOn 'compileRemote'
from layout.buildDirectory.dir("classes/main/remote")
}
插件构建
需要剔除掉remote的编译:
tasks.build {
sourceSets {
main {
java {
exclude('remote/**')
}
}
}
}
然后指定插件信息:
tasks.jar {
manifest {
attributes "Plugin-Author": author
attributes "Plugin-Version": version
attributes "Plugin-Display-Name": project.name
attributes "Plugin-Name": "${project.group}.${project.name.replace(' ', '')}"
}
archiveFileName = "${project.group}.${project.name}-${version}.jar"
}
这一套方案下,可以在同一个项目中开发远程执行的字节码以及插件代码。