今天是来百度实习的第四天,在项目过程中mentor说会用到play框架,当时就一个晕啊。Java还有一个叫play框架,作为一个从大三开始用java的重度javaer,居然不知道这个框架的存在,内心一阵草泥马飞过,于是乎就花了一下午研究一下。
What's play framework?
先来个开胃菜吧,什么是play框架呢?
先来点官方的解释:Play是一款开源、轻量、无状态、Web友好的架构,使用Java语言编写并遵循MVC模式,集成了当今Web开发所需的组件和API。此外Play可以给应用程序提供可预测的和最小的资源消耗(CPU,内存,线程),可构建高扩展的应用程序。play项目的地址如下:http://www.playframework.com/。该地址已经被qiang,自备梯子。还有个一个中文社区的网站,地址如下:http://play-framework.herokuapp.com/。帖子大部分都是针对1.2这个版本的,所以跟最新版的有一定区别。
目前play框架最新的版本是2.3.1,下载的链接是:http://www.playframework.com/download
下面是从某些网站抄来的关于play框架的优劣分析:
优势:
1. 开发效率高。 用Java但开发效率直追RoR; (可以通过play提供的命令行工具,快速创建java应用)
2. 无状态,可拓展性好。比如它的session是以加密方式保存在cookie中的。
3. 使用 Groovy 作为视图层模板使用的表达式语言。模板之间的继承机制避免了重复的代码。
4. 支持异步开发
5. 源码动态编译,无需重启服务器。修改任何java或模板代码,都不需要重启server。
6. 文档和例子很完善
劣势:
1. 在国内还是非主流
2. 反传统的Java框架,完全抛弃Servlet
3. play!社区比较沉稳,没有太多的市场宣传和功能承诺,目前还不太会吸引不太懂技术的人或者说要有hacker精神才会进行尝试。
play框架的版本
Play当前的版本有点乱,不同版本之间差别还相当大,这常常让初学者一头雾水。具体的说明见:http://www.cnblogs.com/babybluevino/p/3851866.html
play框架的使用
在win下要想使用play是很简单的,从上述的链接中download下play框架的二进制包,再在环境变量PATH中配置相应的二进制包的路径。就可以在cmd下使用play框架了。但是需要特殊指出的一点是,play框架需要jdk的支持,所以你在本机需要预先装好不同版本play框架对应的jdk版本。
经过以上操作之后,你就可以在cmd下使用activator命令了。(Tips:在2.3.1版本中使用activator命令代替了原来的play命令)如下图示:
接下来就可以使用activator new my-first-app play-java 命令使用java模板创建一个play工程。
第一个play工程my-first-app创建完成之后,你可以进入该工程目录。 cd my-first-app
然后键入activator,这时候你就会进入一个console,如下图所示:
然后你可以在这个console里进行一系列的操作,比如获取帮助:help,比如获取某个固定命令run的帮助:help run。
在console里输入run是运行这个应用(play框架会启动自带的netty服务器),如下图所示:
这时候你可以用浏览器访问 http://localhost:9000 并看到应用的默认页面。
与eclipse的集成开发
play既然作为一个java的web框架,自然离不开神一样的java开发环境eclipse。
Play 提供一个生成 Eclipse 配置的命令。要将 Play 应用转化为 Ecipse 工程。进入这个工程目录,运行eclipse命令。
然后,你需要使用 File/Import/General/Existing project… 菜单将应用导入到你的工作区中。(javaer不会不知道吧)
以上讲解的都是2.x的版本,鉴于mentor告诉我我们的开发要基于1.7这个版本,所以就继续要看1.7这个版本的。
1.7版本和2.x版本的命令使用不一样,在2.x版本中使用activator,而1.x版本使用play命令。把普通的项目变成eclipse的项目在1.7的命令是:play eclipsify <项目名称>。
使用play框架的Web应用的目录结构如下图所示:
app: 包含应用核心,分为models,controllers和views目录。其中 models 和 controllers 目录下面是 Java 源文件,而 views 目录下面则是视图层使用的模板文件。一个Controller就是一个Java 类,它的静态公共方法则是动作(Action)。动作是接收HTTP请求后的Java处理入口点。Controller类实际是面向过程的,非OO。 Action从HTTP请求中提取数据,读或更新Model对象,然后返回一个包装成HTTP响应(HTTP Response)的结果。
conf:包含应用的所有配置。application.conf应用主配置.routes定义url路由规则,messages国际化用。应用的主入口点配置在conf/routes文件中。它定义了应用所有可访问的URL。比如,默认的helloworld里,它告诉Play,当/路径收到GET请求后调用Application.indexJava方法。它是controllers.Application.
index的缩写,因为controllers包是隐式的附加的。
在play框架中,一个url对应一个action的方法,定义action方法的类称为controller。
一个URL一个。这些方法称为action方法。定义action方法的类称为controller。
lib:包含应用依赖的标准.jar文件。
public:包含所有外部可访问的资源,如:js,css和image。
test:包含所有应用的测试程序。测试程序基于JUnit或Selenium。
另外,由于是eclipse项目,还会多出一个eclipse目录,包含三个启动配置:
- JPDA:连接到已经启动的Play Server,实现alive调试
- helloworld:本地运行
- Test:测试
选中它们,右键执行Run As,即可完成相应的任务。
把Javaweb项目打成war包
play生成war包:activator war exp-platform-mgt/ -o exp.war
tips:war包里面是有源码的。
如果不要源码包的话:
activator war exp-platform-mgt/ --exclude app/controller:app/models -o exp1.war
这样打成war包,会将源文件编译到precompiled目录里。
请求生命周期
Play是完全无状态的(stateless),且仅面向请求-应答(Request-Response)。所有请求遵循相同路径:
框架收到一个HTTP请求
Router匹配请求和Controller、Action,执行动作方法。
应用代码执行
绘制模型,呈现视图
动作方法的结构作为HTTP响应返回。
model层
模型层包含的是 Web 应用中的领域对象。Play 框架推荐的实践是模型层的对象不应该是仅包含 getter/setter 方法的简单 Java Beans,而应该有自己的业务逻辑。Play 框架中应用的模型层类可以是任何的 Java 类。与一般的 Java Beans 不同的是,模型层类使用声明为 public 的域作为对象的属性。Play 框架会自动生成相应的 getter/setter 方法。这样可以使得代码更加简洁。开发人员也可以提供自己的 getter/setter 方法实现。例如,当我们声明一个类:
public class Product { public String name; public Integer price; }
实际上类被加载会变成这样:
public class Product { public String name; public Integer price; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } }
当你想访问 一个属性时,你可以直接写成:product.name = "a dog"; product.price = 58;
model层的持久化
领域对象的实例一般需要持久化下来。最常见的持久化方式就是使用关系数据库。Play 框架使用 JPA 规范来进行领域对象的持久化。具体的后台实现使用的是 Hibernate。开发人员只需要使用 JPA 规范定义的标注,就可以声明领域的持久化行为。比较好的做法是将领域对象类继承自 Play 框架提供的 play.db.jpa.Model 类,该类提供了一个域id作为对象的标识符。play.db.jpa.JPASupport 类是 play.db.jpa.Model的父类,提供了一些实用方法用来完成从领域对象到数据库之间的映射。下表中列出了一些重要的方法,包括常用的增删改查操作。
play.db.jpa.JPASupport API 说明
方法 | 说明 |
---|---|
create(type, name, params) |
用来创建领域对象类的一个实例。参数 type 表示的是领域对象类,类型是 java.lang.Class ;name 表示的是领域对象类的名称;params 表示的是一个包含了实例中属性值的类型为java.util.Map<java.lang.String,java.lang.String[]> 的哈希表。 |
edit(obj, name, params) |
用来编辑领域对象类的一个实例。参数 obj 表示的是领域对象实例;参数 name 和 params 的含义与 create() 方法的相同。 |
delete() |
用来删除单个领域对象类的实例。 |
delete(query, params) |
用来删除多个领域对象类的实例。参数 query 表示的是检索待删除实例的查询,而 params 表示的是查询所使用的参数。 |
deleteAll() |
用来删除领域对象类的所有实例。 |
find(query, params) |
用来查找领域对象类的实例。参数 query 表示的是查找时所用的查询,而 params 表示的是查询所使用的参数。 |
findAll() |
用来查找领域对象的所有实例。 |
findById(id) |
用来根据标识符查找领域对象的实例。 |
count(query, params) |
用来计算某个查询结果中包含的领域对象的实例数。参数 query 和 params 的含义与 find() 方法相同。 |
save() |
用来保存该领域对象实例到数据库中。 |
all() |
用来查找系统中的全部领域对象的实例。 |
表中列出的方法中,find() 和 all() 方法的返回值是 play.db.jpa.JPASupport.JPAQuery 类的实例,表示一个领域对象实例的查询结果。对于此查询结果,可以进行进一步的操作。
play.db.jpa.JPASupport.JPAQuery API 说明
方法 | 说明 |
---|---|
bind(name, param) |
用来绑定一个参数的实际值到查询上。在查询语句中可以使用形式参数作为占位符,参数的实际值可以通过此方法来指定。 |
fetch() |
用来获取此查询的所有记录。 |
fetch(max) |
用来获取此查询的前面 max 条记录。 |
fetch(page, length) |
用来对查询结果进行分页。参数 page 表示当前的页数,从 1 开始;length 表示每页的记录数。 |
first() |
用来返回查询结果中的第一条记录 |
from(position) |
用来设置查询结果中处理的起始位置。参数 position 表示起始位置的序号。该方法的返回结果是一个新的play.db.jpa.JPASupport.JPAQuery 对象。 |
使用上表中给出的方法,就可以在领域对象类中添加一些非常实用的方法,而不需要把这些方法添加到额外的服务层中。
控制层
Play 框架中的每个控制器都是一个普通的 Java 类,继承自 play.mvc.Controller 类,在包 controllers 中。控制器类中的每个公开的静态方法都表示一个动作。每个动作负责完整的请求 / 响应的流程,也就是说,所有前面提到的所有请求/响应的过程都需要在每个动作中来完成。
解析参数
在控制层实现中很繁琐但是必不可少的操作就是解析 HTTP 请求中的参数。不同的 Web 开发框架会提供自己的参数解析方式。Play 框架也提供了相应的支持。Play 框架可以解析 HTTP 请求中查询字符串和 URI 路径中包含的以及请求体中以格式编码的参数。所有这些参数都放在 params 对象中,其中包含 get()、getAll() 和 put() 等方法用来获取和设置参数的值。除了这种传统的使用方式之外,Play 框架还支持直接把参数的值绑定到动作方法的参数上面。比如一个动作方法的声明是 show(String username),那么请求中的参数 username 的值会在 show() 方法被调用时作为实际参数传递进去。Play 框架会负责完成相应的类型转换。值得一提的是对于日期类型(java.util.Date)的参数,Play 框架支持多种类型的日期格式的转换。比如动作方法的声明是 display(Date postedAt),而请求的格式可能是 /display?postedAt=2010-09-22,Play 框架会自动完成相应的类型转换。
除了常见的基本数据类型之外,Play 框架还支持直接绑定领域对象的实例。比如动作方法的声明是 create(Note note),可以在参数中直接指定对象实例的属性的值。请求的格式可能是 /create?title=Note123&content=Good。Play 框架会负责创建一个 Note 类的实例,并根据参数的值设置该实例的属性 title 和 content 的值。这种绑定方式不仅支持简单对象,还支持嵌套对象和列表。比如 /create?tags[0]=ajax&tags[1]=web 可以设置列表类型属性 tags 的值。
Play 框架的这种绑定方式还支持文件对象,使得上传文件变得非常简单。只需要在表单中添加文件上传的控件(<input type="file">)并使用 multipart/form-data编码来提交请求,在动作方法的参数中就可以获取到上传文件对应的 java.io.File 对象。比如动作方法的声明可能是 upload(File picture)。上传的文件被保存在临时目录中,在请求完成之后会被自动删除。可以在动作方法中完成对上传文件的操作。
返回响应结果:
在控制层的动作方法完成了与业务逻辑相关的处理之后,需要把响应返回给客户端。响应的结果可能是正确完成,也可能是出现错误。Play 框架提供了方便的实现用来返回不同类型的响应。使用 play.mvc.Controller 类提供的不同方法就可以生成这些响应内容。
请求正确完成,HTTP 状态代码为 200。使用 ok() 方法生成不带内容的响应。使用 render() 方法来生成使用模板的响应。使用 renderText() 方法生成 text/plain 类型的纯文本响应。使用 renderXml() 方法生成 text/xml 类型的 XML 格式的响应。使用 renderJSON() 方法生成 application/json 类型的 JSON 格式的响应。使用 renderBinary() 方法生成二进制内容的响应。
跳转到新的页面,HTTP 状态代码为 3XX。使用 redirect() 方法来跳转到新的 URL。使用 notModified() 方法来返回状态代码 304。
HTTP 状态代码 4XX。使用 unauthorized() 方法返回状态代码 401。使用 forbidden() 方法返回状态代码 403。使用 notFound() 方法返回状态代码 404。
服务器内部错误,HTTP 状态代码 5XX。使用 error() 方法返回状态代码 500。
从上面列出的方法可以看出,Play 框架使用一些有意义的方法名称替换掉了难以记忆的 HTTP 状态代码,使用起来更加方便。同时,对于常见的响应格式,包括 HTML、XML、JSON 和二进制内容,都提供了相应的方法,使得开发人员不会遗漏掉响应中 Content-Type 的声明。
方法拦截
控制层的方法通常需要执行一些横切的逻辑,比如用户认证、加载通用信息和记录日志等。在 Spring 框架中,这些横切的逻辑是通过面向方面编程(AOP)的支持来实现的。Play 框架提供了更加简单易用的方法拦截支持,通过简单的标注就可以定义一些执行拦截操作的方法。这些方法必须非公开的静态方法。Play 框架支持的方法拦截标注有 @Before、@After、@Finally 和 @With 等四种。
用 @Before 标注的方法在动作方法执行之前被调用。@After 标注的方法在动作方法执行之后被调用。@Finally 标注的方法在动作方法的响应结果已经成功生成之后被调用。这三个标注都支持额外的两个属性:priority 表示标注的方法的优先级,0 为最高;unless 是一个字符串数组,表示不适用此拦截方法的动作方法的名称。如 @Before(unless="index") 表示此拦截方法不会应用在动作方法 index() 上。
如果控制器类中存在继承体系结构的话,父类中声明的拦截方法对于所有子类的动作方法都是适用的。在有些情况下,开发人员可能希望把拦截方法定义在不同的类体系结构中。由于 Java 不支持多继承,无法通过继承的方式来应用来自不同类体系结构上的拦截方法。针对这种情况,Play 框架提供了 @With 标注。在控制器类 ControllerA 中定义的拦截方法可以通过 @With 标注来应用到另外一个控制器类 ControllerB 上,而且不通过继承方式来实现。只需要在 ControllerB 中声明 @With(ControllerA.class) 即可。
视图层
Web 开发框架的使用者都习惯于使用某种模板技术来生成 HTML 页面,这些技术包括常见的 JSP、ASP 和 PHP 等。Play 框架也提供了自己的模板技术,可以用来动态的创建 HTML、XML、JSON 以及其它文本类型的内容。Play 框架使用Groovy作为其模板技术。
模板中可用的动态元素
动态元素 | 说明 |
---|---|
${...} |
用来对一个表达式进行求值。如 ${note.title} 的值是领域对象 note 的属性 title 的值。 |
@{...} 和@@{...} |
用来生成调用控制器中动作方法的 URL,可以用在页面的链接中。@{...} 和 @@{...} 生成的分别是相对 URL 和绝对 URL。如 <a href="@{Application.index()}"> 首页 </> 生成一个指向首页的链接。 |
&{...} |
用来显示经过国际化之后的消息内容。 |
*{...}* |
用来添加注释。如 *{ 这是注释 }* 。 |
%{...}% |
用来添加复杂的 Groovy 脚本,可以声明变量和添加语句。 |
#{...} |
用来调用 Play 框架的或是开发人员自定义的标签。 |
Play 框架中的标签的作用相当于 JSP 中的标签。Play 框架本身提供一些常用的标签,开发人员也可以根据需要开发自己的标签。
play框架的路由机制
在前面介绍过,Play 框架中的控制器用来接受 HTTP 请求并返回相应的响应。这个过程的重要一环就是 HTTP 请求的 URI 与控制器之间的映射关系。Play 框架提供了灵活的 HTTP 路由功能来完成这个映射。路由信息被保存在 config/routes 文件中,采用简单的方式进行声明。每条路由记录包含 3 个元素,分别是 HTTP 方法的名称、匹配的 URI 模式以及对应的控制器动作方法。路由记录表示的含义是当使用给定的 HTTP 方法来请求对应模式的 URI 的时候,控制器动作方法就会被调用。
Play 框架支持的 HTTP 方法有 GET、POST、PUT、DELETE 和 HEAD。使用通配符 *可以匹配任何方法。在 URI 模式的声明中可以使用正则表达式来表示复杂的映射规则。URI 模式中还可以使用 {...} 来声明动态的部分。每个动态部分都是有名称的,可以在控制器动作方法中通过 params 对象来获取。比如,/notes/home 这样的 URI 模式会匹配 /notes/home,但是 /notes/{id} 可以匹配 /notes/123 和 /notes/abc,而且 URI 模式中 /notes/ 后面的部分可以作为参数 id 的值被获取到。URI 模式 /notes/{<[0-9]+>id} 使用了正则表达式,只会匹配 /notes/后面紧跟的全是数字的情况。在声明控制器的动作方法的时候,需要使用带名称空间的全名,如 myapp.Notes.show。有些动作方法是带参数的,可以在声明的时候预先绑定一些参数值,这样可以方便的添加一些 URI 别名。比如动作方法 Notes.show() 有一个参数 id 用来指明要显示的内容的 ID。如果参数 id 的值为 0,则会显示所有内容的一个列表。这样的话,就可以定义一个类似 GET /notes/all Notes.show(id:0) 的路由声明。这样暴露出来的 URI 更加简洁和易于记忆。
在路由文件中的路由声明是按照从上到下的优先级来进行匹配的。比较具体的 URI 模式应该放在比较通用的模式之前。对于静态文件,可以通过一个特殊的动作方法 staticDir 进行声明。比如 GET /files staticDir:files 就声明了 files 目录中包含的是静态文件。
参考:
http://www.ibm.com/developerworks/cn/java/j-lo-play/
play2.0 介绍:http://www.cnblogs.com/vamei/p/3691398.html
续写参考:
两个中英文网站。