java使用handlebars模板,中文变量导致编译失败的替代处理方案

/ 默认分类 / 没有评论 / 452浏览

近期需要集成多个模板引擎,其中就包括handlebars,笔者根据热度,选择了开源的https://github.com/jknack/handlebars.java 作为依赖,但无奈却无法编译如下代码

Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hello {{测试}}!");

大致翻看了git上的issue,以及部分源码,发现确实有此问题,似乎该库也以及久未维护。时间有限,也不能详细调试源码,所以就打算,使用java调用js引擎(js可编译中文)来完成。直接上代码

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author xiatiao
 * @description handlebars js模板实现
 * @date 2022/9/5
 */
@Component
public class HandlebarsTemplateJSConvert implements TemplateConvert {

    //脚本引擎
    private static ScriptEngine scriptEngine;

    //js引擎是否加载
    private volatile boolean engineLoaded = false;

    //对象锁
    Object lock = new Object();

    @Resource
    ThreadPoolExecutor threadPoolExecutor;

    @PostConstruct
    public void init() {
        //忽略告警
        System.setProperty("nashorn.args", "--no-deprecation-warning");
        //不阻塞主线程-加载脚本
        Thread thread = new Thread(() -> {
            try {
                //js引擎
                ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
                scriptEngine = scriptEngineManager.getEngineByName("javascript");
                InputStream inputStream = HandlebarsTemplateJSConvert.class.getResourceAsStream("/handlebars.js");
                String jsContent = IoUtil.read(inputStream, StandardCharsets.UTF_8);
                InputStream helperInputStream = HandlebarsTemplateJSConvert.class.getResourceAsStream("/helpers-handlebars.js");
                String helperContent = IoUtil.read(helperInputStream, StandardCharsets.UTF_8);
                scriptEngine.eval(jsContent);
                scriptEngine.eval(helperContent);
                engineLoaded = true;
                synchronized (lock) {
                    //激活等待线程
                    lock.notifyAll();
                }
            } catch (ScriptException e) {
                throw new RuntimeException("加载handlebars脚本引擎异常", e);
            }
        });
        threadPoolExecutor.execute(thread);
    }

    @Override
    public void convert(String templateDir, String templateFileName, String targetDir, String outFileName, Map param) throws Throwable {
        String templateStr = FileUtil.readUtf8String(templateDir + File.separator + templateFileName);
        String resultString = jsConvert(templateStr, param);
        FileUtil.writeString(resultString, targetDir + File.separator + outFileName, StandardCharsets.UTF_8);
    }

    private String jsConvert(String template, Map param) throws Throwable {
        //引擎加载结束以前线程等待
        if (!engineLoaded) {
            try {
                synchronized (lock) {
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String jsonParam = JSON.toJSONString(param);
        // 执行函数名
        String functionName = "loadMe";
        Invocable invocable = (Invocable) scriptEngine;
        //Object eval = scriptEngine.eval("loadMe("{{a}}",{a:1})");
        Object result = invocable.invokeFunction(functionName, template, jsonParam);
        return result.toString();
    }

}
/**
 * 模板接口
 * @author xiatiao
 * @date 2022/9/5
 */
public interface TemplateConvert {

    /**
     * @description
     * @param templateDir 模板目录
     * @param templateFileName 模板文件名称
     * @param targetDir 输出目录
     * @param outFileName 输出文件名称
     * @param param 参数
     * @author xiatiao
     * @date 2022/9/5
     */
    void convert(String templateDir, String templateFileName,String targetDir ,String outFileName, Map param) throws Throwable;

}

笔者这里要实现多种模板,所以抽象了一个接口,规范接口的调用方式。

文中使用的handlebars.js, 下载地址:https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js

helpers-handlebars.js文件内容如下:

function loadMe(templateStr,jsonStr){
    var jsonObject = JSON.parse(jsonStr);
    var template = Handlebars.compile(templateStr);
    var result = template(jsonObject);
    return result;
}

文件都保存到resources目录下即可。