锁定老帖子 主题:再说Play!framework
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2010-11-09
最后修改:2010-11-10
http://www.iteye.com/topic/806660这里的主贴里去的,想挽回被隐藏的命运,但我写完本贴的内容,却发现为时已晚。好吧,我承认,上一个贴的标题容易引发口水,这次我们实事求是,从代码出发,通过一个小例子较完整的介绍play!framework的开发过程:
这篇帖子的内容我本来想发到
就拿play!framwork自带的房间预订(booking)的例子吧: 1、 下载play 解压,配置环境变量 2、 打开命令行:转到合适的目录,输入Play new booking 这样,项目即生成完毕。 3、 进入项目目录中,执行play eclipsify 或者play netbeansify 这样即可将生成的项目导入到eclipse或者netbeans中。 打开项目目录我们可以看到: 解释下各个目录:
这样的一个目录显然与传统的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 这个注解基本就是个拦截器的意思,所有访问这个Controler方法的请求都会先执行@before方法。 2、controller中的几个作用域:
基类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("from@163.com", "to@163.com", "",result,"text/html") 以上即是使用模板发送邮件的例子。当然您需要在application.conf指定发邮件的一些参数 4、非常多的好用的module:比如支持lucene的search-module ,MongoDB module,GAE module,Excel Module,GWT Module,PDF Module等等。 以上大体介绍了play!framework的开发示例以及一些基本特点。大家可以讨论下你对Play!framework看法,但请勿鄙视一把走人,或者发表带有人身攻击的言论,谢谢! 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-11-10
我艹。。。怎么看着跟rails这么像!
|
|
返回顶楼 | |
发表时间:2010-11-10
jinleileiking 写道 我艹。。。怎么看着跟rails这么像!
它就是一个类rails的full stack framework。并且开发语言为java!你可以使用任意的第三方jar,可以使用java的IDE,可以使用标准的servet容器,开发时候可以不用重启server。 缺点?好吧,有童鞋说的它不面向对象,大量的static,模型属性都是public,还有很多对class文件的hack |
|
返回顶楼 | |
发表时间:2010-11-10
楼主不怕jvm的栈溢出啊~~~,如此疯狂的static
CURD是很爽了,MIS也不错,感觉很有凌乱美!! 看似牛叉,也充满风险。 |
|
返回顶楼 | |
发表时间:2010-11-10
针对不同的项目需求,采用不同的开发框架。风险是选择开发框架这个步骤上,而不是这个开发框架本身。适合的才是最好的。
play个人学习也用过,但是没有开发什么大项目,所以也是观望。 |
|
返回顶楼 | |
发表时间:2010-11-10
# @Before//拦截器
# static void checkUser() { # if(connected() == null) { # flash.error("Please log in first"); # Application.index(); # } # } 那是不是每一个controller都要写一个这方法?假设我需要每一个controller都需检查用户是否登录,和用户的当前角色是否有权限操作controller中的某个方法。 我知道servlet过滤器可以实现,但在play这种基于静态方法的controller模型中,怎么实现? |
|
返回顶楼 | |
发表时间:2010-11-10
zdmcjm 写道 # @Before//拦截器
# static void checkUser() { # if(connected() == null) { # flash.error("Please log in first"); # Application.index(); # } # } 那是不是每一个controller都要写一个这方法?假设我需要每一个controller都需检查用户是否登录,和用户的当前角色是否有权限操作controller中的某个方法。 我知道servlet过滤器可以实现,但在play这种基于静态方法的controller模型中,怎么实现? 您可以写一个公共的controller然后其它所有controller继承它 |
|
返回顶楼 | |
发表时间:2010-11-10
lookdd1 写道 zdmcjm 写道 # @Before//拦截器
# static void checkUser() { # if(connected() == null) { # flash.error("Please log in first"); # Application.index(); # } # } 那是不是每一个controller都要写一个这方法?假设我需要每一个controller都需检查用户是否登录,和用户的当前角色是否有权限操作controller中的某个方法。 我知道servlet过滤器可以实现,但在play这种基于静态方法的controller模型中,怎么实现? 您可以写一个公共的controller然后其它所有controller继承它 并非有意找茬。我们都知道在Java中,继承关系是最难进行扩展的,因为它是单根继承模式。针对你上面的checkUser需求,都要创建一个基类来完成了,更加不要说其他需求的不断扩充了。 所以我一直说这并非静态语言的优势,如果是动态语言,这个将非常容易用植入的方式去实现。 |
|
返回顶楼 | |
发表时间:2010-11-10
linliangyi2007 写道 楼主不怕jvm的栈溢出啊~~~,如此疯狂的static
CURD是很爽了,MIS也不错,感觉很有凌乱美!! 看似牛叉,也充满风险。 基本上能够说getter和setter方法无用的人,是不在乎static满天飞的,也无需考虑任何设计模式。 其实JavaBean的规范的创立,给了我们一种公共的无入侵的方式实现很多编程模式。这也是Spring等开源框架存在的基础。Play之流直接全部静态化了,那我们也不需要什么容器来管理对象的生命周期了,因为这些概念都将不复存在。 因此,我一贯坚持静态语言就是静态语言,动态语言就是动态语言,把2者混着来就是吃饱了撑的没事干。 |
|
返回顶楼 | |
发表时间:2010-11-10
没有mixin 不支持元编程的单根继承 就是在自寻死路
|
|
返回顶楼 | |