开源社区工作日志
在这篇文章里,我将以一个RESTEasy的代码开发任务为例,记录我开源社区中的日常工作。其中包括我使用的工具,工作中的一些习惯和方法,以及工作流程。
在RESTEasy的邮件列表里面看到这样一个问题:
大意就是RESTEasy的Jackson是否支持JAXB标记。因为RESTEasy的JacksonProvider并没有支持Jackson+JAXB,所以应该是需要添加这部分的支持才可以。但是在真正动手之前,我还想和用户再沟通一下,这样有时候可以收集到一些你想不到的需求。于是我回复了一封邮件如下:
没多久,用户给出了回复:
是一封很详细的回复,把自己所需讲得很明白。有的时候,可能我得不到如此详尽的回复,或者用户提出问题后,就不再参与,收集不到更细节的需求,那么我就会根据用户的问题得到的启发,着手我的工作。这封邮件回复很详细,用户想要的和我一开始设想的是一样的,TA要的是JacksonProvider对JAXB的支持。
于是我开始使用Google在网上搜索Jackson和JAXB联合使用相关的内容。然后我找到了下述页面:
http://wiki.fasterxml.com/JacksonJAXBAnnotations
https://github.com/FasterXML/jackson-module-jaxb-annotations
通过上面的文章,了解到了Jackson从1.1开始支持JAXB标记。RESTEasy使用的Jackson版本是1.9.9,因此,应该是可以支持的。
因此,工作的第一步是搞明白问题本身:理解问题与解答问题是同等重要的,有时候所耗费的时间也是差不多的,甚至有时候理解问题要花费比解决问题更长的时间。
接下来我要做的是写代码,重现用户的问题。一般用户的问题都是针对他们自己的具体项目,有很多不相关的东西揉在用户提供过来的代码当中,不利于问题的分析。因此,我比较喜欢跟据用户的描述,自己写一个case,把问题重现出来,同时这个case只关注这个问题本身,去掉无关的东西。因此,我写了这样一个case在这里:
https://gist.github.com/3980877
写完后,我会给在邮件列表中回信,让用户确认我写的这个case重现了他们的问题。如果不对,我再修改这个case。此外,我还会让用户帮忙创建一个JIRA Issue,用来跟踪和记录针对这个问题的工作过程。下面是我给用户Michael在邮件列表中的回信:
很不巧的是,我在写这封信之前,有两周特别忙,因此实际上这封信的发出时间和上次与用户沟通相隔了两周。因此,这封信没有得到回音,这是社区里面经常会遇到的问题,如果你不是很及时地解决了用户的问题,用户在等待的期间精力又转去别处了,或者注意不到你两周后的回复。
但是社区一般开发资源非常有限,不可能对每一个用户的问题都能及时跟进和解决。特别是像RESTEasy这样有很大用户群的项目,用户的反馈特别多,而主要的开发人员只有Bill, Ron和我三个人。
因此,我等待了一天用户的回信,Miachel没有给我回复。但是这个问题之前沟通得比较充分了,因此我相信利用现有的信息可以继续我手头的工作,于是我不再等待用户的回复,开始着手解决这个问题。
首先我针对这个问题创建了一个JIRA Issue:
然后我把这件事情放在我的todo list里面:
因为社区的事情多且杂,每天光处理邮件就要几十封,如果光靠脑子记,是肯定记不住这么多事情的。因此,用一个好的TODO LIST软件来管理自己要做的事情是非常重要的。我比较喜欢使用Things这个软件来管理要做的事情。如果你对这个软件感兴趣,它的主页在这里:
http://culturedcode.com/things/
接下来要进行的就是实际的代码开发工作了。我首先找到RESTEasy的Jackson相关的代码,并进行阅读和分析工作:
RESTEasy有20多万行代码,因此对项目的熟悉程度非常重要。这依赖于日常的不断积累,包括系统有计划地阅读RESTEasy的代码;使用RESTEasy做各种实验项目;看RESTEasy的文档,等等。也就是常说的做任何事要坚持。
因此,我可以快速地定位到要改动的地方,即ResteasyJacksonProvider:
public class ResteasyJacksonProvider extends JacksonJsonProvider
protected final MapperConfigurator _mapperConfig;
public synchronized void setAnnotationsToUse(Annotations[] annotationsToUse) { _setAnnotations(mapper(), annotationsToUse);} /** * Default annotation sets to use, if not explicitly defined during * construction: only Jackson annotations are used for the base * class. Sub-classes can use other settings. */ public final static Annotations[] BASIC_ANNOTATIONS = { Annotations.JACKSON }; /** * Default constructor, usually used when provider is automatically * configured to be used with JAX-RS implementation. */ public JacksonJsonProvider() { this(null, BASIC_ANNOTATIONS); }protected final MapperConfigurator _mapperConfig;
public ResteasyJacksonProvider() { super(); Annotations[] ANNOTATIONS = {Annotations.JACKSON, Annotations.JAXB}; _mapperConfig.setAnnotationsToUse(ANNOTATIONS); } @XmlRootElement public static class XmlResourceWithJAXB { String attr1; String attr2; @XmlElement(name = "attr_1") public String getAttr1() { return attr1; } public void setAttr1(String attr1) { this.attr1 = attr1; } @XmlElement public String getAttr2() { return attr2; } public void setAttr2(String attr2) { this.attr2 = attr2; } } public static class XmlResourceWithJacksonAnnotation { String attr1; String attr2; @JsonProperty("attr_1") public String getAttr1() { return attr1; } public void setAttr1(String attr1) { this.attr1 = attr1; } @XmlElement public String getAttr2() { return attr2; } public void setAttr2(String attr2) { this.attr2 = attr2; } } @Path("/jaxb") public static class JAXBService { @GET @Produces("application/json") public XmlResourceWithJAXB getJAXBResource() { XmlResourceWithJAXB resourceWithJAXB = new XmlResourceWithJAXB(); resourceWithJAXB.setAttr1("XXX"); resourceWithJAXB.setAttr2("YYY"); return resourceWithJAXB; } @GET @Path(("/json")) @Produces("application/json") public XmlResourceWithJacksonAnnotation getJacksonAnnotatedResource() { XmlResourceWithJacksonAnnotation resource = new XmlResourceWithJacksonAnnotation(); resource.setAttr1("XXX"); resource.setAttr2("YYY"); return resource; } } @Test public void testJacksonJAXB() throws Exception { { DefaultHttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(generateBaseUrl() + "/jaxb"); HttpResponse response = client.execute(get); BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); Assert.assertTrue(reader.readLine().contains("attr_1")); } { DefaultHttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(generateBaseUrl() + "/jaxb/json"); HttpResponse response = client.execute(get); BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); Assert.assertTrue(reader.readLine().contains("attr_1")); } }

git checkout -b RESTEASY-795-trunkgit push origin RESTEASY-795-trunk



