概要:
- EXE部署版,内嵌 Tomcat 通过
Tomcat.start()
启动,只添加了特定的 SCI,JasperInitializer
没有被初始化,无法解析 JSP。 - 服务器部署版,通过
Bootstrap.start()
启动,会通过 SPI 自动加载所有的 SCI,包括JasperInitializer
,JSP 能够被正常解析。
问题描述
根据七月火师傅的提示,范围缩小到 EXE 部署版
启动 FineReport:
|
|
访问 *.jsp 文件,响应如下:
|
|
根因分析
空指针异常
根据异常堆栈信息,将断点设置在 org.apache.jasper.compiler.Validator.ValidateVisitor
构造方法处,再次访问 JSP,成功触发断点:
|
|
运行时信息显示 JspFactory.getDefaultFactory()
为 null
,并尝试调用其 getJspApplicationContext()
方法,导致空指针异常。
JspFactory.getDefaultFactory() 为 null
JspFactory.getDefaultFactory()
返回静态变量 deflt
:
|
|
此时 deflt
为 null
,说明在启动时没有调用 javax.servlet.jsp.JspFactory#setDefaultFactory
进行赋值,反之另一种部署方式会进行赋值。
为了验证这一点,安装服务器部署版,在 setDefaultFactory
处设置断点,参考以下 JVM 调试配置:
|
|
使用 IDEA 进行远程调试,成功触发 setDefaultFactory
断点,验证了猜想。
通过 SPI 在加载 JasperInitializer
时触发静态代码中调用 setDefaultFactory
给静态变量 deflt
赋值。
|
|
完整调用栈:
|
|
EXE 部署版的启动流程
由 FineReport/bin/designer.bat
可知运行的主类为 com.fr.start.Designer
,在 com.fr.start.Designer#main
处设置断点,并设置客户端连接时才开始调试。
触发断点:
后续会从 designer-startup.xml 中读取配置进行处理:
|
|
内嵌 Tomcat,通过 Tomcat#start()
启动:
- com.fr.start.server.FineEmbedServerActivator#start
|
|
而服务器部署版是通过 Bootstrap.start()
启动的。
在内嵌 Tomcat 的初始化过程中,只添加了对指定的 ServletContainerInitializer
(SCI) 的支持:
- com.fr.start.server.FineEmbedServerActivator#initTomcat
|
|
而在 Bootstrap.start()
中,会通过 SPI 的方式自动添加所有的 SCI,包括 JasperInitializer
:
- org.apache.catalina.startup.ContextConfig#processServletContainerInitializers
解决方案
刚好去年有挖过另一个任意文件写的利用方式,把 class 写到 /classes/ 下后进行类加载
相应代码
- com.fr.web.controller.common.FileRequestService#getFile
|
|
构造请求如下,成功解决该问题
|
|