【问题解决】金蝶Apusic9.0部署springboot报错java.lang.NoSuchMethodError: HttpServletResponse.setContentLengthLong
问题背景
项目有需要使用AAS9.0(即,金蝶天燕)web中间件部署springboot程序,服务正常启动后,只要访问应用就出现报错提示java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.setContentLengthLong(J)V,导致大部分请求都有问题

详细报错如下:
25-05-20 11:15:00.770 [HTTPHandler-6-50|MAVXY1OF] ERROR - Forwarding to error page from request [/output/@xx/login/dist/app.bundle.js] due to exception [javax.servlet.http.HttpServletResponse.setContentLengthLong(J)V]
java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.setContentLengthLong(J)V
at org.springframework.http.server.ServletServerHttpResponse.writeHeaders(ServletServerHttpResponse.java:130)
at org.springframework.http.server.ServletServerHttpResponse.getBody(ServletServerHttpResponse.java:96)
at org.springframework.http.converter.ResourceHttpMessageConverter.writeContent(ResourceHttpMessageConverter.java:143)
at org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:114)
at org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:45)
at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:227)
查看方法栈可以看到是spring-web包中的ServletServerHttpResponse.writeHeaders()调用了javax.servlet.http.HttpServletResponse.setContentLengthLong()最终导致的报错。

排查解决
API不兼容
借助arthas来查出为啥会报错,servletResponse变量对应的是javax.servlet.http.HttpServletResponse接口,直接反编译看下方法是不是没定义
jad javax.servlet.http.HttpServletResponse
发现的确没定义,不过不急,该接口继承了javax.servlet.ServletResponse,再往上查一层
jad javax.servlet.ServletResponse
发现的确没有setContentLengthLong(),这说明大概率是API不兼容。用问小白查了下发现是Servlet 3.1之后就添加了这个方法。
优先加载高版本servlet-api包
既然中间件中的javax.servlet.ServletResponse版本低,根据JVM类加载机制,可以调整classpath让JVM优先加载 javax.servlet-api新版本包。
怎么调呢?查了AAS部署位置docs目录中的文档,可以将javax.servlet-api放到AAS部署目录下的lib/endorsed中。
错误变成未实现
重启AAS发现日志报错变了,有戏!
2025-05-20 15:57:40.852 [HTTPHandler-8-56|MAW81K5E] ERROR - Forwarding to error page from request [/anon/serverframework/index.html] due to exception [Not implemented.]
java.lang.UnsupportedOperationException: Not implemented.
at com.apusic.web.container.Response.setContentLengthLong(Response.java:978)
at javax.servlet.ServletResponseWrapper.setContentLengthLong(ServletResponseWrapper.java:135)
可见com.apusic.web.container.Response#setContentLengthLong作了一个调用就报错的实现。通过反编译jad com.apusic.web.container.Response 也能看得出。

实现方法内存替换
再启动arthas开始反编译
#反编译生成源码
jad --source-only com.apusic.web.container.Response > /tmp/Response.java
从服务器上下载源码本地调整后,重新上传替换原文件,以下是改动点:
public void setContentLengthLong(long len) {
if (this.canChangeHeaders()) {
if (len <= Integer.MAX_VALUE) {
this.http.setResponseLength((int)len);
} else {
this.http.responseHeaders.setHeader("Transfer-Encoding", "chunked");
}
}
}
编译替换(编译目录中出现了内部类,这次改动不涉及内部类,不用管)
mc /tmp/Response.java -d /tmp
retransform /tmp/com/apusic/web/container/Response.class


试验了下,再访问服务就能打开界面了,有用。
定位替换的类所在jar包
上边的试验方法是临时验证代码是否能解决问题,应该把这个步骤标准化一下,这样后续客户现场再出现这种问题,就可以不需要技术背景也可以更快解决。
我想到的是把com/apusic/web/container/Response.class替换到有问题的jar包中,这样就只需要替换一个jar包,再把javax.servlet-api包放到一个目录里,重启服务就好了。
中间件不更新就不会有问题。
sc -d com.apusic.web.container.Response

替换jar内容
复用之前编译的class
cd /tmp
jar -uvf /data/jq/AAS-V9_0/lib/apusic.jar com/apusic/web/container/Response.class

最终方案
将javax.servlet-api-4.0.1.jar和apusic.jar压缩到一起,有需要就上传到服务器解压复制到相应的位置就可以了。以下示意:
unzip fix-apusic-servlet-no-setContentLengthLong.zip -d /tmp
cd /tmp
cp javax.servlet-api-4.0.1.jar /data/jq/AAS-V9_0/lib/endorsed/
cp apusic.jar /data/jq/AAS-V9_0/lib/apusic.jar

浙公网安备 33010602011771号