设为首页 收藏本站
查看: 833|回复: 0

[经验分享] 微信公众平台开发中-SAE不支持XStream框架的解决方案

[复制链接]

尚未签到

发表于 2017-3-2 07:56:50 | 显示全部楼层 |阅读模式
  问题描述
  最近几天(2014年8月20日之后),突然有不少网友反应,柳峰博客中的微信公众平台开发代码在SAE上运行会报错,或者是能正常部署,但向公众号发消息没反应。以前也有一些初学者质疑过我博客中的代码是否能正常运行,最后都被我一一证明是由于他们的不理解和粗心导致,但这一次短短几天就有很多人反应同样的问题,这就引起了我的足够重视。对于这种“同样的代码以前可以正常运行,现在却不能运行”的问题,我猜测可能是程序运行环境发生了某种变化,应该是SAE近期做了什么更新导致的。
  问题分析
  如果Java Web项目中使用了日志工具log4j或者slf4j,并且设置了将日志输出到控制台(console),那么在项目部署到SAE之后,可以在SAE网站的“日志中心”看到应用的相关日志。查看HTTP服务error级别的日志,能够看到如下图所示的错误日志:
DSC0000.jpg

  为了方便查看和讲解,我对上述错误日志进行了格式化处理,结果如下:






[java] view plain copy



  • 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中到底都写了些什么,源代码如下:









  1 [java] view plain copy
  2  
  3 package org.liufeng.course.util;  
  4   
  5 import java.io.InputStream;  
  6 import java.io.Writer;  
  7 import java.util.HashMap;  
  8 import java.util.List;  
  9 import java.util.Map;  
10   
11 import javax.servlet.http.HttpServletRequest;  
12   
13 import org.dom4j.Document;  
14 import org.dom4j.Element;  
15 import org.dom4j.io.SAXReader;  
16 import org.liufeng.course.message.resp.Article;  
17 import org.liufeng.course.message.resp.MusicMessage;  
18 import org.liufeng.course.message.resp.NewsMessage;  
19 import org.liufeng.course.message.resp.TextMessage;  
20   
21 import com.thoughtworks.xstream.XStream;  
22 import com.thoughtworks.xstream.core.util.QuickWriter;  
23 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;  
24 import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;  
25 import com.thoughtworks.xstream.io.xml.XppDriver;  
26   
27 /**
28  * 消息工具类
29  *  
30  * @author liufeng
31  * @date 2013-05-19
32  */  
33 public class MessageUtil {  
34   
35     /**
36      * 返回消息类型:文本
37      */  
38     public static final String RESP_MESSAGE_TYPE_TEXT = "text";  
39   
40     /**
41      * 返回消息类型:音乐
42      */  
43     public static final String RESP_MESSAGE_TYPE_MUSIC = "music";  
44   
45     /**
46      * 返回消息类型:图文
47      */  
48     public static final String RESP_MESSAGE_TYPE_NEWS = "news";  
49   
50     /**
51      * 请求消息类型:文本
52      */  
53     public static final String REQ_MESSAGE_TYPE_TEXT = "text";  
54   
55     /**
56      * 请求消息类型:图片
57      */  
58     public static final String REQ_MESSAGE_TYPE_IMAGE = "image";  
59   
60     /**
61      * 请求消息类型:链接
62      */  
63     public static final String REQ_MESSAGE_TYPE_LINK = "link";  
64   
65     /**
66      * 请求消息类型:地理位置
67      */  
68     public static final String REQ_MESSAGE_TYPE_LOCATION = "location";  
69   
70     /**
71      * 请求消息类型:音频
72      */  
73     public static final String REQ_MESSAGE_TYPE_VOICE = "voice";  
74   
75     /**
76      * 请求消息类型:推送
77      */  
78     public static final String REQ_MESSAGE_TYPE_EVENT = "event";  
79   
80     /**
81      * 事件类型:subscribe(订阅)
82      */  
83     public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";  
84   
85     /**
86      * 事件类型:unsubscribe(取消订阅)
87      */  
88     public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";  
89   
90     /**
91      * 事件类型:CLICK(自定义菜单点击事件)
92      */  
93     public static final String EVENT_TYPE_CLICK = "CLICK";  
94   
95     /**
96      * 解析微信发来的请求(XML)
97      *  
98      * @param request
99      * @return
100      * @throws Exception
101      */  
102     @SuppressWarnings("unchecked")  
103     public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {  
104         // 将解析结果存储在HashMap中  
105         Map<String, String> map = new HashMap<String, String>();  
106   
107         // 从request中取得输入流  
108         InputStream inputStream = request.getInputStream();  
109         // 读取输入流  
110         SAXReader reader = new SAXReader();  
111         Document document = reader.read(inputStream);  
112         // 得到xml根元素  
113         Element root = document.getRootElement();  
114         // 得到根元素的所有子节点  
115         List<Element> elementList = root.elements();  
116   
117         // 遍历所有子节点  
118         for (Element e : elementList)  
119             map.put(e.getName(), e.getText());  
120   
121         // 释放资源  
122         inputStream.close();  
123         inputStream = null;  
124   
125         return map;  
126     }  
127   
128     /**
129      * 文本消息对象转换成xml
130      *  
131      * @param textMessage 文本消息对象
132      * @return xml
133      */  
134     public static String textMessageToXml(TextMessage textMessage) {  
135         xstream.alias("xml", textMessage.getClass());  
136         return xstream.toXML(textMessage);  
137     }  
138   
139     /**
140      * 音乐消息对象转换成xml
141      *  
142      * @param musicMessage 音乐消息对象
143      * @return xml
144      */  
145     public static String musicMessageToXml(MusicMessage musicMessage) {  
146         xstream.alias("xml", musicMessage.getClass());  
147         return xstream.toXML(musicMessage);  
148     }  
149   
150     /**
151      * 图文消息对象转换成xml
152      *  
153      * @param newsMessage 图文消息对象
154      * @return xml
155      */  
156     public static String newsMessageToXml(NewsMessage newsMessage) {  
157         xstream.alias("xml", newsMessage.getClass());  
158         xstream.alias("item", new Article().getClass());  
159         return xstream.toXML(newsMessage);  
160     }  
161   
162     /**
163      * 扩展xstream,使其支持CDATA块
164      *  
165      * @date 2013-05-19
166      */  
167     private static XStream xstream = new XStream(new XppDriver() {  
168         public HierarchicalStreamWriter createWriter(Writer out) {  
169             return new PrettyPrintWriter(out) {  
170                 // 对所有xml节点的转换都增加CDATA标记  
171                 boolean cdata = true;  
172   
173                 @SuppressWarnings("unchecked")  
174                 public void startNode(String name, Class clazz) {  
175                     super.startNode(name, clazz);  
176                 }  
177   
178                 protected void writeText(QuickWriter writer, String text) {  
179                     if (cdata) {  
180                         writer.write("<![CDATA[");  
181                         writer.write(text);  
182                         writer.write("]]>");  
183                     } else {  
184                         writer.write(text);  
185                     }  
186                 }  
187             };  
188         }  
189     });  
190 }  
  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。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348995-1-1.html 上篇帖子: 技术栈 下篇帖子: How to deploy JAVA Application on Azure Service Fabric
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表