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

420 阅读2分钟

背景

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