模版引擎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路径,请大家注意。具体原因还在研究中

相关推荐
一灯架构7 小时前
90%的人答错!一文带你彻底搞懂ArrayList
java·后端
mldong9 小时前
Python开发者狂喜!200+课时FastAPI全栈实战合集,10大模块持续更新中🔥
后端
GreenTea9 小时前
从 Claw-Code 看 AI 驱动的大型项目开发:2 人 + 10 个自治 Agent 如何产出 48K 行 Rust 代码
前端·人工智能·后端
Moment11 小时前
AI 全栈指南:NestJs 中的 Service Provider 和 Module
前端·后端·面试
IT_陈寒11 小时前
为什么我的JavaScript异步回调总是乱序执行?
前端·人工智能·后端
Moment11 小时前
AI全栈入门指南:NestJs 中的 DTO 和数据校验
前端·后端·面试
小村儿12 小时前
Harness Engineering:为什么你用 AI 越用越累?
前端·后端·ai编程
小码哥_常12 小时前
为什么PUT和DELETE请求在大公司中逐渐被弃用?
后端
宫_商_角_徵12 小时前
动态代理到底在做什么?
后端
苍何12 小时前
我把微信 cli 开源了,群消息终于不用爬楼了!
后端