[053] 微信公众平台开发教程第23篇-SAE不支持XStream框架的解决方案

问题描述

最近几天(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。

如果觉得博客的文章对你有所帮助,请通过留言或关注下方的微信公众账号来支持柳峰!

 

时间: 2024-11-08 19:37:55

[053] 微信公众平台开发教程第23篇-SAE不支持XStream框架的解决方案的相关文章

微信公众平台开发教程第23篇-SAE不支持XStream框架的解决方案

问题分析 如 果Java Web项目中使用了日志工具log4j或者slf4j,并且设置了将日志输出到控制台(console),那么在项目部署到SAE之后,可以在SAE网站 的“日志中心”看到应用的相关日志.查看HTTP服务error级别的日志,能够看到如下图所示的错误日志: 为了方便查看和讲解,我对上述错误日志进行了格式化处理,结果如下: [java] view plaincopy 101.226.62.83 [27/Aug/2014:17:23:10 +0800] JAVA_SAE_Fatal

[051] 微信公众平台开发教程第22篇-如何保证access_token长期有效

为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等,开发者在调用这些接口时,都需要传入一个相同的参数access_token,它是公众账号的全局唯一票据,它是接口访问凭证. access_token的有效期是7200秒(两小时),在有效期内,可以一直使用,只有当access_token过期时,才需要再次调用接口获取access_token.在理想情况下,一个7x24小时运行的系统,每天只需

微信公众平台开发教程第1篇-新手解惑

1.订阅号与服务号的主要区别是什么?订阅号每天能群发一条消息,没有自定义菜单及高级接口权限(目前 个人.企业订阅号关联腾讯微博认证之后才有自定义菜单):服务号有自定义菜单微信认证之后有高级接口权限,但每月只能群发一条消息. 2.到底该申请订阅号还是服务号?申请哪种类型的公众账号,主要取决于账号的用途.服务号主要面向企业和组织,旨在为用户提供服务:订阅号主要面向媒体和个人,旨在为用户提供信息和资讯. 3.订阅号是否支持编程开发?不管是订阅号,还是服务号,在高级功能中都有编辑模式和开发模式,订阅号也

[051] 微信公众平台开发教程第22篇-怎样保证access_token长期有效

为了使第三方开发人员能够为用户提供很多其它更有价值的个性化服务,微信公众平台开放了很多接口,包含自己定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等,开发人员在调用这些接口时.都须要传入一个同样的參数access_token.它是公众账号的全局唯一票据.它是接口訪问凭证. access_token的有效期是7200秒(两小时),在有效期内.能够一直使用.仅仅有当access_token过期时,才须要再次调用接口获取access_token.在理想情况下,一个7x24小时执行的系

柳峰微信公众平台开发教程企业号修改篇(AES验证)

本文针对<微信公众平台应用开发:方法.技巧与案例> 一书中示例和代码不适用于微信企业号的情况进行修改. 修改原因: 企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全 修改方法: 按照微信加密库进行加密验证,具体加密库下载地点请参考开发人员文档,这里不再叙述 注意事项: 异常java.security.InvalidKeyException:illegal Key Size 需要去Oracle官方网站下载JCE无限制权限策略文件,分JDK6 JDK7 JDK8,

微信公众平台开发教程(二) 基本原理及消息接口

微信公众平台开发教程(二) 基本原理及消息接口 一.基本原理 在开始做之前,大家可能对这个很感兴趣,但是又比较茫然.是不是很复杂?很难学啊? 其实恰恰相反,很简单.为了打消大家的顾虑,先简单介绍了微信公众平台的基本原理. 微信服务器就相当于一个转发服务器,终端(手机.Pad等)发起请求至微信服务器,微信服务器,然后将请求转发给自定义服务(这就里就是我们的具体实现). 服务处理完毕,然后挥发给微信服务器,微信服务器再将具体响应回复到终端. 通信协议为:HTTP 数据格式为:XML 具体的流程如下图

微信公众平台开发教程(六)获取个性二维码

微信公众平台开发教程(六)获取个性二维码 一.功能介绍 在进行推广时,我们可以告诉对方,我们的微信公众账号是什么,客户可以去搜索,然后关注.二维码给我们提供了极大的便捷,只要简单一扫描,即可关注. 如果已经关注过,立刻跳入对话画面.在我们进行推广时,不再是简陋的文字,可以是一个有个性的二维码,想必会很生动. 微信对二维码提供了很好的支持,而且还可以根据需要生成不同场景的二维码.下面我们将介绍如何获取和使用二维码. 注意:限服务号,且进行了微信认证,费用300 二.相关接口 为了满足用户渠道推广分

微信公众平台开发教程新手解惑40则

[编者按]由CSDN和<程序员>杂志联合主办的 2014年微信开发者大会 将于8月23日在北京举行,邀请了来自于一线的微信开发商技术负责人或资深工程师从企业应用开发高级篇.智能客服与LBS.微信支付.微信上的HTML5社交应用.微信小店开发等角度为与会者带来实战分享( 议程 ).目前报名处于优惠票价阶段,通过申请加入CSDN CTO俱乐部即可享受8折购票价格(票款中均含午餐),在8月1日前完成付款的同学还将免费获赠微信开发图书一本(两选一,活动现场发放).  值得一提的是,CSDN优秀博主.畅

Senparc.Weixin.MP SDK 微信公众平台开发教程(十六):AccessToken自动管理机制

Senparc.Weixin.MP SDK 微信公众平台开发教程(十六):AccessToken自动管理机制 在<Senparc.Weixin.MP SDK 微信公众平台开发教程(八):通用接口说明>中,我介绍了获取AccessToken(通用接口)的方法. 在实际的开发过程中,所有的高级接口都需要提供AccessToken,因此我们每次在调用高级接口之前,都需要执行一次获取AccessToken的方法,例如: 1 var accessToken = AccessTokenContainer.