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 = None, template_base_path: str = "./"): """单例模式:全局唯一Engine实例""" if cls._instance is None: cls._instance = super().__new__(cls) # 如果未提供jar_path,使用项目内置的enjoy-5.2.2.jar if jar_path is None: # 获取当前文件所在目录,然后拼接jar文件名 current_dir = os.path.dirname(os.path.abspath(__file__)) cls._instance.jar_path = os.path.join(current_dir, "enjoy-5.2.2.jar") else: 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: # 使用JPype1的自动类路径管理,让JPype1自己处理其支持库 jpype.startJVM( "-ea", f"-Djava.class.path={self.jar_path}", "-Dfile.encoding=UTF-8", 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文件自动使用内置版本) TEMPLATE_BASE_PATH = "./templates" # 模板文件所在目录(如index.html放在这个目录下) # 2. 创建Enjoy实例(初始化Engine,自动使用内置的enjoy-5.2.2.jar) try: enjoy = Enjoy( 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()