• 对于很多团队来说,开发和运维现在还是两个世界的人,开发人员写着属于自己的代码,然后丢给运维人员。但作为开发人员,我们必须知道,运维的方式对于开发上的抉择是有影响的。

    和这个世界上的许多项目一样,我现在正在开发的项目也有一些后台定时运行的任务。这是一个Java应用,但我并不想把这些定时任务扔进Java EE容器里,没有必要让这些后台应用和前台应用抢资源。所以,我们就把它做成了一个独立的应用。好,问题来了,谁来做定时调度?

    因为我们的应用最终会部署在Linux操作系统上,所以,我的第一个直觉就是采用Cron。这是一个已经存在了几十年的解决方案,没有任何问题,而且,开发团队几乎不需要做任何额外工作。这个方案一直存在到我们和运维团队交流为止。

    “我们不允许使用任何系统任务”,运维团队开门见山地否决了我们的解决方案。运维团队给出的理由是,他们无法保证一台机器上只运行一个应用,如果其中一个应用挂了,运维人员也许会清理一些资源,换句话说,如果你的应用用了这些东西,也许会被一不小心地删掉了。“所以,按照我们规定,每个应用只能开辟自己的目录,运用自己目录下东西。”

    这是一个合理的要求,所以,我们需要调整自己的设计方案,把原来交由系统处理的调度转成由自己的应用处理。当然,在Java世界,这不是太大的难度,Quartz框架很好地帮我们处理了这些。

    其实,与调度方案同时被推翻的还有我的另外一个方案。这次我原本想尝试把我们的日志写到系统日志里。如果你不知道的话,rsyslog可以让我们把自己的日志写到/var/log下。很显然,这样的方案在这样约束下也是不行的。我们只好回到Java的传统方式上,把日志写到自己的目录下。

    这是两个由运维反过来影响开发方案的小例子。运维是开发的一种很重要的组成部分,运维团队的一些工作方式直接影响到开发上的一些决策。所以,如果开发和运维还是两个团队,开发团队不妨多找运维团队聊聊,更多地了解关于部署的方方面面。当然,更好的解决方案是走向通往DevOps的康庄大道。

  • Jackson雕虫技(一)
    Jackson雕虫技(二)
    Jackson雕虫技(三)

    忽略空字段

    有时候,我们返回对象的字段可能会有为空的情况。缺省情况下,这些字段会以null的形式呈现出来。但如果我们希望忽略这些字段该如何处理呢?如果是针对某个具体的类,我们可以使用JsonInclude这个annotation。

    @JsonInclude(JsonInclude.Include.NON_NULL)
    class Entity {
       ...
    }

    如果所有类都能够遵循这样的规则,那就要在全局配置了,配在Object Mapper上:

      mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

    Joda-Time

    在《你应该更新的Java知识》里面,我推荐大家在Java 8之前使用Joda Time,而非JDK原生的Date和Calendar。但是,如果我们使用了Joda Time,到了Jackson该怎么办呢?

    Java对象和JSON的相互转换,只是一个序列化和反序列化的过程。我们在之前的雕虫技中已经见识过如何针对自己的类自定义序列化和反序列化。所以,本质上来说,为Joda Time自定义一套,自然也不是什么难事。不过,通用如Joda Time这样的程序库,自然也应该有现成的支持。我们直接拿过来用就好了。

    实际上,Jackson已经为Joda Time提供了官方支持。如果你和我一样喜欢Gradle,下面就是依赖的添加方式,其中,jacksonVersion自然是Jackson的版本号。

      "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion"

    有了依赖,我们只要在Object Mapper上注册一个模块即可。

      mapper.registerModule(new JodaModule());

    现在就可以自己的对象里使用DateTime、LocalDate、LocalTime这样的类型了,有了JodaModule,Jackson就会为我们照顾好这些类型的转换。

    我们习惯的日期表示方式一般是年月日时分秒,但Joda Time缺省的做法却是一个数字,一个以毫秒为单位计算出的数字,它叫时间戳。如果你希望让它变成我们喜欢的样子,可以这样做:

      mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

    如果你还让自己的日期表现方式中加入时区,那就再加上时区:

      mapper.setTimeZone(TimeZone.getDefault());

    Guava

    同样在《你应该更新的Java知识》,我还提到一个观点,只要是Java项目就应该使用Guava。我也展示过Guava中一些库的用法。我现在已经无可救药地爱上了Guava中的不变集合。但同Joda Time一样,Guava不是标准的JDK的一部分,也需要额外的支持。值得高兴的是,这个支持也有现成的,也来自Jackson的官方,依赖如下:

      "com.fasterxml.jackson.datatype:jackson-datatype-guava:$jacksonVersion"

    同样,它也要Object Mapper上注册一个模块。

      mapper.registerModule(new GuavaModule());

    如此一来,Jackson就可以支持Guava提供的大部分内容,包括Optional和一些不变集合,以及一些新增的集合。

  • Jackson雕虫技(一)
    Jackson雕虫技(二)

    使用Builder模式

    在日常开发中,我们希望自己编写的类尽可能不变的,对于参数比较多的类,我们通常采用的方法是Builder模式。但如果我们使用Builder模式构造这样的不变对象,当我们将json反序列化成Java对象该怎么办呢?

    Jackson已经为这种做法做好了准备,我们可以告诉它这个类是采用Builder模式构建的:

    @JsonDeserialize(builder = DefaultHttpRequest.Builder.class)
    public class DefaultHttpRequest implements HttpRequest {
     ...
    }

    我们使用了一个Annotaiton:@JsonDeserialize,通过builder参数告诉它,用哪个类做Builder。这里用的就是这个类的一个内嵌类:Builder。

    public class DefaultHttpRequest implements HttpRequest {
       ...

      public static final class Builder {

        ...

        public Builder withVersion(String version) {
          this.version = version;
          return this;
       }

        ...

        public DefaultHttpRequest build() {
          ...
          return request;
       }
      }
    }

    其中,以with开头的就是用来传参数的方法,而build方法则用以构建最终的对象,这是一个缺省的约定。我们还可以按照自己的需要进行订制,只不过要给我们的Builder加上另外一个Annotation:@JsonPOJOBuilder。下面是一个例子:

    @JsonPOJOBuilder(buildMethodName="create", withPrefix="con")
    public static final class Builder {
       ...
    }

    这样一来,所有传参的方法都是以con开头,而构建对象的方法名则改成了create。

    使用对象简化解析

    在《Jackson雕虫技(二)》,我们提到了可以用自定义解析的方式解析对象,但一个一个字段解析写起来并不直观。其实,我们还可以借用已有的对象解析机制简化这个过程。下面是MocoProxyContainerDeserializer,它根据当前正在解析的目标进行处理,要么解析成一个URL,要么解析成一个Proxy的配置。

    public class ProxyContainerDeserializer extends JsonDeserializer {
       @Override
       public ProxyContainer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        JsonToken currentToken = jp.getCurrentToken();
         if (currentToken == JsonToken.VALUE_STRING) {
          return builder().withUrl(jp.getText().trim()).build();
        } else if (currentToken == JsonToken.START_OBJECT) {
          InternalProxyContainer container = get(jp.readValuesAs(InternalProxyContainer.class), 0);
          return container.toProxyContainer();
       }

        throw ctxt.mappingException(TextContainer.class, currentToken);
      }
    }

    这里的关键是readValuesAs,我们没有直接解析接下来的Token,而是确定解析目标之后,又借用了解析器本身的能力,把它解析成一个对象。至于InternalProxyContainer,它只是一个简单的对象类,用以装载解析的结果。

    private static class InternalProxyContainer {
      public String url;
      public String from;
      public String to;
      public String failover;
      public String playback;

      public ProxyContainer toProxyContainer() {
        ...
      }
    }

  • 2014-02-01

    Moco 0.9.1发布

    Tag:Moco

    前版信息:Moco 0.9发布

    我很高兴地宣布,Moco 0.9.1发布了。

    Moco是什么?

    Moco是一个可以轻松搭建测试服务器的框架/工具/程序库。

    变更

    按照版本号来说,这是一个小的修复版本,但实际的修改一点都不少。

    首先,这个版本增加了runner API,让我们可以自己在测试代码里控制Moco服务器的启停。最常见的做法是在集成测试的最初,启动一个服务器,结束之后关闭,下面是一个例子:

    @Before
    public void setup() {
       HttpServer server = httpserver(port());
       server.response("foo");
       runner = runner(server);
       runner.start();
    }

    @After
    public void tearDown() {
       runner.stop();
    }

    在配置API方面,增加了对proxy的批处理方法,我们可以一次性的代理一组URL,比如:

    server.proxy(from("/proxy").to("http://remoteUrl/target"));

    proxy还增加了一个playback的配置,它也是用来在本地存储远程服务器的内容,与failover不同的是,当本地内容可用时,它就不再访问远程服务器,换言之,它是本地优先的。

    server.request(by(uri("/proxy_playback")))
         .response(proxy("http://remoteUrl/target"), playback(path-to-local-playback)));

    这次的发布对模板也进行了改进,增加了对模板变量的支持,你可以根据自己的需要定制模板内容:

    server.request(by(uri("/template"))).response(template("${var}", "var", "TEMPLATE"));

    这次发布还增加了一个全局配置选项:Response。它的作用是,给所有的应答添加一个同样的内容,比如HTTP头。它主要用于独立运行模式,模拟很多类似的请求:

    [
       {
           "response" : {
               "headers" : {
                   "foo" : "bar"
               }
           },
           "include": "src/test/resources/settings/foo.json"
       }
    ]

    本次发布修正了Moco存在的一些问题,比如HTTP连接关闭,内容验证,服务器完全关闭等问题,让Moco的质量更上了一个台阶。本次发布还有一个重大的调整,采用Proguard将Standalone的JAR规模大幅度缩小,上一次发布Moco的Standalone包有8M多,而这次只有不到5M。

    更多的细节请参考ReleaseNotes

    感谢

    感谢崔鹏飞,协助发现修复了HTTP连接关闭的问题。

    感谢黄云坤,实现Proguard压缩Standalone JAR包。

    感谢Marcin Grzejszczak,协助发现并修复了服务器完全关闭的问题,并贡献了gmoco的脚本。

  • 2013-12-31

    回望2013

    2013年就这么又过去了,站在又一年的门槛上,想想这过去的一年,如果选择一个主题词,我想是“坚持”。

    Moco,我在2012年无意间开启的一个开源项目,居然让我坚持住了。我经常会兴之所至,开始写一些小项目,但如果不是有像Github之类的站点,我都不记得我曾经这么想过。但Moco,我就这么一直做下来了。回想起来,Moco从一开始就是一个相对完整的小项目,从一开始就有用户,有反馈,我也就乐呵呵地一直做着。每天为Moco写点代码已经成了我日常生活的一部分,所以,我有了超过300天连续提交。

    坚持的回报是,Moco在2013年拿了两个大奖,一个是ThoughtWorks中国区技术大赛最具技术创新奖,另一个则是更具外部影响力,2013年Oracle的Duke选择奖,这个奖让更多人认识了Moco,也让Moco有了更多的用户,更多的反馈,也促使我不断地,美滋滋地继续为Moco写代码。在可见的未来,Moco还有不少要实现的东西。

    2013年11月,我用了大半年翻译的《The Joy of Clojure》终于正式出版了,也算是为之前的辛苦划上一个句号。正是因为我花了很多时间和精力在上面,所以,我觉得,翻书这活这不好干:不好好干,对不起别人;好好干,对不起自己。于是,我暗下决心,洗手不干了。就在我决定洗手不干了之后,我又组织翻译了《ThoughtWorks文集2》,虽然我没有翻译初稿,但后续的审校花费的时间和精力不比任何一个译者少,所以,最终写译者序的人,居然是我。

    2013年,我还偶尔出去讲了几次东西。在Open Party成都,我讲过Moco,讲过《Java as a Platform》。因为Duke选择奖的原因,我又得到了一个机会,在QCon上分享Moco的故事:《不可思议的Moco》。运气很不错,除了免费差旅,我还拿到了优秀讲师的称号,奖品是一个Kindle Paperwhite 2,据说InfoQ中文站的老大Kevin肉身翻墙背回来的。去年年底,因为出差澳洲的原因,错过了敏捷之旅成都站,今年组织者竟然还记得有我这个人存在,于是,年底又在敏捷之旅讲了一个《你应该更新的开发知识》,还被放到了主题演讲的位置。

    从2013年5月份底开始,ThoughtWorks中国区这边开始有了一个小栏目,ThoughtWorker好声音。我们每周会选择几篇ThoughtWorker写的技术方面的文章组织起来,分享给更多的人看。从一开始的成都办公室内部分享,到后来中国区的内部分享,再到后来,我们搭建了自己的网站,通过ThoughtWorks的微博、微信对外分享。这个小栏目已经逐渐受到越来越多人的关注,它已经成为ThoughtWorker对外展示的舞台。在内部发行版里,我们甚至会把它翻译成英文,让我们更多的外国同事了解到中国区这边的技术发展情况。之所以我把公司的小栏目放到自己的年终总结里面,因为我是这个活动背后的发起者,好声音最初的几期完全是由我一手打造的。现在,我已经不再是这个栏目的瓶颈了,背后已经有了更多的人。我很欣慰,因为我们坚持下来了。

    2013年初,我给自己制定了一个读书目标,一年读30本书。最终的结果是,一年读了52本书。原来制定目标,只是为了让自己多读书,且不会压力太大,结果养成了读书的习惯,每天早晚都会拿起书读一会。虽然我一直号称自己喜欢读书,但还真没抓紧时间读过,有不少以前“想”读的书,这次是真读了。感谢Kindle,让读书变得比以往更容易了。

    2013年,也是我写blog的第十个年头,也许因为时间关系,现在已经没有从前写得那么频繁了,但脚步没有停下。用它总结“坚持”的一年是很恰当的。在年中做的一篇CSDN访谈中,我说过:“找到一个适合自己的平台,坚持写程序,坚持思考,十年后,便会与众不同。”现在,我越发相信坚持的力量了。

    2013年就这么结束了,从结果上来看,这是我值得骄傲的一年,做出了以往不曾做过的一些事。2014年,我还希望自己能够做一些不同以往的事。