import jpype import os from typing import Dict, Any, Optional class Enjoy: """ 适配JFinal Enjoy 5.2.2模板引擎的Python调用类 核心类:com.jfinal.template.Engine 实现:mrzhou@miw.cn """ _instance: Optional['Enjoy'] = None _jvm_started: bool = False _engine: Optional[Any] = None # 保存JFinal Engine实例 def __new__(cls, jar_path: str, template_base_path: str = "./"): """单例模式:全局唯一Engine实例""" if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.jar_path = os.path.abspath(jar_path) cls._instance.template_base_path = os.path.abspath(template_base_path) cls._instance._init_jvm() cls._instance._init_engine() # 初始化JFinal Engine return cls._instance def _init_jvm(self): """启动JVM并加载enjoy-5.2.2.jar""" if not self._jvm_started: try: jvm_path = jpype.getDefaultJVMPath() # 修复:所有位置参数在前,关键字参数在后 jpype.startJVM( jvm_path, # 位置参数1 "-ea", # 位置参数2 f"-Djava.class.path={self.jar_path}", # 位置参数3 "-Dfile.encoding=UTF-8", # 位置参数4(移到关键字参数前) convertStrings=False # 关键字参数(必须在最后) ) self._jvm_started = True print(f"JVM启动成功 | 工作目录: {jpype.java.lang.System.getProperty('user.dir')}") except Exception as e: raise RuntimeError(f"JVM启动失败: {e}") from e def _init_engine(self): """初始化JFinal Engine实例(核心)""" try: # 加载JFinal Engine类 Engine = jpype.JClass("com.jfinal.template.Engine") # 获取默认Engine实例(JFinal推荐单例使用) self._engine = Engine.use() # 配置模板基础路径(关键:指定模板文件所在目录) self._engine.setBaseTemplatePath(self.template_base_path) # 可选配置:设置编码、开启热加载等 self._engine.setEncoding("UTF-8") self._engine.setDevMode(True) # 开发模式,便于调试 print(f"Engine初始化成功 | 模板基础路径: {self.template_base_path}") except jpype.JException as e: e.printStackTrace() raise RuntimeError(f"Engine初始化失败: {e.getMessage()}") from e def _convert_python_to_java(self, obj: Any) -> Any: """转换Python数据为Java Map(适配Enjoy模板数据格式)""" if isinstance(obj, dict): # 优先使用JFinal的Map(也可用java.util.HashMap) HashMap = jpype.JClass("java.util.HashMap") java_map = HashMap() for key, value in obj.items(): # 递归转换嵌套数据 if isinstance(value, (dict, list)): java_map.put(key, self._convert_python_to_java(value)) else: java_map.put(key, value) return java_map elif isinstance(obj, list): return jpype.JArray(jpype.JObject)([self._convert_python_to_java(x) for x in obj]) else: return obj def render(self, tempStr: str, data: Dict[str, Any]) -> str: """ 渲染Enjoy模板(核心方法) :param tempStr: 模板文件名(相对于baseTemplatePath的路径,如index.html) :param data: 模板渲染数据(Python字典) :return: 渲染后的字符串 """ if not self._engine: raise RuntimeError("Engine未初始化,请检查Jar包路径") try: # 1. 转换Python字典为Java Map java_data = self._convert_python_to_java(data) # 2. 获取Template实例(Engine.getTemplate) template = self._engine.getTemplate(tempStr) # 3. 渲染模板:使用Writer接收结果 StringWriter = jpype.JClass("java.io.StringWriter") writer = StringWriter() template.render(java_data, writer) # 4. 转换为Python字符串 result = writer.toString() writer.close() return result except jpype.JException as e: print("\n=== JFinal Enjoy异常详情 ===") e.printStackTrace() raise RuntimeError(f"模板渲染失败: {e.getMessage()}") from e except Exception as e: raise RuntimeError(f"调用失败: {e}") from e def close(self): """关闭JVM,释放资源""" if self._jvm_started: jpype.shutdownJVM() self._jvm_started = False self._engine = None self.__class__._instance = None print("JVM已关闭") def __del__(self): """析构函数:自动关闭""" self.close() # ------------------------------ # 标准调用示例(适配enjoy-5.2.2.jar) # ------------------------------ if __name__ == "__main__": # 1. 配置参数(必须修改为你的实际路径) JAR_PATH = "enjoy-5.2.2.jar" # enjoy-5.2.2.jar的绝对/相对路径 TEMPLATE_BASE_PATH = "./templates" # 模板文件所在目录(如index.html放在这个目录下) # 2. 创建Enjoy实例(初始化Engine) try: enjoy = Enjoy( jar_path=JAR_PATH, template_base_path=TEMPLATE_BASE_PATH ) # 3. 准备模板参数 render_data = { "name": "JFinal Enjoy 5.2.2测试", "hobbies": ["Python调用Java", "模板渲染", "跨语言开发"], "user": {"age": 25, "gender": "男"} } # 4. 渲染模板(tempStr是模板文件名,相对于TEMPLATE_BASE_PATH) result = enjoy.render( tempStr="index.html", # 模板文件:./templates/index.html data=render_data # 渲染数据(Python字典) ) # 5. 输出结果 print("\n=== 模板渲染结果 ===") print(result) except RuntimeError as e: print(f"\n执行失败: {e}") finally: # 6. 关闭资源 if 'enjoy' in locals(): enjoy.close()