这篇帖子的内容我本来想发到 http://www.iteye.com/topic/806660这里的主贴里去的,想挽回被隐藏的命运,但我写完本贴的内容,却发现为时已晚。好吧,我承认,上一个贴的标题容易引发口水,这次我们实事求是,从代码出发,通过一个小例子较完整的介绍play!framework的开发过程:
就拿play!framwork自带的房间预订(booking)的例子吧:
1、 下载play 解压,配置环境变量
2、 打开命令行:转到合适的目录,输入Play new booking 这样,项目即生成完毕。
3、 进入项目目录中,执行play eclipsify 或者play netbeansify 这样即可将生成的项目导入到eclipse或者netbeans中。
打开项目目录我们可以看到:
解释下各个目录:
- app 包含所有的model,controller以及view(模板)。
- conf下是一个application.conf 配置文件。
- lib是Play依赖的第三方jar。
- logs是日志
- public下包含你引用的js,css以及,images等。
- test下所有的测试文件在此。
这样的一个目录显然与传统的JEE目录完全不一样,事实上,它已经摒弃了servlet,jsp那些东西,而完全自己实现了HTTP,您会问,那它是不是无法正常运行于标准的servlet容器中,请不要担心,我们在开发完成后可以使用命令play war –odir 这个命令生成可以正常运行于servlet容器中的项目目录。
Play分为开发模式和生产模式两种,而切换的配置在application.conf中:
Application.mode=dev 生产模式请改为:prod
主要区别在于开发模式中您无需重启server,每次请求都会查看是否有文件发生改变,改变即编译,这对于传统Java EE开发人员无疑是相当敏捷的。而这种方式同样会导致性能下降,所以生产模式中就不会这样了,而是采用预编译机制。
下面开始coding:
按照OO的开发模式 首先编写模型层:
在app包下新建类Hotel.java 继承Model ,如下:
@Entity public class Hotel extends Model { @Required public String name; public String address; public String city; …..省略部分字段 @Column(precision=6, scale=2) public BigDecimal price; public String toString() { return "Hotel(" + name + "," + address + "," + city + "," + zip + ")"; } }
其中继承Model基类实现了一个富血的Domain Model(这不是比传统的PO更加OO ?),完全基于JPA,上手非常简单
同样Booking类:
@Entity public class Booking extends Model { @Required @ManyToOne public User user; @Required @ManyToOne public Hotel hotel; @Required @Temporal(TemporalType.DATE) public Date checkinDate; @Required @Temporal(TemporalType.DATE) public Date checkoutDate; @Required(message="Credit card number is required") @Match(value="^\\d{16}$", message="Credit card number must be numeric and 16 digits long") public String creditCard; @Required(message="Credit card name is required") public String creditCardName; public int creditCardExpiryMonth; public int creditCardExpiryYear; public boolean smoking; public int beds; public Booking(Hotel hotel, User user) { this.hotel = hotel; this.user = user; } public BigDecimal getTotal() { return hotel.price.multiply( new BigDecimal( getNights() ) ); } public int getNights() { return (int) ( checkoutDate.getTime() - checkinDate.getTime() ) / 1000 / 60 / 60 / 24; } public String getDescription() { DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); return hotel==null ? null : hotel.name + ", " + df.format( checkinDate ) + " to " + df.format( checkoutDate ); } public String toString() { return "Booking(" + user + ","+ hotel + ")"; } }
User类 类似,不再给出。
编写Controller 在controller包中编写类:Hotels 继承Controller(这儿Application继承Controller)
public class Hotels extends Application { @Before//拦截器 static void checkUser() { if(connected() == null) { flash.error("Please log in first"); Application.index(); } } public static void index() { List<Booking> bookings = Booking.find("byUser", connected()).fetch();//这句是不是更加面向对象? render(bookings); } public static void list(String search, Integer size, Integer page) { List<Hotel> hotels = null; page = page != null ? page : 1; if(search.trim().length() == 0) { //分页的代码是不是很简单?链式调用更加方便 hotels = Hotel.all().fetch(page, size); } else { search = search.toLowerCase(); hotels = Hotel.find("lower(name) like ? OR lower(city) like ?", "%"+search+"%", "%"+search+"%").fetch(page, size); } render(hotels, search, size, page); } public static void book(Long id) { Hotel hotel = Hotel.findById(id); render(hotel); } public static void confirmBooking(Long id, Booking booking) { Hotel hotel = Hotel.findById(id); booking.hotel = hotel; booking.user = connected(); validation.valid(booking); // Errors or revise if(validation.hasErrors() || params.get("revise") != null) { render("@book", hotel, booking); } // Confirm if(params.get("confirm") != null) { booking.save(); flash.success("Thank you, %s, your confimation number for %s is %s", connected().name, hotel.name, booking.id); index(); } // Display booking render(hotel, booking); } public static void saveSettings(String password, String verifyPassword) { User connected = connected(); connected.password = password; validation.valid(connected); validation.required(verifyPassword); validation.equals(verifyPassword, password).message("Your password doesn't match"); if(validation.hasErrors()) { render("@settings", connected, verifyPassword); } connected.save(); flash.success("Password updated"); index(); } }
上面的代码中展示了
1.@before 这个注解基本就是个拦截器的意思,[email protected]??法。
2、controller中的几个作用域:
- 1、session这儿的session只支持您放里面放String类型,而不是和传统JEE中任何对象都可以放到session中。这儿的session和rails的类似。
- 2、flash 跨请求的存储对象
- 3、params 基本相当于request.getParameters();
- 4、renderArgs 渲染到模板的数据,上面代码中您看到的render里面的就是放到了这个renderArgs里面了。还有个validation存放验证数据。
基类Controller里定义了很多好用的方法:如果您想使用ajax返回JSON,则使用renderJSON() play使用的json序列化工具是gson.jar,您想返回一个文件流,使用renderBinary(File f,String name)方法。
上面没有展示文件上传的代码:我再贴一个文件上传的代码:
public static void save(Picture picture,File pic){ File uploadFile=new File(Play.applicationPath.getAbsoluteFile()+”/public/uploads”); play.libs.Files.copy(pic,uploadFile); picture.url =path; picture.save(); QZ_Admin.pictures();}
其它的Controller不再给出
这儿会有同学问,我没有配置和URL映射规则啊。事实上,play借鉴rails默认大于配置的思想,默认的映射规则是/Controller/method?params 这种。当然您也可以在配置文件routes中重新设定您所需要的映射规则。同时模板的位置默认也和Controller的名字有很大关系,比如这人我们Controller的名字叫Hotels 方法名是Index 那如果您不指定渲染模板的话默认play会去views 下面的Hotels文件夹下找index.html模板。这种约定是不是限制了很多东西?会不会对开发造成一些影响,我个人认为是有的,由于和Controller,method的名字关系密切,这需要你良好的规划,以保证你的项目目录的合理,以及URL的优雅。
最后是编写模板:
在views 下面建立文件夹 Hotels 新建文件index.html
#{extends 'main.html' /}///在views文件夹下面编写main.html一般为网站所有页面的公共部分,比如header和footer #{set title:'Search' /}//为每一个页面设置title 在Main.html有变量title <table> <thead> <tr> <th>Name</th> <th>Address</th> <th>City, State</th> <th>Check in</th> <th>Check out</th> <th>Confirmation number</th> <th>Action</th> </tr> </thead> <tbody> #{list bookings, as:'booking'} //遍历 <tr> <td>${booking.hotel.name}</td> <td>${booking.hotel.address}</td> <td>${booking.hotel.city},${booking.hotel.state}, ${booking.hotel.country}</td> <td>${booking.checkinDate.format('yyyy-MM-dd')}</td> <td>${booking.checkoutDate.format('yyyy-MM-dd')}</td> <td>${booking.id}</td> <td> #{a @cancelBooking(booking.id)}Cancel#{/a} </td> </tr> #{/list} </tbody> </table>
这样,一个简单的模板页面就编写完成了。Play的模板相较于jsp或者JSTL以及struts2标签啥的都更加简单,也没有freemarker 空指针异常(可能有童鞋喜欢这个)这些问题。具体其它的用法可以参看play的帮助文档。
以上基本上就把play的大体用法说完了,现在我再写下play其它让人心动的地方:
1、 缓存支持:
2、 public static void showProduct(String id) {3、 Product product = Cache.get("product_" + id, Product.class);4、 if(product == null) {5、 product = Product.findById(id);6、 Cache.set("product_" + id, product, "30mn");7、 }8、 render(product);9、 }
而您可以使用EhCache或者Memcached作为缓存的实现。使用起来非常方便
2、 JOB支持:
3、 @Every("1h")4、 public class Bootstrap extends Job {5、 6、 public void doJob() {7、 List<User> newUsers = User.find("newAccount = true").fetch();8、 for(User user : newUsers) {9、 Notifier.sayWelcome(user);10、 }11、 }12、 13、 }
这段代码即会每1小时运行一次。
3、 Email支持:
4、 Template t =TemplateLoader.load("UserCenter/mailTemplate.html");//邮件模板 5、 Scope.RenderArgs templateBinding = Scope.RenderArgs.current(); 6、 templateBinding.put("url","http;//url")); 7、 String result =t.render(templateBinding.data); 8、 Mail.send("[email protected]", "[email protected]", "",result,"text/html")
以上即是使用模板发送邮件的例子。当然您需要在application.conf指定发邮件的一些参数
4、非常多的好用的module:比如支持lucene的search-module ,MongoDB module,GAE module,Excel Module,GWT Module,PDF Module等等。
以上大体介绍了play!framework的开发示例以及一些基本特点。大家可以讨论下你对Play!framework看法,但请勿鄙视一把走人,或者发表带有人身攻击的言论,谢谢!
39 楼 yuan 2010-11-10
downpour 写道
你在让别人睁大眼睛看世界之前,先要证明你所谓的充血模型的正确性。不要一个外国人放个屁就当成圣旨了。
我觉得很简单,贫血模型把数据和行为分离的做法跟OO是相违背的。如果要说OO是放屁……我还没到讨论这个话题的水平,溜。
40 楼 mathgl 2010-11-10
night_stalker 写道
C/C++ 程序静态数据区大小有限制,所以有 static 内容太多会把栈空间压小的说法。
但是 java 程序里写多少 static 关系不大的,static 内容往往不存在静态数据区而是驻留在 perm gen 里 …… 另外 C/C++ 程序也是可以调栈大小的 ……
java 可以做元编程但是繁琐得要命,所以,就和不能元编程没什么区别。
但是 java 程序里写多少 static 关系不大的,static 内容往往不存在静态数据区而是驻留在 perm gen 里 …… 另外 C/C++ 程序也是可以调栈大小的 ……
java 可以做元编程但是繁琐得要命,所以,就和不能元编程没什么区别。
java的meta programming不是繁琐。。是萎缩...
41 楼 易卡螺丝君 2010-11-10
mathgl 写道
logicgate 写道
yangguo 写道
易卡螺丝君 写道
没有mixin 不支持元编程的单根继承 就是在自寻死路
盲目mixin就是盲目迷信,才是真的自寻死路。
C++er,请你们到csdn去自我感觉良好。
人家是rubyer.
metaprogramming用起来极爽,可以开发出非常强大的框架。
那mixin 和 c++里面的multiple inheritance 类似的东西。。不过这玩意在c++里面也不是都用。。
也就在qt里面用得比较多....其他的时候 更多是作为interface来处理的。
又一个不懂装x的 动态语言 要你妹的interface 要你妹的多继承
请问什么是duck type
c++算你妹的动态语言?
42 楼 ray_linn 2010-11-11
night_stalker 写道
C/C++ 程序静态数据区大小有限制,所以有 static 内容太多会把栈空间压小的说法。
但是 java 程序里写多少 static 关系不大的,static 内容往往不存在静态数据区而是驻留在 perm gen 里 …… 另外 C/C++ 程序也是可以调栈大小的 ……
java 可以做元编程但是繁琐得要命,所以,就和不能元编程没什么区别。
但是 java 程序里写多少 static 关系不大的,static 内容往往不存在静态数据区而是驻留在 perm gen 里 …… 另外 C/C++ 程序也是可以调栈大小的 ……
java 可以做元编程但是繁琐得要命,所以,就和不能元编程没什么区别。
静态语言里元编程超强的当属nemerla了,.NET家族的,函数和oo双吃,不但能造词,还能轻松造句,ruby也只能望尘兴叹。。
热切等待nemerla 1.0啊
43 楼 ray_linn 2010-11-11
易卡螺丝君 写道
请问什么是duck type
c++算你妹的动态语言?
c++算你妹的动态语言?
扯犊子吧你,人家说mixin,你扯到鸭子类型。。。。
mixin就是等同于多根继承,难不成还是新的创造啊?
鸭子类型--》传个函数指针就完事了呗,嘛事不能用一根指针搞定? void** 万能无敌
44 楼 skyfen 2010-11-11
艹,从来就不看好java,ADODB,以前是漏洞大王,现在java却成了漏洞大王。真是世事难料啊。SUN的落幕,Orcale能撑起Java的天空吗?
45 楼 finalfanny 2010-11-11
讲的不错,正在学习play!中,觉得蛮不错的,谢谢
46 楼 pangbuddy 2010-11-12
为什么log文件夹要用复数logs, 而其他文件夹没有同样命名?
47 楼 yangguo 2010-11-12
pangbuddy 写道
为什么log文件夹要用复数logs, 而其他文件夹没有同样命名?
can't you see webapps?
48 楼 vb2005xu 2010-11-12
http://vb2005xu.iteye.com/blog/795607 我写的 play入门基础
49 楼 vb2005xu 2010-11-12
请大家明确一点,Play确实不是用来做企业应用的好东西,但是在快速web网站开发,专指网站这块,确实很棒.... play做且做好了一件事情,就是web网站开发...
做企业应用的就不咋的...
做企业应用的就不咋的...
50 楼 thinkx 2010-11-12
每种语言都有各自的特性,java这种鼓励OO的静态语言中,模仿太多动态语言的东西的确没好处。
play!在model中用hack的方式生成getter和setter,除了能少敲点代码,看不出有什么好处,现在任何一个java ide都可以生成getter/setter;在controller中使用static倒是没大问题的,很简洁;至于充血和贫血倒是各有好处。我的感觉是play!的定位是用java简单的开发web应用,甚至是不用IDE只用Editor开发。不过话说在web开发领域java比动态语言唯一的优势就是执行效率了,但那东西真的这么重要么。java开发web应用还是活在企业领域里更合适吧。
play!在model中用hack的方式生成getter和setter,除了能少敲点代码,看不出有什么好处,现在任何一个java ide都可以生成getter/setter;在controller中使用static倒是没大问题的,很简洁;至于充血和贫血倒是各有好处。我的感觉是play!的定位是用java简单的开发web应用,甚至是不用IDE只用Editor开发。不过话说在web开发领域java比动态语言唯一的优势就是执行效率了,但那东西真的这么重要么。java开发web应用还是活在企业领域里更合适吧。
51 楼 zcq100 2010-11-12
做互联网应用还是需要快捷,Java更适合企业项目用,觉得这个框架别扭的可以直接来学习rails
52 楼 store88 2010-11-12
thinkx 写道
play!在model中用hack的方式生成getter和setter,除了能少敲点代码,看不出有什么好处,现在任何一个java ide都可以生成getter/setter
直接public了, 没有用getter setter哦
呵呵
53 楼 tapestry1122 2010-11-12
store88 写道
thinkx 写道
play!在model中用hack的方式生成getter和setter,除了能少敲点代码,看不出有什么好处,现在任何一个java ide都可以生成getter/setter
直接public了, 没有用getter setter哦
呵呵
你看play的源代码就知道了,他在运行的时候通过cglib生成的
包括你的model类,很多都是运行时通过bytecode动态注入的
play的定位就是快速开发网站的而不是啥企业级开发
54 楼 zdmcjm 2010-11-12
play!的定位是快速开发网站,是开发诸如企业首页吗?那用asp,php,rails都比这四不像好呀。
55 楼 okjacky 2010-11-19
还不太明白继续学习中。
56 楼 C_J 2011-03-14
看完之后就2点体会:
1、编写index.html模版,以前用struct的标签来写,现在又得重新学习Play的语法?我觉得有点痛苦。
2、继承xxx,无所谓,不继承你的xxx,估计我也得继承我的xxx,那么Controller里的static如何来保证线程安全呢?
谢谢
1、编写index.html模版,以前用struct的标签来写,现在又得重新学习Play的语法?我觉得有点痛苦。
2、继承xxx,无所谓,不继承你的xxx,估计我也得继承我的xxx,那么Controller里的static如何来保证线程安全呢?
谢谢
57 楼 lookdd1 2011-03-14
C_J 写道
看完之后就2点体会:
1、编写index.html模版,以前用struct的标签来写,现在又得重新学习Play的语法?我觉得有点痛苦。
2、继承xxx,无所谓,不继承你的xxx,估计我也得继承我的xxx,那么Controller里的static如何来保证线程安全呢?
谢谢
1、编写index.html模版,以前用struct的标签来写,现在又得重新学习Play的语法?我觉得有点痛苦。
2、继承xxx,无所谓,不继承你的xxx,估计我也得继承我的xxx,那么Controller里的static如何来保证线程安全呢?
谢谢
这么长时间的帖子也被你扒拉出来了。呵呵。。看来关注play的人还是有滴。。。
第一个:搞我们这行,怕学习的"痛苦"就么法进步。。。当你学习完play的模版标签你会感叹struts标签那是搞的些啥玩意啊。。况且,学习play模版的标签估计用不了一天时间吧,看一遍官方的帮助文档基本就OK了。。
第二个:涉及全局数据存取(如在request,response,flash,session等)这些中是使用的TreadLocal实现的线程安全。而我们不会定义其它的全局变量。这样就没有什么问题了。具体可以看看Play Controller的源代码。呵呵。不知道表述的是否正确
58 楼 C_J 2011-03-15
1.我还是觉得痛苦。
2.ThreadLocal貌似是各自的Thread用map存储Object的副本,难道对于Class.xxx有用?
2.ThreadLocal貌似是各自的Thread用map存储Object的副本,难道对于Class.xxx有用?