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

相关推荐
yangminlei5 小时前
Spring Boot3集成LiteFlow!轻松实现业务流程编排
java·spring boot·后端
计算机毕设VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue医院设备管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
J_liaty5 小时前
Spring Boot整合Nacos:从入门到精通
java·spring boot·后端·nacos
面汤放盐6 小时前
后端系统设计文档模板
后端
2***d8857 小时前
SpringBoot 集成 Activiti 7 工作流引擎
java·spring boot·后端
五阿哥永琪7 小时前
Spring中的定时任务怎么用?
java·后端·spring
追逐时光者7 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 65 期(2026年1.1-1.11)
后端·.net
计算机毕设VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
gelald7 小时前
AQS 工具之 CountDownLatch 与 CyclicBarry 学习笔记
java·后端·源码阅读
且去填词7 小时前
Go 语言的“反叛”——为什么少即是多?
开发语言·后端·面试·go