Douyu vs Play!

  1. 云栖社区>
  2. 博客>
  3. 正文

Douyu vs Play!

沉默术士 2017-05-02 14:50:00 浏览1092 评论0

摘要: Douyu vs Play! 1.比较的版本: Douyu : 0.1,暂时没有对外开放源代码,仅用于学习、交流目的。 Play : 1.0,稳定版、已对外开放源代码,能用于生产环境。 2.项目参与人数: Douyu : 1个。

Douyu vs Play!

1.比较的版本:

Douyu : 0.1,暂时没有对外开放源代码,仅用于学习、交流目的。
Play : 1.0,稳定版、已对外开放源代码,能用于生产环境。

2.项目参与人数:

Douyu : 1个。
Play : 至少大于1个。

3.项目开发时间:

Douyu :
2008年12月25号到2009年3月是萌芽期:先是有做个MVC框架的念头,浪费3个月时间折腾Servlet/JSP。
从2009年4月开始推翻Servlet/JSP从头来过,到2009年11月16号发布0.1版的Douyu,
算下来真正用于开发Douyu的时间不超过7个月。

Play :
http://groups.google.com/group/play-framework中有讨论记录的时间最早是2007年5月13日
http://download.playframework.org/releases/中发布的第一个play-1.0-stable1.zip是2008年2月2号,
如果play也有0.1版的话,保守估计下来到今天至少也开发了2年半时间

4.源代码、大小、及第三方jar:

Douyu : 目前不依赖任何第三方jar,
Douyu的源代码的总行数是11万行左右(这里必须包括javac编译器,因为我修改了javac编译器),
共有400多个java源文件,其中javac编译器占了260,douyu自身占了140左右,
发布包解压后1.4M。

Play :目前默认提供的第三方jar超过50个,
在Playframeworksrc目录中给出的源代码总行数是25000行左右,共有180多个java源文件,
发布包解压后84M左右。

5.社区:

Douyu : 没有官方网站,没有讨论组
Play : 有官方网站,也有讨论组

接下来详细比较Douyu与Play!的实现细节:

6.控制器层:有必要RESTFul吗?有必要多加个routes配置文件吗?

我的问答是没有必要。

比如最简单的Hello(这个例子不是用来比较代码量大小的,只是用来讲解实现原理)
Douyu版:

  1. @Controller
  2. public class Hello {
  3. public void index(PrintWriter out, String name) {
  4. out.println(“Hello “+name);
  5. }
  6. }


Play版:

public class Hello extends Controller {

public static void index(String name) { render(name);; } }

//对应的view层html省略了。。。


Douyu使用http://localhost:8000/Hello?name=Douyu这样的请求uri就可以直接访问了,
我为什么还要去配个routes呢?
Douyu的内部实现也很简单明了,动态编译Hello类,分析Action方法的参数个数和类型,
为每一个Controller动态生成一个包装器,包装器简化后的代码如下:

public class HelloControllerWrapper implements ControllerWrapper {

private Hello controller = new Hello(); public void executeAction(String action, HttpRequest request, HttpResponse response) { if (action.equals(“index”)) controller.index(response.getWriter(), request.getParameter(“name”)); else response.sendError(404); } }

除了请求参数自动绑定之外,
在ControllerWrapper中还能做很多事:比如权限检查,日志记录,参数校验等等,
用户想做什么事,取决于在@Controller中设置了什么参数。
Douyu已经将请求uri直接映射到具体的Action方法名了,
完全不需要routes配置文件。
如果觉得Controller/Action这样的uri太长了,也是可以在Douyu的服务器配置文件中指定一个别名的。

而Play是怎么实现的呢?
先在routes配置文件中事先把uri跟Action方法名对应起来:

  1. GET /Hello/{name} Hello.index

然后根据请求uri到routes中查找出是哪一个Controller的那一个Aciton,
如果Controller从没有编译过或被修改过了,那么用eclipse jdt编译这个Controller,
eclipse jdt并不生成class文件,而是把编译后的字节码暂时放在内存中(一个byte数组),
接着用javassist分析字节码,找出所有的Action,再找出Action方法定义了哪些参数,
再把参数名称构造成一个字符串数组,通常每一个Action都会对应一个参数名称字符串数组,
最后javassist把这些字符串数组转化为字节码后再写入原先由eclipse jdt生成的字节码中,
至此动态编译就完成了。

紧接着用ClassLoader把上面得到的字节码装载进来就可以得到一个与Controller相关Class实例,
因为Play的Action方法都是public static,所以不用生成对应的Controller实例,
而是用反射来查找对应Action方法名的java.lang.reflect.Method,
找到Method后再用反射来查找与这个Method相关的参数名称字符串数组,
得到一个java.lang.reflect.Field,
把这个Field的值取出来后,按照参数名称的类型和先后顺序自动绑定到请求参数值,
最后生成Method所需的实际参数值,调用这个Method。

(备注:Play除了应用javassist在字节码中插入参数名称字符串数组外,也还有其他用途)

可以对比一下,
完成一次动态编译,Douyu只需借助Javac编译器,
而Play需要用到eclipse jdt编译器与javassist,
Douyu为每个Controller生成一个包装器,由包装器通过字符串比较来确定该调用哪个Action,
Play不生成包装器,而是在原有Controller的字节码中插入额外的字节码来记录与参数名称相关的信息,
最后运用两次反射来调用Action。

很明显,生成一个包装器不管从设计的灵活性和调试的方便性上都优于在Controller中添加额外字节码的方式,
字符串比较在性能上也比反射要快,
唯一一个缺点是,生成包装器多了一个额外的类,增加了硬盘空间,
但是添加额外字节码也同样增加硬盘空间,包装器通常很小,所以这个缺点也不算是缺点。

Play的routes配置文件还有这样的功能:

  1. GET /public/ staticDir:public

这种处理静态资源的问题本身就是Http服务器的事,根本就没必要放到应用层。

Play抄袭Rails最失败的一点就是每个控制器必须继承play.mvc.Controller,
采用继承方式远没有采用@Controller灵活,@Controller本身就相当于一个配置参数的容器,
@Controller中的配置参数可以任意扩展,但对控制器本身毫无影响,
影响的只是ControllerWrapper的代码,而ControllerWrapper的代码是编译器动态生成的,
用户根本不用关心。

总体来说,Douyu在控制器层和动态编译这两方面做得比Play更好。

回复这两个问题


5.REST
因为ZHH没有实践过Play,其实Play的REST用一些pattern后会很简单的.
Play的REST做的是非常方便而且简单的,而Douyu没有这个功能.
在Play中REST只是作为route功能的一个子集,route功能还可以做其他的用处.

对技术的理解不一定非要实践,只要有过项目经验,
要是你理解了Play的源代码实现过程了实不实践都是小事,
REST、routes配置文件没必要用

1.动态编译
动态编译除了在开发模式下能够修改java文件而不重启服务器以外还有其他用处吗?(非常希望ZHH或者其他大侠能给我答案.)
从这一点出发,我不关心Play和Douyu的动态编译技术谁牛逼.
我认为Play在动态编译上已经做的很好,无论你修改Entity,Controller或者view,都无需重启服务器.

臆测:ZHH一直强调动态编译,我认为一部分原因是动态编译所用的技术比较深,值得炫耀.


如果严格来讲,Play算不上是动态编译,在Play的整个实现中javassist的作用远大于eclipse jdt,
而javassist的功能只是eclipse jdt的子集,javassist能做的eclipse jdt都能做,
之所以出现两者并存的情况只是因为Play是个胶水框架,
也许是Play的开发人员不想修改或没能力修改eclipse jdt。

当然你完全可以不用eclipse jdt,
光是使用标准库的javax.tools包然后再自己写个ClassLoder你就可以马上达到Play开发人员的水平,
动态编译不仅仅是编译源代码生成字节码就了事,不重启服务器只是个很不起眼的小功能,
当然,如果你只是个普通用户,只是为了不用重启服务器,那么你没必要了解动态编译的好处。

动态编译强调的是一个过程而不是最后的编译结果,在编译的过程中必须提炼出有用的信息,
然后根据这些信息再生成其他一些辅助类,
上面所讲的用Javac生成ControllerWrapper就是动态编译的一个使用案例,当然还有更多好处。

7.Action方法参数解析

