• 2013-09-11

    Hello,Akka

    Tag:akka

    只要稍微了解过一些Scala,这门JVM上广受关注的程序设计语言,你一定会对其中的一个Actor特性印象深刻。Actor是另一种进行并发计算的方式。通过在不同的Actor之间彼此发送消息,我们会惊喜地发现,那令人纠结的锁将不再困扰我们。

    不过,那是Scala的世界,作为一个Java程序员难道只有艳羡的份吗?显然不是。Akka把Actor带到了Java世界。

    实际上,Akka主要是以Scala编写的,但它很好地支持Java API,让所有特性对于Java程序员也唾手可得。即使相比于与Scala内建的Actor,Akka也不遑多让。Akka除了最基本的Actor特性外,它可以能在多台机器上透明地实现Actor,此外,还提供了很好的监管特性,这些都是Scala内建的Actor所不具备的。

    说了那么多,让我们用一个Hello的例子和Akka打个招呼吧!

    既然我们这里关注的是Actor,我们就来实现两个Actor,让它们之间实现问候。下面是一个:

    import akka.actor.Props;
    import akka.actor.UntypedActor;
    import akka.actor.ActorRef;

    public class HelloWorld extends UntypedActor {

        @Override
       public void preStart() {
           final ActorRef greeter =
                   getContext().actorOf(Props.create(Greeter.class), "greeter");
           greeter.tell(Greeter.Msg.GREET, getSelf());
       }

        @Override
       public void onReceive(Object msg) {
           if (msg == Greeter.Msg.DONE) {
               getContext().stop(getSelf());
           } else {
               unhandled(msg);
           }
      }
    }
    (HelloWorld.java)

    这个HelloWorld继承了UntypedActor,表明我们实现的是一个Actor。

    其中的preStart是在启动这个Actor时调用的方法。在这里,我们创建了另一个Actor的实例。我们稍后会看到另一个Actor Greeter的实现。然后,我们调用tell方法给它发了一个消息,Greeter.Msg.GREET,后面的getSelf()给出了一个Actor的引用(ActorRef),用以表示发消息的Actor。这只是启动一个Actor,后面的部分才是更重要的。

    onReceive方法是处理我们接收到消息的情况。这里我们看到,如果接收到的消息是一个Greeter.Msg.DONE,我们就会停下(stop)所有的处理,同样,getSelf()指明停下的目标,否则的话,就说我们没处理(unhandled)。

    看完了一个Actor,或许你已经迫不及待地想看与它打交道的另一个Actor了,下面就是:

    import akka.actor.UntypedActor;

    public class Greeter extends UntypedActor {

        public static enum Msg {
           GREET, DONE
       }

        @Override
       public void onReceive(Object msg) {
           if (msg == Msg.GREET) {
               System.out.println("Hello World!");
               getSender().tell(Msg.DONE, getSelf());
           } else {
               unhandled(msg);
           }
       }
    }
    (Greeter.java)

    同样的Actor结构,我们已经在HelloWorld类里面看到了。在onReceive方法里,如果它接收到的消息是Msg.GREET,它就打印出“Hello World!”,然后,给发送者回复一条Msg.DONE。没处理的话,就说没处理。

    就是这么简单!

    好,万事具备,我们把它跑起来。你看到了我们还没有入口点,实际上,Akka自身为我们提供了一个。设置好classpath之后,我们只要这样运行即可:

        java akka.Main HelloWorld

    这里的akka.Main就是我们的入口点,我们把HelloWorld这个类名传给它作为启动参数。运行的结果自然是我们最熟悉的:

        Hello World!

    好,就到这里吧!

  • 2013-09-09

    Hello,Dropwizard

    Tag:dropwizard

    Dropwizard是Java世界里给人带来新思维的一个框架,它在主页上如是说:

    Dropwizard is a Java framework for developing ops-friendly, high-performance, RESTful web services.

    这句话不足以概括Dropwizard的新思维,在我看来,Dropwizard的新,在于它把轻量级的开发/部署的概念带回了Java世界。我们用个最简单的例子来体验一下Dropwizard。

    首先要声明一点的是,Dropwizard集成了众多开源框架,所以,这里看到的许多API并不是Dropwizard本身的API。

    因为Dropwizard侧重点在于服务,所以,我们来开发一个打招呼的服务。你已经看到了这里它的长项是RESTful的服务,所以,我们先写一个Resource:

    import com.google.common.base.Optional;

    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.QueryParam;
    import javax.ws.rs.core.MediaType;

    @Path("/consent")
    @Produces(MediaType.APPLICATION_JSON)
    public class HelloResource {
      @GET
      public HelloResult sayHello(@QueryParam("name") Optional name) {
          return new HelloResult(String.format("Hello, %s", name.or("Stranger")));
      }
    }
    (HelloResource.java)

    Dropwizard里用来做RESTFul服务的框架是Jersey,所以,可以参考Jersey的文档来更好地理解这里的代码。不过,这段代码本身很简单,定义了一个Resource,其中的sayHello是一个GET请求,用来和人打招呼。另外,你看到了这里用到了Optional

    HelloResult是一个服务与客户端交互的对象,正如我们服务中所定义的,它会产生一个JSON对象,这个转换是由Jackson完成的。下面是HelloResult类的实现。

    public class HelloResult {

        private String result;

        public HelloResult(String result) {
           this.result = result;
       }

        public String getResult() {
           return result;
       }
    }
    (HelloResult.java)

    好了,最基础的东西有了,我们可以把它们连在一起,运行起来。还记得你上次写main函数是什么时候吗?

    import com.yammer.dropwizard.Service;
    import com.yammer.dropwizard.config.Bootstrap;
    import com.yammer.dropwizard.config.Configuration;
    import com.yammer.dropwizard.config.Environment;

    public class HelloMain extends Service {
       public static void main(String[] args) throws Exception {
           new HelloMain().run(args);
       }

        @Override
       public void initialize(Bootstrap bootstrap) {
       }

        @Override
       public void run(Configuration configuration, Environment environment) throws Exception {
           environment.addResource(new HelloResource());
       }
    }

    简单吧?这里我们就继承了一个Service,然后,在run方法里把我们的Resource添加到环境里,最后,在main函数里启动起来。

    有了main函数,我们就可以直接把它运行起来,不过,这里是要有参数的:server,指定程序按照server的方式运行。

    好了,我们看到程序启动起来了,虽然我们本身什么都没做,实际上,Dropwizard却利用Jetty帮我们把服务加载起来了。剩下的就是打开浏览器,输入

      http://localhost:8080/hello

    你会看到

      {"result":"Hello, Stranger"}

    我们几乎没做什么,但JSON已经产生了。它还不认识我们,我们需要一个自我介绍,输入

      http://localhost:8080/hello?name=dreamhead

    你会看到

      {"result":"Hello, dreamhead"}

    好,一个最简单的RESTful服务已经开发完毕。如果你是一个传统Java应用的开发者,回想一下,按照原有的工作方式,开发这样一个服务需要怎样的工作量。别的不说,搭建一个Web服务器,配置web.xml,半天就没了,而我们分分钟就可以启动一个服务,这是怎样的效率提升啊!

    作为一个Hello的例子,本篇的目标已经达成,就到这里吧!

  • 你应该更新的Java知识之常用程序库(一)
    你应该更新的Java知识之常用程序库(二)
    你应该更新的Java知识之构建工具
    你应该更新的Java知识之Observer
    你应该更新的Java知识之集合初始化
    你应该更新的Java知识之集合操作
    你应该更新的Java知识之惰性求值
    你应该更新的Java知识之Optional

    介绍了Optinal的基本用法,我们来看一个有趣的例子,找到一个人的出生国家。按照传统的思路,代码大约是这个样子:

    Place place = person.getPlaceOfBirth();
    if (place != null) {
     City city = place.getCity();
     if (city != null) {
       Province province = city.getProvince();
       if (province != null) {
         return province.getCountry();
       }
     }
    }

    return null;

    如果你对整洁代码稍有追求,这样的if套if都会让你觉得不爽。让我们尝试用Optional改造它,不过,事先声明一下,以下代码并不在Guava代码库里,而是自行的扩展,也是弥补Guava Optional的缺失,你可以把下面的代码添加到你自己的程序库中,作为基础代码:

    首先,我们要给Optionals添加一个方法:

    public class Optionals {
       public static <T, U> Optional bind(Optional value,
                                           Function<T, Optional> function) {
         if (value.isPresent()) {
          return function.apply(value.get());
        }

        return absent();
      }
    }
    (参见具体代码

    这个方法的意图是,对一个Optional值(value)执行一个操作(function),如果value不是空,则对value执行操作,否则,返回空。

    如果单纯从这个函数,你还不是很清楚它到底能做些什么,那我们就直接来看代码:

    bind(
     bind(
       bind(
         bind(personOptional, getPlaceOfBirth()),                          
         getCityFromPlace()),
       getProvinceFromCity()),
     getCountryFromProvince());

    我们连着用了几个bind连Person对象里一层一层地往外找我们所需的值,如你所料,这里的每个函数实际上都是一个函数对象,我们就以其中的一个演示一下基本的做法:

    Function<Province, Optional> getCountryFromProvince() {
     return new Function<Province, Optional>() {
       @Override
       public Optional apply(Province input) {
         return fromNullable(input.getCountry());
       }
     };
    }

    把所有这些放在一起你就应该理解了,在这中间执行的任何一个环节如果出现空值,那么整个的返回值就是一个空值,否则,它就会一层一层的执行下去。

    这样一来,如果我们把bind函数视为程序库里的函数,那我们的客户端代码里面,一个if都没有出现,我们成功地消除那一大堆的if嵌套。

    不过,这种括号套括号的用法颇有Lisp风味,作为一个Java程序员,我们对于这样的写法还着实需要适应一下。让我们再进一步探索一下,看看怎么能把它做得更Java一些。

    public class FluentOptional {
       private Optional optional;

       private FluentOptional(Optional optional) {
         this.optional = optional;
      }

      public static FluentOptional from(Optional optional) {
         return new FluentOptional(optional);
     }

      public FluentOptional bind(Function<T, Optional> function) {
         if (isPresent()) {
             return from(function.apply(get()));
         }

          return from(Optional.absent());
     }

      ...

    (参见具体代码

    通过这段代码,我们可以用FluentOptional提供一个对Optional类的封装,这里面我们新增了两个方法from和bind,其它方法都是可以由Optional提供,实现很容易,这里省略了。我们看看通过这个新实现,我们的方法变成了什么模样:

    from(personOptional)
      .bind(getPlace())
      .bind(getCityFromPlace())
      .bind(getProvinceFromCity())
      .bind(getCountryFromProvince());

    怎么样,如此一来,代码就就像Java代码了吧!

    实际上,这种做法也是来自一种函数式编程的理念:Maybe Monad,这是Haskell程序设计语言为探索纯函数式编程所做的努力之一,这里就不做过多的介绍了。

  • 你应该更新的Java知识之常用程序库(一)
    你应该更新的Java知识之常用程序库(二)
    你应该更新的Java知识之构建工具
    你应该更新的Java知识之Observer
    你应该更新的Java知识之集合初始化
    你应该更新的Java知识之集合操作
    你应该更新的Java知识之惰性求值

    java.lang.NullPointerException,只要敢自称Java程序员,那对这个异常就再熟悉不过了。为了防止抛出这个异常,我们经常会写出这样的代码:

    Person person = people.find("John Smith");
    if (person != null) {
     person.doSomething();
    }

    遗憾的是,在绝大多数Java代码里,我们常常忘记了判断空引用,所以,NullPointerException便也随之而来了。

    “Null Sucks.”,这就是Doug Lea对空的评价。作为一个Java程序员,如果你还不知道Doug Lea是谁,那赶紧补课,没有他的贡献,我们还只能用着Java最原始的装备处理多线程。

    "I call it my billion-dollar mistake.",有资格说这话是空引用的发明者,Sir C. A. R. Hoare。你可以不知道Doug Lea,但你一定要知道这位老人家,否则,你便没资格使用快速排序。

    在Java世界里,解决空引用问题常见的一种办法是,使用Null Object模式。这样的话,在“没有什么”的情况下,就返回Null Object,客户端代码就不用判断是否为空了。但是,这种做法也有一些问题。首先,我们肯定要为Null Object编写代码,而且,如果我们想大规模应用这个模式,我们要为几乎每个类编写Null Object。

    幸好,我们还有另外一种选择:Optional。Optional是对可以为空的对象进行的封装,它实现起来并不复杂。在某些语言里,比如Scala,Optional实现成了语言的一部分。而对于Java程序员而言,Guava为我们提供了Optional的支持。闲言少叙,先来如何使用Optional,完成前面的那段代码。

    Optional person = people.find("John Smith");
    if (person.isPresent()) {
     person.get().doSomething();
    }

    这里如果isPresent()返回false,说明这是个空对象,否则,我们就可以把其中的内容取出来做自己想做的操作了。

    如果你期待的是代码量的减少,恐怕这里要让你失望了。单从代码量上来说,Optional甚至比原来的代码还多。但好处在于,你绝对不会忘记判空,因为这里我们得到的不是Person类的对象,而是Optional。

    看完了客户端代码,我们再来看看怎样创建一个Optional对象,基本的规则很简单:

    如果我们知道自己要封装的对象是一个空对象,可以用
     Optional.absent();

    如果封装的对象是一个非空对象,则可以用
     Optional.of(obj);

    如果不知道对象是否为空,就这样创建创建
     Optional.fromNullable(obj);

    有时候,当一个对象为null的时候,我们并不是简单的忽略,而是给出一个缺省值,比如找不到这个人,任务就交给经理来做。使用Optional可以很容易地做到这一点,以上面的代码为例:

      Optional person = people.find("John Smith");
      person.or(manager).doSomething()

    说白了,Optinal是给了我们一个更有意义的“空”。

  • gigix总喊着一年50本书,和这样的人工作在一起,自然很有压力。我也号称喜欢读书,回头查了一下自己的豆瓣,一年不过十来本。其实,我也好奇,一年怎么能读那么多的书。2012年底,我就给自己制定了一个新年读书计划:30本。这个数没有那么多,我是不想步子迈得太大。

    2013年8月11日,年度30本的计划达成。

    曾经,觉得读那么多书不可能,现在看来,只是没有抓紧时间读书而已。很多人以为读书都是要拿出大把时间正襟危坐地细细品味,所以,抱怨自己没有时间、没有地点、没有合适的方式读书。其实,根本没那么多要求,闲暇时,抓起本书读就好了。我知道,有很多同事朋友是在利用上下班时间公交地铁时间在读书。

    现在下班之后,我基本上已经不开电脑,陪着家人聊天散步就是我如今的生活。有时,趁着家人忙碌的空隙,我就随手抽出一本书来读。周末也一样,有时甚至是一家人各捧一本书在读。家里领导也盘点了一下,今年似乎也读了十几本书。出差是ThoughtWorker生涯中无可规避的事情,随身带几本书,利用飞机时间来读书是一个不错的选择,毕竟一趟飞机,少则一个多小时。现在有了Kindle,不用随身带那么多书,轻了很多。顺便说一下,在亚马逊商店买书很方便,价格也大多很便宜。

    程序员的直觉是,程序员读书就要读技术书。哪来的规定?我今年读完的书里,技术书还真不多。混迹了程序世界十几年,现在对技术书的选择其实比以前更加挑剔了,大多数技术书我只是当做参考资料翻翻,这样的书,我基本上不会说自己读了。少有一些书,才会从头读到尾。我承认,技术书很难很快读完,消化是需要时间的。比如,现在在读的《Java并发编程实战》,前前后后已经读了几个月,但真正读完,估计还要有好长一段时间。

    由于去年读《三体》的原因,开始迷恋上科幻小说。于是,从去年到现在,开始找一些科幻小说来说,比如基地系列,甚至找来凡尔纳的老科幻来回味,也因此对科普作品感了兴趣,读读《上帝掷骰子吗 : 量子物理史话》、《时间的形状》,现在在读的《外星人防御计划》也是如此。曾经我很好奇,很多国外的程序设计书里,为什么常常有一些科幻的引用。读了这些书,可以更好地理解这些程序员,科幻和程序设计都是需要一定想象力的。

    对于想持续发展的程序员来说,适当的休息是不可或缺的。其实,对于我们每天把大把时间奉献给程序的人来说,读读书实际上也是一种休息,换换脑子。当然,你如果需要,这里面是有科学根据的。

    2013年时间将近过了2/3,时间还有,读书是个好习惯。最后是我的豆瓣