模版引擎Beetl,如何加载自定义CLASS并调用其中方法

背景

在调用模版引擎的时候,有时候Beetl中的自带方法函数不能满足我们的需求;此时,我们就需要加载一些自定义的java静态方法。但是,我们又不想每次都加到工程中,走发布流程。所以想到了从数据库中去加载,然后动态插入到Beetl中

实现步骤

1、从数据库读取类配置

数据库表中,存放className(类全名)和sourceCode(类的原始代码) 然后通过一下代码生成相应二进制

swift 复制代码
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 代码如下

java 复制代码
/**
 * @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

scala 复制代码
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

ini 复制代码
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

ini 复制代码
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);

完整实例代码

ini 复制代码
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路径,请大家注意。具体原因还在研究中

相关推荐
牛奔5 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌10 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX11 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法12 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
Cobyte13 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
程序员侠客行14 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple14 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东15 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable