玩转play framework ( by quqi99 )
p { margin-bottom: 0.21cm; }h1 { margin-bottom: 0.21cm; }h1.western { font-family: "Arial",sans-serif; font-size: 16pt; }h1.cjk { font-family: "宋体"; font-size: 16pt; font-style: normal; font-weight: bold; }h1.ctl { font-family: "Tahoma"; font-size: 16pt; font-weight: bold; }h2 { margin-bottom: 0.21cm; }h2.western { font-family: "Arial",sans-serif; font-size: 14pt; font-style: italic; }h2.cjk { font-family: "宋体"; font-size: 14pt; font-style: italic; }h2.ctl { font-family: "Tahoma"; font-size: 14pt; font-style: italic; }pre { font-family: "Courier New",monospace; }h3 { margin-bottom: 0.21cm; }h3.western { font-family: "Arial",sans-serif; }h3.cjk { font-family: "宋体"; font-style: normal; }h3.ctl { font-family: "Tahoma"; }a:link { color: rgb(0, 0, 255); }
内容目录
1 开发环境构建 1
1.1 下载二进制发布包 1
1.2 创建应用 1
1.3 集成eclipse 2
2 Hello World 2
3 Play 程序开发 3
3.1 视图层 3
3.1.1 模板元素 3
3.1.2 模板继承 3
3.2 控制层 3
3.3 模型层 4
3.4 无状态的体系结构 4
3.5 任务调度 5
3.6 支持non-web 应用 5
3.7 支持CRUD 应用 8
3.8 支持安全 9
http://download.playframework.org/releases/play-1.2.1.zip
下载完后解压,例如我解压的目录是: /home/workspace/play-1.2.1
zhanghua:play-1.2.1 root# ./play new QATool
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ /| |/ _' | || |_|
~ | __/|_|/____|/__ (_)
~ |_| |__/
~
~ play! 1.2.1, http://www.playframework.org
~
~ The new application will be created in /home/workspace/play-1.2.1/QATool
~ What is the application name? [QATool]
~
~ OK, the application is created.
~ Start it with : play run QATool
~ Have fun!
~
zhanghua:play-1.2.1 root# ./play run QATool
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ /| |/ _' | || |_|
~ | __/|_|/____|/__ (_)
~ |_| |__/
~
~ play! 1.2.1, http://www.playframework.org
~
~ Ctrl+C to stop
~
Listening for transport dt_socket at address: 8000
10:44:48,951 INFO ~ Starting /home/workspace/play-1.2.1/QATool
10:44:49,774 WARN ~ You're running Play! in DEV mode
10:44:49,876 INFO ~ Listening for HTTP on port 9000 (Waiting a first request to start) ...
这时,你能在浏览器上访问 http://localhost:9000 验证
zhanghua:play-1.2.1 root# ./play eclipsify QATool
然后,能过 File/Import/General/Existing project… 将 QATool 应用导入到 eclipse 中。
其次,要安装 Eclipse 插件,在 support/eclipse/ 目录下,要安装它,只需将你在该目录下找到的 JAR 文件,简单地复制到 Eclipse 安装目录的 dropins 文件夹下。
说明一下, eclipsify 命令生成若干个应用程序的启动器( launcher ),主启动器( main launcher )只能通过 Eclipse 的 Run As 命令使用。可以随时通过 Debug As 启动一个调试会话,然后使用 Connect JPDA launcher ,停止调试会话并不会导致服务器终止。如果你对应用程序做了任何重要的修改,如改变 classpath ,则需要使用 eclipsify 重新生成 Eclipse 配置文件。
在调试时,首先用 ./play run QATool 命令启动应用 ; 然后连接 JPDA 调试器即可。
在启动应用时,也可以先将所有 conf 目录、应用本身的 JAR 包、及 play 本身的 JAR 包加到环境变量后再执行下列命令即可 ( 通过 run as 启动,而不是 debug as) :
java -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
-Dapplication.path="${project_loc:QATool}"
-javaagent:"/home/workspace/play-1.2.1/framework/play-1.2.1.jar"
play.server.Server
vim app/views/Application/index.html
#{extends 'main.html' /}
#{set title:'Home' /}
#{if flash.error}
< p style =" color : #c00 " >
${flash.error}
</ p >
#{/if}
< form action = "@{Application.sayHello()}" method = "GET" >
< input type = "text" name = "myName" />
< input type = "submit" value = "Say hello!" />
</ form >
2 ) vim app/controllers/Application.java 添加一方法:
public static void sayHello( @Required String myName) {
if ( validation . hasErrors ()) {
flash .error( "Oops, please enter your name!" );
index ();
}
render (myName);
}
3 ) vim app/views/Application/sayHello.html
#{extends 'main.html' /}
#{set title:'Home' /}
<h1>Hello ${myName ?: 'guest'}!</h1>
<a href="@{Application.index()}">Back to form</a>
3 Play
程序开发
3.1
视图层
3.1.1
模板元素
Play
框架的模板技术使用的是 Groovy
语言。在模板中可以使用的动态元素如下说明:
${...}
用来对一个表达式求值,如 ${note.title}
是指取 note
对象的 title
属性。
@{...}
和 @@{...}
是指 JAVA
端控制器动作方法的相对 URL
及绝对 URL
。
&{...}
用来显示经国际化之后的消息内容。
*{...}*
注释
%{...}%
用来添加复杂的 Groovy
脚本,可以声明变量和添加语句。
#{...}
用来调用 Play
框架或自定义的标签。
举个例子,如对 topics
变量进行循环迭代,则用 Groovy
模板,如下:
%{ for(topic in topics){ }%
<td>
${topic.id}
</td>
%{ } }%
3.1.2
模板继承
Play
框架中可以使用
#{extends}
和
#{doLayout}
来实现模板之间的继承。
在父模板中可以包含任意的内容。在需要由子模板填充的位置,使用
#{doLayout /}
进行声明即可。在子模板中通过
#{extends}
来声明所继承的模板。如
#{extends 'main.html'}
就声明继承自模板
main.html
。当子模板被生成之后,将包含父模板中的内容。而子模板中只需要定义扩展的内容即可。
3.2
控制层
在控制层的动作方法完成了与业务逻辑相关的处理之后,需要把响应返回给客户端。响应的结果可能是正确完成,也可能是出现错误。 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 。
3.3
模型层
领域对象的实例一般需要持久化下来。最常见的持久化方式就是使用关系数据库。 Play
框架使用 JPA
规范来进行领域对象的持久化。具体的后台实现使用的是 Hibernate
。开发人员只需要使用 JPA
规范定义的标注,就可以声明领域的持久化行为。比较好的做法是将领域对象类继承自 Play
框架提供的
play.db.jpa.Model
类。
play.db.jpa.Model
类提供了一个域
id
作为对象的标识符,也是对应的数据库表中的主键。
play.db.jpa.JPASupport
类是
play.db.jpa.Model
的父类,提供了一些实用方法用来完成从领域对象到数据库之间的映射。
举些例子:
1
)分页查询:
List<Content> list = Content.
find
(
" status=? order by id asc"
, status).from(startPos).fetch(pageSize);
2
)查询数目
Content.
count
(
"status = ?"
, status);
3
)保存 user.save();
4
)更新
User user = Dao.
findByUsername
(
"quqi99"
);
user.setPassword( "111111" );
user.save();
可参考: Play! 连接 MySQL 配置 http://c4fun.iteye.com/blog/506959 http://www.playframework.org/documentation/1.1.1/jpa
3.4
无状态的体系结构
HTTP 协议本身就被设计成无状态的,采用请求 / 响应的模式。不同的请求之间并不存在相互关系。但是这种架构模式在开发某些 Web 应用的时候不是很方便。有些应用要求用户进行认证登录之后才能进行某些操作。同样的 URL ,认证和未认证用户看到的内容是不同的。而且用户认证成功之后,他应该在一段时间内保持这种认证状态。否则的话,用户每次都需要输入用户名和密码才 能访问受限的内容。对于这种情况,很多 Web 开发框架提供了会话的支持,允许应用保存一些与会话相关的数据。 Java Servlet 规范中的 javax.servlet.http. HttpSession 就是一种会话的接口。应用的服务器会负责维护每个会话相关的数据。这些数据可以通过一个会话 ID 来进行标识。这个标识会利用浏览器的 cookie 机制保存在浏览器端,也可以作为请求 URL 的参数来传递。服务器端通过此标识来识别每个会话。在处理相应的请求的时候,就可以根据会话 ID 来获取保存在服务器端上的会话数据。会话机制的问题是会影响应用的可伸缩性。如果一个应用使用多台服务器的话,就需要额外的机制来保证同一用户在不同机器 上面的会话是同步的。而无状态的实现 则不存在这个问题,对于某一个请求,由不同机器来处理的结果都是相同的。
Play 框架的设计架构就是无状态的。它没有提供服务器端的机制用来维护跨多个请求的数据。如果确实需要保存这样的数据的话,可以考虑下面几种方案:
保存在 Session 或 Flash 作用域中。 Play 框架中仍然有会话的机制,但是并没有提供在服务器端保存会话数据的能力。会话数据是保存在浏览器的 cookie 中的,由浏览器在每次请求的时候自动发送。通过这种方式来达到维护会话数据的目的。 由于会话数据是保存在 cookie 中,其大小是有限制的,一般不能超过 4K 字节,而且只能保存字符串类型的数据。 Flash 作用域和会话一样,也是通过 cookie 来保存的。所不同的是, Flash 作用域中的数据只在下次请求中是有效的。
保存在持久化的数据存储中,如数据库中。如果需要在多个请求中使用同一个领域对象的话,可以把这个对象的 ID 保存在 Session 或 Flash 作用域中,而在控制器动作方法中使用此 ID 来从数据库中查询相应的对象。
保存在暂时性数据存储中,如缓存中。 Play 框架内置了缓存的支持,通过调用类 play.cache.Cache
就可以对缓存进行操作。与使用持久化存储类似,缓存中的键的值可以保存在 Session 或 Flash 作用域中。
对于熟悉了 Java Servlet 规范的开发人员来说,需要一些时间来适应 Play 框架的这种无状态的体系结构。不过这种结构对于应用的可伸缩性来说,确实是非常有好处的。
3.5
任务调度
在 Web 应用开发中,有时候会需要定期执行一些调度任务,比如数据库备份和数据同步等。这些任务不是通过 HTTP 请求来触发的,而是定时执行的。 Play 框架提供了内置的任务调度支持的能力。创建新任务的时候,只需要继承自 play.jobs.Job
类,并覆写 doJob()
方法即可。如果要创建的任务有返回结果的话,覆写 doJobWithResult()
方法即可。任务创建完成之后,可以选择不同的调度方式。一种方式是在应用启动的时候执行一次。只需要在任务的 Java 类上添加标注 @OnApplicationStart
即可。对于定期执行的任务, Play 框架提供了两个标注:一个是 @Every
,用来按照固定的时间间隔调度任务,如 @Every("1h")
声明任务每个小时执行一次;另外一个是 @On
,用来声明描述调度策略的 CRON 表达式。
3.6
支持 non-web
应用
对于 non-web
应用,如果想要重要模型层的话,可用如下代码:
package
nonwebapp;
import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import models.Content;
import play.Play;
import play.db.DBPlugin;
import play.db.jpa.JPAPlugin;
import play.test.Fixtures;
/**
*
* author: huazhang since : 2011 - 6 - 21
*/
public abstract class PlayLoaderMain
{
public static void main(String[] args) throws Exception
{
try
{
String appPath = "/home/workspace/play-1.2.1/QATool" ;
File root = new File(appPath);
Play. init (root, System. getProperty ( "play.id" , "" ));
Thread. currentThread ().setContextClassLoader(Play. classloader );
Class<?> c = Play. classloader .loadClass( "nonwebapp.BuildAccounts" );
Method m = c.getMethod( "run" );
m.invoke(c.newInstance());
} catch (Exception e)
{
e.printStackTrace();
}
}
public void run() throws Exception
{
new DBPlugin().onApplicationStart();
new JPAPlugin().onApplicationStart();
JPAPlugin. startTx ( true );
exec();
JPAPlugin. closeTx ( false );
}
public abstract void exec() throws Exception;
}
package
nonwebapp;
import java.util.List;
import models.Content;
/**
*
* author: huazhang since : 2011 - 6 - 22
*/
public class BuildAccounts extends PlayLoaderMain
{
@Override
public void exec() throws Exception
{
List<Content> list = Content. findAll ();
System. out .println( "" );
}
}
再写个脚本调用它,脚本中加环境变量
#!/bin/bash
# huazhang
# 2011-06-28
#
# resolve links - $0 may be a softlink
THIS="$0"
while [ -h "$THIS" ]; do
ls =` ls - ld "$THIS"`
link=` expr "$ ls " : '.*-> /(.*/)$'`
if expr "$link" : '.*/.*' > / dev /null; then
THIS="$link"
else
THIS=` dirname "$THIS"`/"$link"
fi
done
# some directories
THIS_DIR=` dirname "$THIS"`
QATOOL_HOME=` cd "$THIS_DIR" ; pwd `
PLAY_HOME=` cd "$QATOOL_HOME/.." ; pwd `
if [ $# - lt 1 ] ; then
echo "USAGE: $0 class"
echo " e.g.: $0 nonwebapp.ImportData"
exit 1;
fi
if [ "$JAVA_HOME" = "" ]; then
echo "Error: JAVA_HOME is not set."
exit 1
fi
JAVA=$JAVA_HOME/bin/java
JAVA_HEAP_MAX=-Xmx1000m
for f in ${PLAY_HOME}/framework/*.jar; do
CLASSPATH=${CLASSPATH}:$f;
done
for f in ${PLAY_HOME}/framework/ lib /*.jar; do
CLASSPATH=${CLASSPATH}:$f;
done
if [ -d "${QATOOL_HOME}/ conf " ]; then
CLASSPATH=${CLASSPATH}:${QATOOL_HOME}/ conf
fi
for f in $QATOOL_HOME/ lib /*.jar; do
CLASSPATH=${CLASSPATH}:$f;
done
if [ -d "${QATOOL_HOME}/ tmp /classes" ]; then
CLASSPATH=${CLASSPATH}:${QATOOL_HOME}/ tmp /classes
fi
# default log directory & file
if [ "$QATOOL_LOGFILE" = "" ]; then
QATOOL_LOGFILE="$QATOOL_HOME/logs/QATool.log"
fi
if [ "x$JAVA_LIBRARY_PATH" != "x" ]; then
QATOOL_OPTS="$QATOOL_OPTS -Djava.library.path=$JAVA_LIBRARY_PATH"
fi
# run it
JAVA_OPTS="-server -Xms1600m -Xmx1600m -XX:PermSize=64M -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true"
exec "$JAVA" $QATOOL_OPTS - classpath "$CLASSPATH" "$@"
3.7
支持 CRUD
应用
1) vim application.conf
# Import the crud module
module.crud =${play.path}/modules/crud
2) vim routes
# Import CRUD routes
* /admin module:crud
3)
改动了配置,要重新运行下列命令:
zhanghua:play-1.2.1 root# ./play eclipsify QATool
4)
新建 Control
package
controllers;
/**
*
* author: huazhang
* since : 2011 - 6 - 27
*/
public class Users extends CRUD
{
}
5) 新建 Model
/**
*
* author: huazhang
* since : 2011 - 6 - 27
*/
@Entity
public class User extends Model
{
@Required
@MaxSize (256)
public String username ;
@Required
public String password ;
@Required
public String role ;
启动后访问 http://localhost:9000/admin 即可
7) 客户化字符串 , vim conf/messages, 添加:
username= 用户名
password= 密码
role= 角色
8) 客户化表现层,执行下列命令创建 /app/views/Users/list.html
zhanghua:play-1.2.1 root# ./play crud:ov --template Users/list
3.8
支持安全
http://www.playframework.org/documentation/1.0/secure
1) vim application.conf
# Import the
secure
module
module.secure=${play.path}/modules/secure
2) vim routes
# Import Secure routes
* / module:secure
3)
改动了配置,要重新运行下列命令:
zhanghua:play-1.2.1 root# ./play eclipsify QATool
4)
修改 Controller,
添加 With
及 Check
两个标签:
@With
(Secure.
class
)
public class Application extends Controller {
@Check (Constant. ROLE_ADMIN )
public static void index() {
String user = Security. connected ();
System. out .println( "" );
render ();
}
5) 新建一个自定义 Security 类,如下:
/**
*
* author: huazhang
* since : 2011 - 6 - 28
*/
public class Security extends Secure.Security
{
static boolean authentify(String username, String password)
{
User user = Dao. findByUsername (username);
return user != null && user. password .equals(password);
}
static boolean check(String role) {
String loginUser = connected ();
User user = Dao. findByUsername (loginUser);
return user== null ? false : role.equalsIgnoreCase(user.getRole());
}
}