scala雾中风景(12): App特质的延迟初始化

一个同事的问题,下面的代码运行时引用的外部变量TestClosure.count没有初始化:

object TestClosure extends App {
    val words = Array("a","ab","abc")
    val count = 10

    val cnt = words.map{word => (word, count)}
    cnt.foreach(println)
}

object TestRef extends App {
    //对应上面map里面那个匿名函数
    val c = Class.forName("TestClosure$$anonfun$1") 
    val meod = c.getDeclaredMethod("apply", classOf[String])
    val res = meod.invoke(c.newInstance(), "zhang")

    // (zhang,0) 并不是 (zhang,10),说明外层object的count并没有被赋值
    println(res) 
}

如果运行 TestClosure 是ok的:

$ scala TestClosure
(a,10)
(ab,10)
(abc,10)

但是运行 TestRef 时,发现引用的TestClosure里的count变量没有被赋值:

$ scala TestRef
(zhang,0)

这个问题咋一看以为是闭包上下文绑定问题,实际上与闭包无关,是因为继承了App特质导致的,看一下App特质:

trait App extends DelayedInit

DelayedInit特质里定义了延迟初始化方法:

def delayedInit(x: => Unit): Unit

scala在运行时同java,由一个包含main方法的单例作为入口,大概是2.8的时候为了简便,设计了App特质,由App提供main方法,用户可以直接在初始化块里写逻辑,然后编译器会把这段初始化代码块里的逻辑封装成一个函数对象缓存起来(并没有运行),只有在执行到main方法的时候才会触发。

通过一个简单的例子看一下,首先看没有继承App特质的情况:

object Foo { 
    val count = 10
    println(count)
}

上面的代码,在翻译为java时,成员的赋值,以及println都是在构造函数或构造块里执行的

class Foo$ {
    private final int count;

    private Foo$(){
        count = 10;
        println(count);
    }
    // 忽略getter等其他不相关内容
}

再看看 Foo继承自 App 之后:

object Foo extends App { 
    val count = 10
    println(count)
}

翻译成java代码时,构造函数里相当于:

class Foo$ implements App {
    private final int count;

    private Foo$(){
        // 逻辑被封装起来,延迟到main方法时才执行
        delayedInit( anonymousFunction{count = 10; println(count)});
    }
}

逻辑并没有被执行,而是封装在initCode这个Buffer里:

/** The init hook. This saves all initialization code for execution within `main`.
*  This method is normally never called directly from user code.
*  Instead it is called as compiler-generated code for those classes and objects
*  (but not traits) that inherit from the `DelayedInit` trait and that do not
*  themselves define a `delayedInit` method.
*  @param body the initialization code to be stored for later execution
*/
override def delayedInit(body: => Unit) {
    initCode += (() => body)
}

只有main方法执行时,才会触发这些逻辑,见App.main

def main(args: Array[String]) = {
    ...
    for (proc <- initCode) proc()
    ...
}

所以原因就在TestClosure这个单例继承了App导致内部的逻辑延迟初始化,取消继承App就正常了。

3.29号的技术沙龙

上周六在北京主持了一场阿里技术沙龙,主题是“中间件与后端技术”,邀请了Redhat的张建锋和搜狐的秦启东分别分享了《Wildfly的新特性以及规划》,《搜狐新闻客户端的push技术》以及中间件团队的封仲淹分享了《JStorm》

相对于Wildfly,我更关注它所用的Undertow的情况,从这里看到一些对比,在纯文本和JSON情况下undertow的性能在netty之上(三轮里有两轮),尽管对这个测试持有一些怀疑,但了解到undertow是jboss团队里核心程序员DavidStuart Douglas在主导的,也是XNIO框架的作者。分享结束后,跟jboss和weblogic的几个开发聊了聊,我们与他们关注的领域有一些差别,主要在解决自己的痛处,偏向运维层面。

搜狐客户端的后台长连接是基于Go语言实现的,最初他们基于MQTT协议实现的Java(Netty)版发现GC引起的抖动没能很好的解决,后来切换到了Go,改写了MQTT,基于goroutine机制实现单机百万的长连接。这与之前淘宝实现web旺旺时的经历有些类似,web旺旺的长连接也是经历了jetty,netty,erlang的过程,也是因为jvm full gc的抖动导致改用了erlang(基于erlang的长连接确实很稳定,但也带来了cpu利用率较高以及不好找人维护的问题)。对GC的调优是门艺术,设置的好的话,基于jvm实现的长连接也是可以达到百万级的。

封仲淹/纪君祥的JStorm分享也有很多干货,他们相当于用java重写了storm,增加了一些特性并做了优化。细节可以看他的ppt,当前内部很多日志处理都是用jstorm在做的。

之前也和邓草原沟通过,希望能交流一下他在豌豆荚怎么用scala构建后端服务的,不过时间上不凑巧,希望下次有机会再切磋。老邓是国内erlang/scala圈子里超级大牛,最早也是用他开发的netbeans插件写scala的。

这些资料可以在: http://club.alibabatech.org/resources.htm 获取,稍后组织方应该会把ppt放上去。也可以关注阿里技术嘉年华的微博

// 补充
建锋写了一些《Undertow服务器基础分析》的博客,很值得参考: 1.概述 2.XNIO 3.Undertow