问题描述
最近几天(2014年8月20日之后),突然有不少网友反应,柳峰博客中的微信公众平台开发代码在SAE上运行会报错,或者是能正常部署,但向公众号发消息没反应。以前也有一些初学者质疑过我博客中的代码是否能正常运行,最后都被我一一证明是由于他们的不理解和粗心导致,但这一次短短几天就有很多人反应同样的问题,这就引起了我的足够重视。对于这种“同样的代码以前可以正常运行,现在却不能运行”的问题,我猜测可能是程序运行环境发生了某种变化,应该是SAE近期做了什么更新导致的。
问题分析
如果Java Web项目中使用了日志工具log4j或者slf4j,并且设置了将日志输出到控制台(console),那么在项目部署到SAE之后,可以在SAE网站的“日志中心”看到应用的相关日志。查看HTTP服务error级别的日志,能够看到如下图所示的错误日志:
为了方便查看和讲解,我对上述错误日志进行了格式化处理,结果如下:
- 101.226.62.83 [27/Aug/2014:17:23:10 +0800] JAVA_SAE_Fatal_error:
- Error for /coreServletjava.lang.NoClassDefFoundError: Could not initialize class org.liufeng.weixin.util.MessageUtil
- at org.liufeng.gywodejia.service.CoreService.processRequest(CoreService.java:40)
- at org.liufeng.gywodejia.servlet.CoreServlet.doPost(CoreServlet.java:54)
- at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
- at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
- at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:538)
- at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:478)
- at com.sina.sae.servlet.SaeServletHandler.doHandle(SaeServletHandler.java:49)
- at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)
- at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:517)
- at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:225)
- at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:937)
- at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:406)
- at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:183)
- at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:871)
- at com.sina.sae.webapp.SaeWebAppContext.doScope(SaeWebAppContext.java:166)
- at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
- at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:259)
- at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:149)
- at com.sina.sae.handler.SaeUserInfoHandler.handle(SaeUserInfoHandler.java:105)
- at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)
- at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:305)
- at org.eclipse.jetty.server.handler.HandlerW yq36.javaruntime
从日志中的第二行可以看出,在访问/coreServlet时报了一个错误NoClassDefFoundError(类找不到),并且提示org.liufeng.weixin.util.MessageUtil类不能被实例化。在部署的WAR中,MessageUtil.class明明存在,为什么会找不到类呢?我们来看看,MessageUtil.java中到底都写了些什么,源代码如下:
- package org.liufeng.course.util;
- import java.io.InputStream;
- import java.io.Writer;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.dom4j.Document;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- import org.liufeng.course.message.resp.Article;
- import org.liufeng.course.message.resp.MusicMessage;
- import org.liufeng.course.message.resp.NewsMessage;
- import org.liufeng.course.message.resp.TextMessage;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.core.util.QuickWriter;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
- import com.thoughtworks.xstream.io.xml.XppDriver;
- /**
- * 消息工具类
- *
- * @author liufeng
- * @date 2013-05-19
- */
- public class MessageUtil {
- /**
- * 返回消息类型:文本
- */
- public static final String RESP_MESSAGE_TYPE_TEXT = "text";
- /**
- * 返回消息类型:音乐
- */
- public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
- /**
- * 返回消息类型:图文
- */
- public static final String RESP_MESSAGE_TYPE_NEWS = "news";
- /**
- * 请求消息类型:文本
- */
- public static final String REQ_MESSAGE_TYPE_TEXT = "text";
- /**
- * 请求消息类型:图片
- */
- public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
- /**
- * 请求消息类型:链接
- */
- public static final String REQ_MESSAGE_TYPE_LINK = "link";
- /**
- * 请求消息类型:地理位置
- */
- public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
- /**
- * 请求消息类型:音频
- */
- public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
- /**
- * 请求消息类型:推送
- */
- public static final String REQ_MESSAGE_TYPE_EVENT = "event";
- /**
- * 事件类型:subscribe(订阅)
- */
- public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
- /**
- * 事件类型:unsubscribe(取消订阅)
- */
- public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
- /**
- * 事件类型:CLICK(自定义菜单点击事件)
- */
- public static final String EVENT_TYPE_CLICK = "CLICK";
- /**
- * 解析微信发来的请求(XML)
- *
- * @param request
- * @return
- * @throws Exception
- */
- @SuppressWarnings("unchecked")
- public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
- // 将解析结果存储在HashMap中
- Map<String, String> map = new HashMap<String, String>();
- // 从request中取得输入流
- InputStream inputStream = request.getInputStream();
- // 读取输入流
- SAXReader reader = new SAXReader();
- Document document = reader.read(inputStream);
- // 得到xml根元素
- Element root = document.getRootElement();
- // 得到根元素的所有子节点
- List<Element> elementList = root.elements();
- // 遍历所有子节点
- for (Element e : elementList)
- map.put(e.getName(), e.getText());
- // 释放资源
- inputStream.close();
- inputStream = null;
- return map;
- }
- /**
- * 文本消息对象转换成xml
- *
- * @param textMessage 文本消息对象
- * @return xml
- */
- public static String textMessageToXml(TextMessage textMessage) {
- xstream.alias("xml", textMessage.getClass());
- return xstream.toXML(textMessage);
- }
- /**
- * 音乐消息对象转换成xml
- *
- * @param musicMessage 音乐消息对象
- * @return xml
- */
- public static String musicMessageToXml(MusicMessage musicMessage) {
- xstream.alias("xml", musicMessage.getClass());
- return xstream.toXML(musicMessage);
- }
- /**
- * 图文消息对象转换成xml
- *
- * @param newsMessage 图文消息对象
- * @return xml
- */
- public static String newsMessageToXml(NewsMessage newsMessage) {
- xstream.alias("xml", newsMessage.getClass());
- xstream.alias("item", new Article().getClass());
- return xstream.toXML(newsMessage);
- }
- /**
- * 扩展xstream,使其支持CDATA块
- *
- * @date 2013-05-19
- */
- private static XStream xstream = new XStream(new XppDriver() {
- public HierarchicalStreamWriter createWriter(Writer out) {
- return new PrettyPrintWriter(out) {
- // 对所有xml节点的转换都增加CDATA标记
- boolean cdata = true;
- @SuppressWarnings("unchecked")
- public void startNode(String name, Class clazz) {
- super.startNode(name, clazz);
- }
- protected void writeText(QuickWriter writer, String text) {
- if (cdata) {
- writer.write("<![CDATA[");
- writer.write(text);
- writer.write("]]>");
- } else {
- writer.write(text);
- }
- }
- };
- }
- });
- }
MessageUtil是消息处理工具类,该类的代码大致可以分为以下3部分:
1)第33~91行:定义了若干常量,用于表示请求消息类型、事件类型和响应消息类型。
2)第93-124行:定义了一个parseXml()方法,通过dom4j工具解析微信服务器发来的xml格式的消息。
3)第126~187行:通过XStream工具将Java消息对象转换成xml。
很明显,问题应该不会出现在第1部分代码中,因为这段代码太平常不过了。我猜想,问题可能与第2、3部分代码中引用的第三方工具dom4j或XStream有关,会不会是SAE做了什么更新不支持dom4j或XStream了呢?要想证明也不难,写一个最简单的Java web工程,其中只用到dom4j或者只用到XStream工具,就能知道是哪里出了问题。好在我认识一个SAE官方的运营人员,就偷了个懒,直接咨询他,他帮忙问过SAE研发人员之后给出的答复是:XStream源码中通过反射机制使用到了sun.misc.Unsafe类,而该类因为安全原因被SAE禁用掉了,这就是为什么用到XStream的项目部署到SAE会报NoClassDefFoundError的原因。噢,原来是这么回事,知道原因了就总能找到解决方案。
问题解决
XStream框架的作用是实现Java对象与XML的互相转换,SAE研发人员建议用其他有类似功能的框架替代,如Xerces、jdom或者dom4j,当然,这是一个很不错的建议,如果是在新的项目中,我肯定会这样做。但现在的问题是,如果真的用其他框架来替换XStream,可能要修改的不仅仅是MessageUtil一个类,这样的改动太大了,我也很难向这么多读者交待。正是出于这种考虑,让我想到了有没有可能通过修改XStream框架的源码来解决问题。
我在XStream官方网站http://xstream.codehaus.org/上找到了xstream-1.3.1.jar对应的源码,导入到Eclipse,然后借助Eclipse强大的搜索功能,很快找到了使用sun.misc.Unsafe的类,我尝试将这些类删除或者修改它们的实现,避免使用sun.misc.Unsafe类,最终得到了一个新的jar包,我将其命名为xstream-1.3.1-sae-liufeng.jar,用它替换以前项目中使用的xstream-1.3.1.jar,最终项目再次顺利地运行在SAE上。
可能很多看到标题进来的读者,就是想知道这个问题是如何解决的,并不想听我哆嗦半天。授人鱼不如授人以渔,我之所以将问题的发现、分析和解决整个过程写出来,也是希望能够帮助更多初学者逐渐掌握自行解决问题的方法。
xstream-1.3.1-sae-liufeng.jar的下载地址:http://download.csdn.net/download/lyq8479/7830911。
声明
我提供的解决方案有些暴力,旨在帮助大家能够在SAE上继续测试学习微信公众平台开发,可能会影响到XStream的性能。如果是作为正式项目使用,在非SAE平台上运行公众平台程序,还是建议用XStream官方原本的jar。
相关推荐
修改XStream源码,解决SAE不支持XStream的问题。
powermock-classloading-xstream-1.4.7powermock-classloading-xstream-1.4.7powermock-classloading-xstream-1.4.7powermock-classloading-xstream-1.4.7powermock-classloading-xstream-1.4.7powermock-class...
以下是针对“主要用于Java版微信公众账号开发后台所用JAR包”的介绍,经过重新...这些JAR包为Java开发者在搭建微信公众账号开发后台时提供了必要的支持和工具,帮助开发者高效、安全地与微信服务器进行交互和数据处理。
第二部分(第3~4章)首先讲解了如何启用开发模式,然后详细讲解了公众平台的消息接口(包括请求校验、请求消息、事件推送和响应消息)。 第三部分(第5~6章)重点介绍了公众平台的自定义菜单接口和高级接口,并...
本人通过柳锋老师的教程,成功开发自己公司的微信公众平台,其中会涉及很多jar,共12个,现在和大家一起分享: commons-beanutils-1.7.0.jar commons-collections-3.2.1.jar commons-lang-2.3.jar commons-logging-...
XStream 反序列化漏洞(CVE-2020-26258 & 26259),修复jar包 xstream-1.4.15.jar Xstream 是 Java 类库,用来将对象序列化成 XML (JSON) 或反序列化为对象。XStream 是一款开源软件,允许在 BSD 许可证的许可下分发...
在微信开发过程中,经常会涉及到与服务器的交互,这时就需要用到JSON解析库、XML处理库等工具。以下是对标题和描述中提及的几个关键知识点的详细介绍: 1. JSON解析: JSON (JavaScript Object Notation) 是一种轻...
包含柳峰老师微信自定义菜单所需的所有jar包,commons-beanutils.jar;commons-beanutils-1.7.0.jar;commons-collections.jar;commons-httpclient.jar;commons-lang-2.3.jar;...xstream-1.3.1-sae-liufeng.jar
java微信公众平台开发,基于springMVC技术和xstream,json等技术,通过使用图灵机器人接口,实现微信公众号的智能回复
xstream xstream-1.4.5.jar
xstream最新版jar包
包含java微信开发中大部分jar: commons-codec.jar xstream-1.3.1.jar httpcore-4.2.4.jar httpclient-4.2.5.jar dom4j-1.6.1.jar json-lib-2.3-jdk15.jar ezmorph-1.0.jar commons-logging-1.0.4.jar commons-lang-...
xstream-1.4.20.jar
xstream-1.4.19.jar
Java微信公众平台开发,基于springMVC技术和xstream,json等技术。
最底一分,xstream-1.4.8的jar包、源码、说明文档.zip
XStream 用法详解 XML 转换为 java 实体类 所需要的 xstream-1.4.3-sources.jar jar包 具体实现请参考 http://blog.csdn.net/morning99/article/details/28875159
XStream拒绝服务漏洞(CVE-2022-41966)响应通告
《XStream详解与应用》 ...总之,XStream是一个强大且灵活的工具,对于处理Java对象与XML之间的转换,它提供了简洁、高效的解决方案。无论你是新手还是经验丰富的开发者,都能从XStream的易用性和强大功能中受益。