Playframework热情交流群里的邮件讨论

Freewind@play23月17日 10:21

昨天有感于play源代码里的一些强大功能和精巧实现,提出了以下问题,这些问题在群里和几位朋友简单讨论过,但我觉得整理一下会有用。

play的一些打破常规的做法,让我非常欣赏和赞叹。希望大家不要仅满足于使用play,在有余力的情况下,多看看play的源代码,对自己java能力的进一步提高非常有帮助。当你能读懂甚至能独立实现一个类似play的框架时,就是真牛人了:)

有一些问题我也不知道答案,我有时间也会去读,共勉。

问题列表:

  1. play的hot reload是怎么实现的
  2. 有的库不能直接在play中使用,必须写一个插件以解决classloader的问题,为什么
  3. action中向view中传参数时,可以render(a,b,c),就直接过去了,不用专门传参数名””a””,””b””,””c””,这是怎么实现的
  4. javabean不用写getter/setter,自动生成,是怎么实现的
  5. 其它类中调用javabean中的public field,但是最终将被转为getter/setter,怎么实现的
  6. play中的template用的是groovy的gsp,但增加了自己的东西,比如tag等,是怎么实现的
  7. User类继承Model后,可以使用findXxx这样的静态方法,这个方法是父类提供的,却能返回正确的子类类型,是怎么实现的
  8. Action中直接调用另一个类,不用写return,但后面的代码就不再执行了,这是怎么实现的
  9. play的session是基于cookie的,是怎么实现的
  10. play中可以支持大量客户端的ajax应用,在action里,当接收到一个请求但没有取得需要的数据时,会暂停它的执行让出线程,等到有数据时再回到线程,发给客户端,是怎么实现的
  11. public static void action(String name, String password),play能根据name和password的名字,到request中取对应的数据,是怎么实现的?默认情况下,java程序编译后,参数名都会变,比如变成action(String a,String b)
  12. play的request,response,cookie等类,都是自己定义的,为什么却可以打包为war,部署到tomcat下?那边传过来的可是j2ee里的类

green@使用中3月17日 11:07

很好的问题。谈谈自己的一些认识:

  1. play的hot reload是怎么实现的

    Play 会尝试使用Java instrument来做类的热替换,不过这个过程成功率不高,因为限制很大,包括类方法的签名不能改变
    如果java instrument替换失效,play会重启,这样会重新装载application class loader,以便获得最新的应用类字节码
    参见 play.classloading.ApplicationClassloader.detectChanges()

  2. 有的库不能直接在play中使用,必须写一个插件以解决classloader的问题,为什么?

    能举例吗?

  3. action中向view中传参数时,可以render(a,b,c),就直接过去了,不用专门传参数名””a””,””b””,””c””,这是怎么实现的?

    play使用本地变量名增强器对应用类进行增强,这个过程会获得参数名字信息并保存到线程变量(ThreadLocal)当中。
    同时控制器的render方法会尝试获得线程变量中的变量名字,将其放入renderArgs
    参见 play.classloading.enhancers.LocalvariablesNamesEnhancer 和 play.mvc.Controller.render()

  4. javabean不用写getter/setter,自动生成,是怎么实现的

    参见 play.classloading.enhancers.PropertiesEnhancer
    另外不是不用写getter/setter,而是当你没有提供getter/setter方法的时候,play帮你提供一个默认的

  5. 其它类中调用javabean中的public field,但是最终将被转为getter/setter,怎么实现的

    参见 play.classloading.enhancers.PropertiesEnhancer

  6. play中的template用的是groovy的gsp,但增加了自己的东西,比如tag等,是怎么实现的?

    tag的实现有两种,一种是定义tag模板(/play/framework/templates),另一种是定义Java类/play/framework/src/play/templates/(FastTags.java|GroovyInlineTags.java))。
    调用机制在play.templates.GroovyTemplateCompiler.startTag()可以找到

  7. User类继承Model后,可以使用findXxx这样的静态方法,这个方法是父类提供的,却能返回正确的子类类型,是怎么实现的?

    这是通过Java Generic机制实现的,如下代码所示:

    :::java

    public static `<T extends Model>` T findById(Object id) {
        throw new UnsupportedOperationException(
                ""Embedded entity does not support this method"");
    }

其实Java编译器无法判断具体返回类型,只能说是Model的子类。这个机制也有缺陷,导致下面代码编译出错:

:::java
return (id == null) ? null : MyModel.findById(id);

你只能用一下代码替换:

:::java
if (null == id) return null;
else return MyModel.findById(id);

另外在Model的各种find方法中很多都会抛出UnsupportedOperationException异常,之所以运行的时候没有抛出异常,也是因为通过增强器进行了代码注入。

  1. Action中直接调用另一个类,不用写return,但后面的代码就不再执行了,这是怎么实现的?
    Controller的各种render方法最后都会抛出play.mvc.results.Result类型的异常
  2. play的session是基于cookie的,是怎么实现的?
    很简单,在回应请求的时候play的session对象会被写入cookie,而读取请求的时候会从cookie生成session对象
  3. play中可以支持大量客户端的ajax应用,在action里,当接收到一个请求但没有取得需要的数据时,会暂停它的执行让出线程,等到有数据时再回到线程,发给客户端,是怎么实现的?
    通过await()调用实现的。具体过程比较复杂,如果愿意探究,需要仔细研究Controller和ActionInvoker代码
  4. public static void action(String name, String password),play能根据name和password的名字,到request中取对应的数据,是怎么实现的?默认情况下,java程序编译后,参数名都会变,比如变成action(String a,String b)
    与问题3同理,不过使用LocalVariablesNamesTracer的换成了ActionInvoker,这个过程不仅仅是变量名传递,同时还有很复杂的变量类型绑定。具体参见ActionInvoker.getActionMethodArgs的代码
  5. play的request,response,cookie等类,都是自己定义的,为什么却可以打包为war,部署到tomcat下?那边传过来的可是j2ee里的类
    不熟悉这方面的,大概是play.server.ServletWrapper做转换工作,参见play.server.ServletWrapper.service方法。

Freewind@play23月17日 11:27

  1. 比如使用Ebean,如果不写一个插件来处理classloader的问题,在play中无法使用。

    Play在dev模式下,编译java源代码后,生成的字节码是放在内存中的,而不是以.class文件形式保存在硬盘上。这给其它一些库带来了麻烦。
    比如在Ebean的config中,需要传入Model类,Ebean会设法导入对应的.class文件。此时必须写一个插件,让Ebean能从play的classloader中找那些类。不然找不到会报错。

赞赏留名,相识相惜 ~