在这篇文章里,我将以一个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
注意到JacksonJsonProvider是Jackson这个开源项目提供的,而不是RESTEasy自己的东西,因此我去Jackson的网站上下载相关代码。
在开源社区里面就是这样,一个项目依赖另一个项目,你经常需要进行跨项目的代码分析。因此,阅读代码的能力和分析代码的能力要好好地积累和培养,要能够比较快地去阅读新的代码和新的项目,这是一个经验与技术相结合,需要时间去积累的能力。
最后,我从Jackson的源代码中找到JacksonJsonProvider这个类,进行阅读和分析。这是个700多行的Java Class,对于开源项目中的Class来讲,算是一个中等规模的类。我不可能逐行地,漫无目的地去读它,那样的话没有任何意义。因此我必须要针对手里的问题来分析这个代码。
因此,回到刚才说的,理解问题本身很重要。我要做的是使RESTEasy的Jackson Provider要支持JAXB标记。而从Jackson的文档中我已经知道,Jackson从1.1开始支持JAXB标记。而RESTEasy使用的Jackson版本是1.9.9,因此,应该是可以支持的。
于是我深入阅读了Jackson的源代码,学习了Jackson是支持JAXB的使用方法。而我通过阅读ResteasyJacksonProvider,知道了RESTEasy里面的JacksonProvider是对Jackson自带的JacksonJsonProvider的简单封装。因此,我要做的就是通过ResteasyJacksonProvider使用JacksonJsonProvider提供的JAXB解析能力。
思路确定下来以后,就是具体的代码分析过程,经过大概一个多小时的分析,我了解到了JacksonJsonProvider是通过_mapperConfig来控制是否支持JAXB:
protected final MapperConfigurator _mapperConfig;
MapperConfigurator包含有这样一个方法:
public synchronized void setAnnotationsToUse(Annotations[] annotationsToUse) {
_setAnnotations(mapper(), annotationsToUse);
}
这个就是设定是否支持JAXB标记的地方。而JacksonJsonProvider默认只支持Jackson标记,并没有开启JAXB标记:
/**
* 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);
}
注意到_mapperConfig是protected:
protected final MapperConfigurator _mapperConfig;
因此,我可以直接在ResteasyJacksonProvider操作这个instance,因此,最后的任务变得非常简单,就是在ResteasyJacksonProvider添加代码,注入JAXB标记即可:
public ResteasyJacksonProvider() {
super();
Annotations[] ANNOTATIONS = {Annotations.JACKSON, Annotations.JAXB};
_mapperConfig.setAnnotationsToUse(ANNOTATIONS);
}
实际上最终的改动非常简单,但是整个分析过程和沟通过程要花去几个小时。
最后,还要在RESTEasy中添加相关的单元测试:
@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"));
}
}
虽然改动很简单,但是测试却很长。对于RESTEasy这种规模的项目,你的一个小改动也许造成意想不到的BUG,因此测试必须充分。最后,要在本机整体跑一遍单元测试,不但要跑新加的测试,所有的测试都要跑一遍,这叫做Regression Test,以免新的Patch导致原来没问题的地方出错:
确定没有问题后,就可以针对这些改动创建一个新的Branch,然后提交到自己fork的项目当中:
git checkout -b RESTEASY-795-trunk
git push origin RESTEASY-795-trunk
然后,我就可以在github上面创建一个pull request了:
这样,我们的实体工作就算是完成了,等待RESTEasy社区leader Bill把request给合并到upstream就可以了。
接下来,我会在JIRA里面加上Pull Request的地址,方便以后查阅:
以上就是一个问题解决的过程。如果遇到比较热心的用户,会很积极地与你交流沟通解决问题,因此通常我会在提交Pull Request之前,在JIRA当中给用户一个Example和我Patch过的RESTEasy,让用户去帮忙验证一下这个Patch是否解决了问题。就像在这个issue当中做的一样:
https://issues.jboss.org/browse/RESTEASY-789
参与开源社区开发是一件非常有意思的事情。此外,你还能收获巨大的成就感,结识世界各地的技术朋友。希望能有更多的来自中国的技术爱好者,参与到世界级的开源社区工作当中。