自我介绍:
在无棣等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供网站设计、成都网站建设 网站设计制作按需开发,公司网站建设,企业网站建设,成都品牌网站建设,营销型网站建设,外贸营销网站建设,无棣网站建设费用合理。
发文动因:
发文目的:
困 惑:
目标一:学习官网Getting Started和Run Demo
@
学弟学妹醒目,要了解一个框架,第一件事就是运行最简单的例子“HELLO WORLD”,(喂!喂!学长,你不会以为我们是白痴吧。。。#_#)。别怀疑,其实往往跑最简单的例子能更加迅速的了解框架的大致工作原理,这里也说一下的是,在学校的学习时,我们大多习惯于,想学一门技术时,先去图书馆找一本厚厚的教材,书名字中经常会带着“精通 专家 大全 权威”之类的忽悠人的词汇,俨然如葵花宝典版通读后立马能成为大师的感觉,但是书的质量先不说,单纯看书,这种学习技术知识的方式就如空中筑楼,基于幻想的学习,其实从实际动手入手,多体会,再看看一些经典书籍,加深理解才是比较好的途径. |
1.1 基本概念和特点
不免落俗,先说些基本概念和特点
基本都是照搬官网的,所以也可以自己去看
http://www.playframework.org/documentation/1.0.2/home
概念:Play framework是个轻快的REST风格J2EE FULL-STACK框架
RESTFul:可以参考一下这篇文章 www.infoq.com/cn/articles/rest-introduction ,暂且可以不关注。 Full-stack:现在很流行的SSH(Struts+Spring+Hibernate)的整合就是Full-Stack框架,及能提供J2EE开发全套功能的框架。比较出名的Full-Stack框架还有ruby on rails。 |
一些优点:
From a Introduction post 写道
稍作解释:
No configuration是指没有web.xml等配置文件(比如:如果自己组合SSH,得配置web.xml , spring和struts的配置文件,要配很多bean,注入以及过滤器)。
框架使用的应用服务器支持热加载,写好代码后,框架再编译后直接将类加载到服务器中,不需要重启服务器,这就大大提高了工作效率。
Routing非常简单,类似windows的hosts文件,定义了HTTP请求和应用程序的映射,再第一个例子中可以看到。
测试工作变得简单,是因为Play提供了良好的测试框架,在例子中可以看到。
这里有个大概印象即可,后面通过例子的讲解会逐一解释。
1.2 准备工作
原文在此 : www.playframework.org/documentation/1.0.2/firstapp
1.3 创建默认项目
我们对照默认页面的说明,看看生成的项目目录里都有些什么:
打开项目目录helloworld,我们可以看到如上图的目录结构
文章里面有处用词稍微不当:“MVC模式的三层的源码” 我理解,MVC模式中 M、V、C 是3类平等的组件, 一定要和 3层模式中的 表现层、逻辑层、持久层,相区别,此处应酌情改下,否则容易暗示读者(尤其是你说的学弟学妹)把3层模式和MVC模式混为一谈。 |
Routes代码:
- # Routes
- # This file defines all application routes (Higher priority routes first)
- # ~~~~
- # Home page
- GET / Application.index
- # Map static resources from the /app/public folder to the /public path
- GET /public/ staticDir:public
- # Catch all
- * /{controller}/{action} {controller}.{action}
Java代码:
- package controllers;
- import play.mvc.*;
- public class Application extends Controller {
- public static void index() {
- render();
- }
- }
如上所示,当我们用GET方法访问应用程序根目录(localhost:9000/)时,框架从router中找到匹配的映射,即 Application.index 所对应的调用方法是controllers目录下的Application类的index()方法。
index()方法中调用了render()方法,这个方法将显示模板(template)app/views/Application/index.html。
再来看看index.html
Html代码:
- #{extends 'main.html' /}
- #{set title:'Home' /}
- #{welcome /}
#{extends 'main.html' /}表示该模板继承自'main.html'
#{welcome /}Tag则是显示默认页面的主体部分。
其中:app/view/main.html
Html代码:
#{get 'title' /} - #{get 'moreStyles' /}
- #{get 'moreScripts' /}
- #{doLayout /}
可以看到main.html里定义了一些共通的html元素,如所用的css,shortcut icon图标,字符集和js库等。
通过继承的方式,可以使得模板的复用性大大增强。
#{doLayout /} Tag指示index.html的内容是插入此处的。
1.4 将默认项目改写成第一个app:Hello world
编辑helloworld/app/views/Application/index.html
Html代码:
- #{extends 'main.html' /}
- #{set title:'Home' /}
与默认项目的index区别,去掉了welcome tag,添加了一个form,注意action部分
@{}的用途是让Play自动生成能够Invoke Application.sayHello action的URL"@{Application.sayHello()}" method="GET"
我们将其转换成routes的表达方式是: GET /Application/sayHello Application.sayHello
保存后我们重新访问 localhost:9000/
出错的原因是“No route able to invoke action Application.sayHello was found.”
因为route找不到 Application.sayHello action。
这里我们还能看到Play友好的出错信息,一目了然。
此处可能你会奇怪,我们在routes里没有写明GET /Application/sayHello Application.sayHello的映射,那么route又怎么知道去invoke Application.sayHello action呢? 回头看routes,最后两行定义了 # Catch all * /{controller}/{action} {controller}.{action} 这是一个共通的匹配 因为@{Application.sayHello()}实质上时转化成url:/Application/sayHello 而*能匹配所有Http方法,因此route这里对应的action自然是Application.sayHello
|
在/app/controllers/Application.java中添加sayHello()方法。
Java代码:
- package controllers;
- import play.mvc.*;
- public class Application extends Controller {
- public static void index() {
- render();
- }
- public static void sayHello(String myName) {
- render(myName);
- }
- }
sayHello仍然是调用render方法,但是与index方法的区别是这里传了一个参数myName。
保存类文件后我们重新访问 localhost:9000/
找到对应的action后,index显示出来了,随便输入名字点击按钮后,又显示错误 ...+_+
提示找不到模板,我们需要写与Action对应的模板Application/sayHello.html
观察此时URL: http://localhost:9000/application/sayhello?myName=Darcy 点击‘Say Hello’按钮后,URL跳转到如我们先前所说的/application/sayhello,并且带上参数myName=Darcy。 route通过此URL找到匹配的action后Invoke application.sayhello("Darcy") 将参数传入render("Darcy"). 而render所作的工作,通过出错提示可以猜测到是需要模板sayhello.html来显示相应的PAGE. 其实这里Play帮我们做了很多工作,不需要我们一 一配置。 关于MVC:类Application继承自play.mvc.Controller,在MVC中处于控制器。 控制器的作用将数据模型(model)和视图(view)联系起来。 说白了最简单的用途是怎样将数据模型传递到相应页面上。 本例我们在app/models没有类,并不意味着我们没有model,其实myName就是个简单的model,当我们在文本框输入后并点击按钮后,myname被传递到render里,下一步自然是要将myName传递到视图层,此处我们没显式的指定传到哪个视图,其实Play默认做法是 Application中的方法名与模板名是一致的。 |
创建模板文件helloworld/app/views/Application/sayHello.html
Html代码:
- #{extends 'main.html' /}
- #{set title:'Home' /}
Hello ${myName ?: 'guest'}!
- Back to form
保存文件后我们重新访问 localhost:9000/
现在可以正常访问了。
我们看看参数myName在模板里是如何得到的。
如上面的红字部分${myName ?: 'guest'},直接用同样的变量名即可访问,真是非常简单!!
但是您肯定会疑惑,这种表达式${myName ?: 'guest'}以及之前的#{extends 'main.html' /}到底是什么语言....?
通过声明此表达式,能创建一个模板的动态元素,这个表达式最终显示的值是动态取得的,如本例${myName ?: 'guest'},myName是个动态元素,其名称与控制器Application中与本模板关联的相应方法中的参数名一致,myName后面的'?' 是判断myName是否存在(不为null),如果存在,则${myName ?: 'guest'}=‘Darcy‘(以我之前输入Darcy为例),否则${myName ?: 'guest'}= ‘guest’(不输入内容直接点按钮)
#{tagName /}通过tag名和参数完成模板功能,比如#{extends 'main.html' /} extends是tag名,‘main.html’是参数,模板引擎读到#{extends 'main.html' /}后会帮我们完成继承main.html模板的功能。这些tag是在模板引擎中定义好的。
|
现在的URL( localhost:9000/application/sayhello?myName=darcy )看起来不是很舒服,我们可以在routes里对他优化一下。
在默认route前加上我们自定义的route
在routes文件中,是靠上的r oute 优先。
Route代码:
- GET /hello Application.sayHello
保存一下再跑一次看看,URL变了 : localhost:9000/hello?myName=ddd
自定义一些页面元素
编辑helloworld/app/views/main.html
将
稍作修改Html代码:
- The Hello world app.
- #{doLayout /}
保存后查看
这里我们可以再次看到,main作为一个父类模板,将一些共通的页面元素写在main里,其他继承于他的模板都会有这些元素,大大提高了复用性,因此,我们在自己用Play做东西时,也应该灵活使用此特性。
添加基本校验(Adding validation)
编辑helloworld/app/controllers/Application.java
Java代码:
- package controllers;
- import play.mvc.*;
- import play.data.validation.*;
- public class Application extends Controller {
- public static void index() {
- render();
- }
- public static void sayHello(@Required String myName) {
- if(validation.hasErrors()) {
- flash.error("Oops, please enter your name!");
- index();
- }
- render(myName);
- }
- }
除了修改sayHello方法,别忘了import play.data.validation.*
annotation @Required的用途是校验myName是否存在。若验证失败(validation.hasErrors() == ture) ,则抛出错误信息(flash.error("Oops, please enter your name!");。
这些错误信息是存放于Play的flash scope
from Play org
The flash scope allows to keep messages during action redirection.
flash scope能够在redirection时保存消息。
为了显示错误信息,我们还需添加显示错误信息的代码,由于这个错误信息是在Redirect至index后显示的,因此我们编辑index.html
编辑helloworld/app/views/Application/index.html
Html代码:
- #{extends 'main.html' /}
- #{set title:'Home' /}
- #{if flash.error}
- ${flash.error}
- #{/if}
注意看#{if flash.error},这是Play中if Tag的用法,后面跟着Boolen参数。
flash虽然没作为参数传至render,但是也传递过来了,这就是flash scope的作用。
保存后我们访问主页,不填值,直接点按钮。
出错信息正常显示。
添加自动化测试
Play的测试框架继承JUnit和Selenium等,使写测试代码也非常方便。
因为本例没什么逻辑,所以我们只能进行一些页面的测试。
这里我们写一个Selenium测试脚本进行页面的测试。
首先,我们需要切换到测试模式。
到CMD中关闭应用模式服务器(ctrl+c)
使用命令:
play test helloworld
切换到测试模式。
打开浏览器访问http://localhost:9000/@tests
这个页面是play测试框架的控制台,可以选择要进行测试的项目并执行测试,查看测试结果等。
我们将三项都选择,然后点Start进行测试,结果都是绿灯。原因是此时的测试代码都是必通过的= =#
比如:assertEquals(2, 1 + 1);
从此控制台还能看出,Play的测试框架可以进行单元测试,功能测试和Selenium web测试,真是太方便了!!
Selenium 是HTML的脚本,有点冗余,Play在测试框架中对Selenium 脚本也进行了优化,及可以使用Play的模板来写Selenium 脚本
(此处我想应该是Play对html脚本进行了二次封装,转成Play模板,当Selenium 读测试脚本是,Play的模板引擎会将模板再还原成Selenium 脚本,这里顺便标记一下,以后通过看Play的具体实现来验证)。
编辑现成的Selenium 脚本:helloworld/test/Application.test.html
Html代码:
- #{selenium}
- // Open the home page, and check that no error occurred
- open('/')
- assertNotTitle('Application error')
- // Check that it is the form
- assertTextPresent('The Hello world app.')
- // Submit the form
- clickAndWait('css=input[type=submit]')
- // Check the error
- assertTextPresent('Oops, please enter your name!')
- // Type the name and submit
- type('css=input[type=text]', 'bob')
- clickAndWait('css=input[type=submit]')
- // Check the result
- assertTextPresent('Hello bob!')
- assertTextPresent('The Hello world app.')
- // Check the back link
- clickAndWait('link=Back to form')
- // Home page?
- assertTextNotPresent('Hello bob!')
- #{/selenium}
真是简洁不少= =#
保存后我们运行一下
至此我们参照官网的第一个例子教程完成了helloworld,这个最简单例子。
我们现在做的事情无非是照着官网step by step的把这个例子做完,并对Play有了初步的印象。
@
我想大部分我们学弟学妹在做完例子后,肯定是脑海浮现很多idea想去迫不及待的实现~其实大家都是这样的,这是好事。 这里想插一件事情: 不知道你们有没有选董群峰老师的课,我有幸听了他的数据挖掘的课,更有幸的是听到他在第二节课的即兴演讲: 记得不是非常准确了,但是大致情形是,他给我们讲数据挖掘的某算法之前,让我们先想想应该用什么样的算法来解决那个问题,当时我们很囧,因为我们刚初学,怎么能想到那些大师发明的算法呢? 但是这个头脑风暴在一个非常葱的MM大胆发言后居然就这么在整个课堂讨论开了,最后短短半小时居然答案越来越接近了(当然这和直接发明算法的大师还是区别很大的,因为每次发言后董老师会给出些提示,比如‘不对’ ‘接近了’ ‘更近了’之类的,帮我们剔除掉很多错误的路径)。 随后董老师展开演说,让我们千万不要忽视初识某个领域时脑海里想法,因为我们的思想还没被以往沉积的知识体系固化,所以此时我们是创造力,创新力最旺盛的时期,任何想法都可能对此领域有重大的贡献,不要退缩,不要自我否定,不要急功近利去啃书本。要勇于挑战权威,勇于坚定自己的想法。 我可能有点演绎了,总之,我觉得这话说到我心坎了,尤其在我们中国,总觉得没有个砖家叫兽之类的头衔都不敢正视自己的想法,非要觉得自己的想法应该是错的,地位高的人说的就是对的。 这里我又想到Hibernate的创始人Gavin King(www.iteye.com/wiki/Celebrity/293-hibernate-founder-gavin-king)。他的经历可能正是这种需要自信,需要能坚持自己的想法,需要能挑战权威的最好榜样。
|
总结:经过了最简单的例子,我们看到了Play的部分特点:
目标二:Demo总结及阅读Play源码
以上特点都是我们实际跑例子直观看到的一些东西,下面我们深入一点,通过Debug demo的方式深入Play的源码看看Play是具体如何工作的:
@
手把手教新手学弟学妹读代码:以上总结都是我们通过run demo直观看到的Play特性,本文除了是自己学习play的总结,更希望能引导学弟学妹用正确的方式读开源项目的源码。 这里先说一下我觉得初涉编程,阅读源码容易出现的错误: 1:从官网下了代码包后直接解压后逐个文件扫描,比较像读小说,按页翻,这样看肯定是稀里糊涂,看不出什么门道来。 2:要深入到一定层次(比如:能灵活使用框架,能做出复杂的引用,官方文档也读了不少)才敢去读源代码,总觉得斤两不够的时候读了也没啥用。 我个人认为,读源码也应该是渐进式的,从最简单开始,即从helloworld开始。 读代码要通过debug demo的方式去进行,善用step into和step over两种步进方式(什么,你分不清step into和step over的区别!!??赶紧去谷哥之) 此外,由于刚开始看,肯定会有很多不明白的(因为框架的运行是个复杂的过程,其中很多工作我们可能还没有接触到,也就不会有体会,当然看不懂)。因此我们应该尽量挑看的懂的地方看,要带着问题去看: 比如本例,我们可以想想: 数据模型是怎么传递的视图的 加参数校验是怎么实际工作的。 校验失败的错误信息时何时写入flash scope并伴随redirect传递到视图层。
|
因为需要Debug,因此需要IDE的支持,这里我们使用Eclipse。
Play提供了命令,可以直接将Play的项目转换成eclipse项目。
进入放置helloaworld的目录,使用命令
play eclipsify myApp
即可完成转换。
然后直接用Eclipse导入该项目即可
在Eclipse的package Explorer中,我们可以看到项目里多了一个eclipse目录
eclipse导入项目.png
此目录里的三个文件:
注※ myFirstApp是项目名,如果参照例子做,项目名应该是helloworld
开始Debug阅读源码
此处我们先run myFirstApp.launch,然后Debug As..Connect JPDA to myFirstApp.
在Application的index()方法里的render()方法设上断点。
打开浏览器访问主页,程序执行到断点,转到eclipse,按F5进入render()方法,此时,由于class文件没有和source文件关联,所以看不到源码。
根据提示点击"Change Attach source",选择Play1.02目录下的xxx\play-1.0.2\framework\src即可完成关联,稍等即可看到源码。
按F6继续往下走(F5跳进方法内,F6在方法内步进)
我们现在在Controller类里面:
先看看Controller类:
这个类没有抽象方法,但是有abstract关键字,是个抽象类。
这个类所有方法都是静态的protected方法,成员变量也都是静态的,除了_currentReverse这个ThreadLocal变量外,其他也都是protected的。
因此,在\app\controllers包下要使用Controller的静态方法,必须通过继承。
然而,从作为一个父类考虑,此类没有成员变量和方法,子类继承后没有得到父类的任何成员,在面向对象这一角度观察和实现一个空接口的功能相似,即标明这类是个controller。
因此我的理解是,这种设计,当我们在\app\controllers下面写一个Application并继承controller后,作用就是标明 Application是个controller,并且Application作为一个代理执行controller类的静态方法。
同时,Application要实现controller的核心功能,必须调用controller的静态方法,因此,调用此方法的方法也必须是静态的。从而Application也没有被实例化的必要,所以可以证明,Play中的控制器是没有实例存在于容易中的。
回到render()方法,这个方法的参数是个类型为object的可变参数列表,
Java代码:
- protected static void render(Object... args) {
- ...
- }
可见render这个接口是个吞吐量巨大的视图层入口,为模板传递数据。
这个方法的主要功能取得到要render的模板名(templateName)。
此时的逻辑很简单,由于参数为空,走入else,然后拼接访问index的路径文件名Application/index.html(用Debug的方式阅读代码的另一个好处,即一些与我们关注点不大的逻辑直接走过后用watch查看结果,能加快我们队代码的理解)
Java代码:
- String templateName = null;
- if (args.length > 0 && args[0] instanceof String && LocalVariablesNamesTracer.getAllLocalVariableNames(args[0]).isEmpty()) {
- templateName = args[0].toString();
- } else {
- templateName = Http.Request.current().action.replace(".", "/") + "." + (Http.Request.current().format == null ? "html" : Http.Request.current().format);
- }
我们继续往下走。先不管[if(templateName.startsWith("@"))]处的逻辑,因为暂时没遇到过这个case,等以后再看不迟。
走到 renderTemplate(templateName, args); 我们F5进去看看
这个方法将模板名以及render的可变参数列表传递进来,再加上方法名,我们猜测这个方法的作用是根据模板名找到模板,然后把可变参数列表里的对象传到模板上。
看看实现:
Java代码:
- protected static void renderTemplate(String templateName, Object... args) {
- // Template datas
- Scope.RenderArgs templateBinding = Scope.RenderArgs.current();
- for (Object o : args) {
- List
names = LocalVariablesNamesTracer.getAllLocalVariableNames(o); - for (String name : names) {
- templateBinding.put(name, o);
- }
- }
- templateBinding.put("session", Scope.Session.current());
- templateBinding.put("request", Http.Request.current());
- templateBinding.put("flash", Scope.Flash.current());
- templateBinding.put("params", Scope.Params.current());
- try {
- templateBinding.put("errors", Validation.errors());
- } catch (Exception ex) {
- throw new UnexpectedException(ex);
- }
- try {
- Template template = TemplateLoader.load(templateName);
- throw new RenderTemplate(template, templateBinding.data);
- } catch (TemplateNotFoundException ex) {
- if(ex.isSourceAvailable()) {
- throw ex;
- }
- StackTraceElement element = PlayException.getInterestingStrackTraceElement(ex);
- if (element != null) {
- throw new TemplateNotFoundException(templateName, Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber());
- } else {
- throw ex;
- }
- }
- }
先得到Scope.RenderArgs对象,还记得我们在之前加参数校验时,按照官网说法,如果校验失败,错误信息会放在Scope.flash里。
此处又用到了Scope的内部类RenderArgs的实例储存Render参数。
Scope在J2EE里通常是指生命周期的意思,因此Scope中保存的是各种状态:
根据代码可以清晰地看出,此处Scope.RenderArgs里存放了各种状态(session,request,flash,传递进来的参数和出错信息)
Template template = TemplateLoader.load(templateName);
这个方法把模板实例load进来。模板的load过程我们后面单独开一节说,先关注核心部分。
分享名称:Play!Framework学习笔记:初识Play
标题链接:http://www.shufengxianlan.com/qtweb/news28/482728.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联