背景
在调用模版引擎的时候,有时候Beetl中的自带方法函数不能满足我们的需求;此时,我们就需要加载一些自定义的java静态方法。但是,我们又不想每次都加到工程中,走发布流程。所以想到了从数据库中去加载,然后动态插入到Beetl中
实现步骤
1、从数据库读取类配置
数据库表中,存放className(类全名)和sourceCode(类的原始代码) 然后通过一下代码生成相应二进制
byte[] bytes = ClassFileCompiler.compile("com.qxwz.triton.DynamicClassTest", "package com.qxwz.triton; public class DynamicClassTest {\n" +
"\tpublic static String test() {\n" +
"\t\treturn "test";\n" +
"\t}\n" +
"}");
ClassFileCompiler 代码如下
/**
* @author honglei.wan
* @date 2024/2/8 10:55
* @desc
*/
import lombok.extern.slf4j.Slf4j;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Collections;
@Slf4j
public class ClassFileCompiler {
/**
* 动态加载类
* @param className
* @param sourceCodeInText
* @return
*/
public static byte[] compile(String className, String sourceCodeInText) {
SourceFile sourceFile = new SourceFile(className, sourceCodeInText);
Iterable<? extends JavaFileObject> compilationUnits = Collections.singletonList(sourceFile);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits);
boolean success = task.call();
if (success) {
return fileManager.getByteCode();
} else {
log.error("class load failed ---> {}", className);
return null;
}
}
private static class SourceFile extends SimpleJavaFileObject {
private final String sourceCode;
protected SourceFile(String className, String sourceCode) {
super(URI.create(className.replaceAll("\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.sourceCode = sourceCode;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return sourceCode;
}
}
private static class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private ClassFile classFile;
protected ClassFileManager(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) {
classFile = new ClassFile(className, kind);
return classFile;
}
public byte[] getByteCode() {
return classFile.getByteCode();
}
}
private static class ClassFile extends SimpleJavaFileObject {
private final ByteArrayOutputStream byteCode;
protected ClassFile(String className, Kind kind) {
super(URI.create(className.replaceAll("\.", "/") + kind.extension), kind);
byteCode = new ByteArrayOutputStream();
}
@Override
public OutputStream openOutputStream() {
return byteCode;
}
public byte[] getByteCode() {
return byteCode.toByteArray();
}
}
}
2、自定义类加载器ByteClassLoader
public class ByteClassLoader extends ClassLoader {
public ByteClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
public Class<?> findClassByName(String clazzName) {
try {
return getParent().loadClass(clazzName);
} catch (ClassNotFoundException e) {
// ignore
}
return null;
}
}
也可以直接使用Beetl自带的
3、传入bytes,动态加载class
ByteClassLoader byteClassLoader = new ByteClassLoader(GroupTemplate.class.getClassLoader());
Class<?> dynamicClassTest = byteClassLoader.defineClass("com.qxwz.triton.DynamicClassTest", bytes);
Object o = ReflectionTestUtils.invokeMethod(dynamicClassTest, "test");
System.out.println("打印返回值--->" + o);
4、Beetl配置中注入自定义的ByteClassLoader
String temp = "${@com.qxwz.triton.DynamicClassTest.test()}";
StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
Configuration cfg = Configuration.defaultConfiguration();
GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
gt.setClassLoader(byteClassLoader);
Template t = gt.getTemplate(temp);
String str = t.render();
System.out.println(str);
完整实例代码
import lombok.extern.slf4j.Slf4j;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.misc.ByteClassLoader;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.IOException;
@Slf4j
public class MyTest {
public static void main(String[] args) throws IOException {
byte[] bytes = ClassFileCompiler.compile("com.qxwz.triton.DynamicClassTest", "package com.qxwz.triton; public class DynamicClassTest {\n" +
"\tpublic static String test() {\n" +
"\t\treturn "test";\n" +
"\t}\n" +
"}");
ByteClassLoader byteClassLoader = new ByteClassLoader(GroupTemplate.class.getClassLoader());
Class<?> dynamicClassTest = byteClassLoader.defineClass("com.qxwz.triton.DynamicClassTest", bytes);
Object o = ReflectionTestUtils.invokeMethod(dynamicClassTest, "test");
System.out.println("打印返回值--->" + o);
String temp = "${@com.qxwz.triton.DynamicClassTest.test()}";
StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
Configuration cfg = Configuration.defaultConfiguration();
GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
gt.setClassLoader(byteClassLoader);
Template t = gt.getTemplate(temp);
String str = t.render();
System.out.println(str);
}
}
注意事项
自定义类一定不要放在根目录下面,一定要有package路径,请大家注意。具体原因还在研究中