Files
py-enjoy/enjoy/__init__.py

197 lines
8.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 addSharedFunction(self, layouts: [str]):
"""添加全局共享函数"""
for layout in layouts:
self._engine.addSharedFunction(layout)
def invoke_engine_method(self, method_name: str, *args, **kwargs) -> Any:
"""
通用方法调用内部_engine的其他可调用方法
:param method_name: 要调用的_engine方法名
:param args: 位置参数
:param kwargs: 关键字参数
:return: 方法调用结果
:raises RuntimeError: 如果Engine未初始化或方法调用失败
"""
if not self._engine:
raise RuntimeError("Engine未初始化请检查Jar包路径")
try:
# 获取_engine的方法
method = getattr(self._engine, method_name)
# 转换Python参数为Java兼容类型
converted_args = [self._convert_python_to_java(arg) for arg in args]
converted_kwargs = {k: self._convert_python_to_java(v) for k, v in kwargs.items()}
# 调用方法并返回结果
return method(*converted_args, **converted_kwargs)
except AttributeError:
raise RuntimeError(f"Engine对象没有方法: {method_name}") from None
except jpype.JException as e:
print(f"\n=== JFinal Enjoy {method_name} 方法调用异常详情 ===")
e.printStackTrace()
raise RuntimeError(f"{method_name}方法调用失败: {e.getMessage()}") from e
except Exception as e:
raise RuntimeError(f"{method_name}方法调用失败: {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
)
enjoy.addSharedFunction(['layout.html'])
# 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="layout-test.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()