Douyu是采用Javac编译器的类型系统来实现Action方法参数解析的,
Action方法参数解析不仅仅用来实现请求参数自动绑定,
还有一点IoC的味道,你可以把Context、PrintWriter、HttpRequest等等作为参数,
Douyu都能正确识别。

而Play仅有一个简单的Binder

8.缓存

Play只是使用第三方缓存(Memcached、EhCache),仅做了一点简单的包装工作
Douyu没有缓存,如果要使用第三方缓存也是件简单的事,包装工作开发人员自己都能完成,
所以目前两者在缓存这一点上没有哪个更优也没有哪个更差。

9.ORM

关于ORM的一些基本想法在下面这个小节中已经详细说明了,我想要说的都在里面说了,
2.9 ORM能达到多大程度的自动化?
http://www.iteye.com/topic/517796?page=1#1246290

到底是先有数据库表还是先有Entity这里又重复了一次
http://www.iteye.com/topic/517796?page=22#1249015

另外,装个MySQL比用内存数据库复杂不到哪去,
在Windows上装个MySQL花不了你5分钟。

b.1.Entity中有一些属性和方法不依赖数据库.这种情况从数据库生成Entity就很麻烦,生成了以后要手动去添加这些不依赖数据库的东西

JPA叫Entity,而Douyu喜欢用”模型类”这个词,
模型类忠实于数据库表,数据库表中有什么字段,模型类中就有什么字段,
这完全是一种设计上的问题,就好比充血模型与贫血模型的区别。

而Play的Entity更像是个四不像,一边学ActiveRecord继承一个play.db.jpa.Model
另一边又用JPA在Entity中列出所有的字段,
然后再加一大堆的@Required、@MaxSize、@ManyToOne、@OneToMany。。。
何苦这么麻烦呢,你用SQL建两个表再加上主键、外键其他什么事你都不用管了。

如果你使用了Douyu的ORM,那么模型类都已经自动化,也没必要往模型类添加任何东西。
solonote 写道

b.2.从数据库生成表依赖于特定的数据库,如果我换一个数据我必须要重新建表.

首先不谈换数据库的可能性有多大,
我就想问你JDBC的强项是什么?JDBC的强项是从已有的表中提出元数据,
而不是从Entity中提取元数据放入数据库,
到底是数据库复杂还是Java语言复杂,你用一个Entity就能描述所有数据库的专有特性,
Hibernate能保证它生成的SQL建表语句能让DBA满意?

再次引入我说过的话:

另外这种先设计模型类再生成关系表的设计思想也不能充分利用关系数据库的专有特性,
比如在Oracle中我想建个表,这个表必须放到不同的表空间中,
还需要设置不同的存储参数以及块空间管理参数。
这些事通常是DBA擅长的,开发人员没有精力、也没有时间去学习不同数据库的专有特性。

Play在ORM这个相当繁琐的领域无所作为,而是直接依赖JPA,
我只能说Guillaume Bort没有勇气去颠覆传统。

我现在非常希望Sun/Oracle在JDBC规范上面再多下功夫,
比如加入Database Change Notification特性,
这两天我就在Oracle11g上面尝试了Database Change Notification特性,
使用:
oracle.jdbc.dcn.DatabaseChangeEvent;
oracle.jdbc.dcn.DatabaseChangeListener;
oracle.jdbc.dcn.DatabaseChangeRegistration;
这个特性很酷,只要注册一个DatabaseChangeListener,就可以收到数库库服务器发来的各种通知,
包括字段或记录的增、删、改,表被删除等等,对动态编译模型类很有用,
对实现ORM对象缓存也非常有用(特别是那种多个应用项目共享同一个数据库的情况)。

只有JDBC规范越来越细化、减少更多歧义,ORM的自动化才能100%实现,
不要再从Hibernate身上搞什么花样了,有些东西该抛弃的就要舍得抛弃。

10.视图

Play借助Groovy实现了一套模板标记,从功能的完备程度上甚至还比不上freemarker,
当然Play的功能比Douyu肯定要多了,如果你想在Douyu中使用freemarker也是超级简单的,
不需要对Douyu做任何修改就能整合freemarker,我可以马上给出个样例:
先写个ftl文件:

  1. <html>
  2. <head>
  3. <title>Welcome!</title>
  4. </head>
  5. <body>
  6. <h1>Welcome ${user}!</h1>
  7. <p>Our latest product:
  8. <a href=”${latestProduct.url}”>${latestProduct.name}</a>!
  9. </body>
  10. </html>


