Liferay 7登录片段部署后首次访问EL依赖异常求助
我之前也碰到过类似的情况,给你梳理下问题原因和可行的解决办法:
问题场景还原
你给login.jsp做的登录片段原本运行正常,后来为了从自定义表取数据,在scriptlet里加了XYZLocalServiceUtil的调用。服务本身调用没问题,但部署片段钩子后第一次访问页面就会报EL相关的错误,直接导致Portlet不可用,刷新页面后又能恢复正常。报错核心信息是:
07:17:00,237 ERROR [http-nio-9080-exec-9][PortletRequestDispatcherImpl:265] Unable to dispatch request: javax.el.ELException: Provider com.sun.el.ExpressionFactoryImpl not found
07:17:00,242 ERROR [http-nio-9080-exec-9][PortletServlet:111] javax.portlet.PortletException: org.apache....
问题根源
这种"首次访问报错、刷新就好"的情况,大概率是类加载器初始化顺序冲突导致的。Liferay的片段钩子在首次加载时,scriptlet里的ServiceUtil调用提前触发了某些类加载操作,打乱了EL表达式工厂的正常初始化流程,导致com.sun.el.ExpressionFactoryImpl没被正确加载。
可行解决方案
1. 避免在JSP scriptlet里直接调用LocalServiceUtil(最推荐)
Liferay本身不建议在JSP里直接写scriptlet代码,更规范的做法是通过MVC机制预处理数据:
- 自定义一个MVCRenderCommand,在里面调用
XYZLocalServiceUtil获取数据,把数据放到request属性里 - 然后在login.jsp里通过EL表达式直接读取request里的数据,比如
${customData}
这样不仅能避开类加载顺序的问题,代码的可维护性也会更高。
2. 手动提前初始化EL工厂类
如果实在要保留scriptlet里的ServiceUtil调用,可以在调用之前手动触发EL工厂类的加载,在scriptlet最开头加上这段代码:
try { // 强制加载EL工厂类,避免首次访问时找不到 Class.forName("com.sun.el.ExpressionFactoryImpl"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 后面再写你的XYZLocalServiceUtil调用代码
这样能确保EL工厂类在ServiceUtil调用之前被加载,解决首次访问的依赖缺失问题。
3. 检查片段钩子的类加载配置
打开你的片段钩子的liferay-hook.xml文件,看看有没有设置<class-loader>属性。如果设置了<class-loader>portal</class-loader>,可能会导致应用类和门户类的加载冲突,改成默认配置(去掉这个属性)或者设置成<class-loader>application</class-loader>试试。
调试小技巧
如果还是不确定问题点,可以在Liferay的日志配置里开启类加载相关的debug日志,看看首次访问时com.sun.el.ExpressionFactoryImpl的加载路径,对比刷新后的加载情况,就能更精准定位冲突的原因。
内容的提问来源于stack exchange,提问作者pratik

