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 }
很明显,问题应该不会出现在第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框架的源码来解决问题。
可能很多看到标题进来的读者,就是想知道这个问题是如何解决的,并不想听我哆嗦半天。授人鱼不如授人以渔,我之所以将问题的发现、分析和解决整个过程写出来,也是希望能够帮助更多初学者逐渐掌握自行解决问题的方法。