再写个Controller

import java.util.*;

import freemarker.template.*;
3.4. import java.io.PrintWriter;
import com.douyu.main.Controller;
6.7. @Controller
public class Test { public void index(PrintWriter out) { Configuration cfg = new Configuration(); Template temp = cfg.getTemplate(“test.ftl”); Map root = new HashMap(); root.put(“user”, “Big Joe”); Map latest = new HashMap(); root.put(“latestProduct”, latest); latest.put(“url”, “products/greenmouse.html”); latest.put(“name”, “green mouse”); temp.process(root, out); out.flush(); } }

关于视图层这一方面有很多难题需要解决的,
如果又是老套的${var}、${object.field}、if、list、include(或extends)。。。
那实在是太没有创意了,
不管是Play还是freemarker,实现原理本质上都差不多,
Douyu就目前发布的0.1版来说也是${var}、${object.field}、if…,
我认为这些都不是我想要的,我一直在想有没有一种更好的办法,
如何让美工与程序员合理的分工协作?我目前也没有完美的方案,
不过也可以说说我现在的初步设想:

第1种方案:类似于Delphi、VB那样有一个表单设计器,能够拖拉控件,
Douyu的ORM在自动生成模型类之后,美工可以把表单中的控件关联到模型类的相应字段,
与此同时,程序员负责编写Controller,然后美工可以把控件的事件关联到Controller的相关Action,
最后生成模板文件。

第2种方案:可以运用AJAX处理所有客户端问题,用JSON传送数据。

第3种方案:这种方案更绝,完全没有模板文件,也不依赖于浏览器,直接使用JavaFX,
Controller的Action只负责序列化Java对像,然后通过Http传到JavaFX客户端,
JavaFX客户端通过javafx.io.http.HttpRequest接收响应数据,然后反序列化Java对像,
原理基本上与AJAX相同,只不过摆脱了浏览器和HTML。

回复这两个问题


3.控制器
Douyu渲染一个试图参数是:


  1. context.out(“WhatTime.html”, paramMap);


而Play的参数是:

render(param1,param2…);


Play的优势:
a.play不需要指明去渲染哪一个html,它根据方法名,Controller类去判定,Play定义的是一个规则.这种类似的规则在Play中还有很多,而且Play有一个特点,如果你不需要这种规则时,你往往可以去自定义.
而Douyu使用的方式是只能自定义.
b.play传给页面的参数不需要组织成一个map,更加自然.


因为Play是抄袭Rails,所以有一大堆的默认约定(比如controllers、models、views等),
Douyu没有那么多啰嗦的约定,也不需要controllers、models、views目录,
你只要把合适的类放到合适的package就行了,

一个方法名默认一个同名的html这本身就是一件很天真的事,
比如最常见的一个Form对应一个表,通常这个Form在添加、修改、查看时界面都是一样的,
只是按钮有可能不同,Controller中可能就有insert、update、display三个Action,
要是你也去分开三个html: insert.html、update.html、display.html,
那么我只能说你没事找事。

当然了,要实现一个方法名对应一个html这本身就是件简单的事。

Douyu现在的版本也不需要非得组织成一个map,只是你帖子都没细看,
@Form、数据库模型类的实例都可以传,在VIEW层连权限问题都有实现了。


4.视图
Play的页面模版有一套非常简单易用的tag机制,复用view非常的方便.Douyu当前的版本介绍里并没有提到如何复用view.

Douyu现在没有实现tag机制。

11.测试(Testing)


6.Testing
Play可以非常方便的做Unit Test以及FunctionalTest.
Play在Test上下了很多功夫,这一点大家实践一下就可以知道.
Play可以方便的组织测试数据,而这些数据是一个文本结构,不依赖于特定数据库.
也就是说即使你用的是一个内存数据库,你也可以很方便的组织测试数据.

什么才是真正的自动化测试?
真正的自动化测试不需要你去写测试用例。

  1. int compare(int frist, int second) {
  2. if(first > second) return 1;
  3. else if(first == second) return 0;
  4. else return -1;
  5. }

这个例子很简单,就是比较两个数的大小,
第一个数比第二个数大时返回1,
两数相等返回0,
第一个数比第二个数小时返回-1,

真正的自动化测试是把这些事交给编译器去做的,
比如编译器在分析上面的代码时,通常会采用一种数据流分析技术(这种技术也常用于分析变量在使用前是否正确赋值),
先确定方法参数的类型,然后分析代码中出现的各种分枝(if、while、另一个方法调用等等),
接着跟踪方法参数在分枝中的流转过程,并把流转路径信息记录下来,
然后对照方法参数的类型生成一些模拟数据,而一条路径就可以生成一个测试方法,
比如上面的代码很明显有三条路径,最后会生成下面的测试代码:



Java代码收藏代码收藏代码

  1. public class SimpleTest {
  2. public void run() {
  3. int result1 = compare(1,0);
  4. assertEquals(result1, 1);
  5. int result2 = compare(1,1);
  6. assertEquals(result2, 0);
  7. int result3 = compare(1,2);
  8. assertEquals(result3, -1);
  9. }
  10. }

当然,自动化测试的复杂性远比上面的这个例子要难上无数倍,
虽然有难度,但是这个领域也是值得研究的。

我认为现在的测试工具,像Junit、testNG这些都只是小工具,称不上什么自动化测试,
你只不过是用这些工具把你的代码中的实现流程重新写一次罢了。

Play也是用这些工具来实现测试的,也没有什么大的突破,只是简单封装了一下Junit。

Douyu如果要整合Junit、testNG这些小工具也是相当容易的,
但是这种整合还是离我的要求相差太远了。

7.关于第三方类库依赖以及大小
Douyu依赖的开源库很少,Play依赖了很多开源库,比如Hibernate等等.Douyu很小,Play下载下来需要90mb(其中还包含了python的解释器).
但我认为,作为一个非桌面应用程序来说大小和第三方类库的依赖完全不是评价一个framework的关键.

定位不同,Douyu的目标是基础平台,你甚至可以在Douyu中直接使用Hibernate、Ibatis。

8.J2ee体系的支持
Play的应用程序发布以后会打包成一个war包,值得一提的是这个war包还是可以轻松的修改代码的.而且基本的程序结构也没发生什么变化.Play的war包可以运行在标准的J2ee容器中.
Douyu不支持原来的J2ee体系,只能运行在自己的服务器中.我不谈Douyu服务器和其他J2ee服务器的优劣.但是有一点
当前没有Douyu的Hosting提供商,你想用Douyu必须自己有一个服务器.而开发一个Play网站,仅仅租用一个tomcat空间就可以发布.
这会让Douyu陷入无人使用的境地,如果陷入这种境地,那么它就全无价值了.

Douyu生成的类都是些很普通的Java类,打包后仅仅是个jar文件,
我现在发现只要写个拦截器在web.xml文件中配置一下就能运行这个打包后的jar,

Douyu也不一定就得支持J2ee体系,真要是强大了,颠覆J2ee体系也不是不可能。
Play没法脱离J2ee体系是因为Play用到了JPA。

9.其他的一些支持
Play对GAE也有支持,它还有很多的功能,比如Play的在运行出错时的报告非常的直观,你可以自己去了解和实践.

这些都是小功能,只是需要时间完善,Douyu目前在细节方面做得不好。

最后总结

Douyu与Play的设计目标是完全不同的,
我把Play看成是一种胶水框架,是因为Play是横向扩展的,需要什么就引进第三方库来实现,
而Douyu是紧紧围绕编译器作文章的,是纵向扩展,尽量挖掘编译器未被发现的潜力。

Douyu也非常容易形成一套规范,
Douyu向普通开发人员开放的都是些接口API或者是一些简单的工具类,
接口与实现分开,就像Servlet一样,一套接口API本身就可以看成是一套规范,
围绕编译器实现一套规范是一种另类的尝试,我在开发Douyu的过程中越来越觉得有趣。

本文来源于"阿里中间件团队播客",原文发表时间" 2011-07-06 "

【云栖快讯】阿里巴巴小程序繁星计划,20亿补贴第一弹云应用立即开通购买,限量从速!  详情请点击

网友评论