您的位置:首页 > 其它

play框架使用起来(7)

2016-11-03 16:04 417 查看


1 高级HTTP绑定#

简单类型

Play可以实现所有Java原生的简单数据类型的自动转换,主要包括:int,long,boolean,char,byte,float,double,Integer,Long,Boolean,Char,String,Float,Double。

日期类型

如果HTTP参数字符串符合以下几种数据格式,框架能够自动将其转换为日期类型:

yyyy-MM-dd'T'hh:mm:ss’Z' // ISO8601 + timezone
yyyy-MM-dd'T'hh:mm:ss" // ISO8601
yyyy-MM-dd
yyyyMMdd'T'hhmmss
yyyyMMddhhmmss
dd'/'MM'/'yyyy
dd-MM-yyyy
ddMMyyyy
MMddyy
MM-dd-yy
MM'/'dd'/'yy

而且还能通过@As注解,指定特定格式的日期,例如:

archives?from=21/12/1980


public static void articlesSince(@As("dd/MM/yyyy") Date from) {
List<Article> articles = Article.findBy("date >= ?", from);
render(articles);
}


也可以根据不同地区的语言习惯对日期的格式做进一步的优化,具体如下:

public static void articlesSince(@As(lang={"fr,de","*"},
value={"dd-MM-yyyy","MM-dd-yyyy"}) Date from) {
List<Article> articles = Article.findBy("date >= ?", from);
render(articles);
}


在这个例子中,对于法语和德语的日期格式是dd-MM-yyyy,其他语言的日期格式是MM-dd-yyyy。语言值可以通过逗号隔开,且需要与参数的个数相匹配。

如果没有使用@As注解来指定,Play会采用框架默认的日期格式。为了使默认的日期格式能够正常工作,按照以下方式编辑application.conf文件:

date.format=yyyy-MM-dd


在application.conf文件中设置默认的日期格式之后,就可以通过${date.format()}方法对模板中的日期进行格式化操作了。

日历类型

日历类型和日期类型非常相像,当然Play会根据本地化选择默认的日历类型。读者也可以通过@Bind注解来使用自定义的日历类型。

文件类型

在Play中处理文件上传是件非常容易的事情,首先通过multipart/form-data编码的请求将文件发送到服务器,然后使用java.io.File类型提取文件对象:

public static void create(String comment, File attachment) {
String s3Key = S3.post(attachment);
Document doc = new Document(comment, s3Key);
doc.save();
show(doc.id);
}


新创建文件的名称与原始文件一致,保存在应用的临时文件下(Application_name/tmp)。在实际开发中,需要将其拷贝到安全的目录,否则在请求结束后会丢失。

数组和集合类型

所有Java支持的数据类型都可以通过数组或者集合的形式来获取。数组形式:

public static void show(Long[] id){
...
}

List形式:

public staic void show(List<Long> id){
...
}

集合形式:

public static void show(Set<Long> id){
...
}


Play还可以处理Map<String, String>映射形式:

public static void show(Map<String, String> client) {
...
}


例如下面的查询字符串会转化为带有两个元素的map类型,第一个元素key值为name,value为John;第二个元素key值为phone,value为111-1111, 222-2222。:

?user.name=John&user.phone=111-1111&user.phone=222-2222


POJO对象绑定

Play使用同名约束规则(即HTTP参数名必须与模型类中的属性名一致),自动绑定模型类:

public static void create(Client client){
client.save();
show(client);
}


以下的查询字符串可以通过上例的Action创建client:

?client.name=Zenexity&client.email=contact@zenexity.fr


框架通过Action创建Client的实例,并将HTTP参数解析为该实例的属性。如果出现参数无法解析或者类型不匹配的情况,会自动忽略。

参数绑定是递归执行的,这意味着可以深入到关联对象:

?client.name=Zenexity
&client.address.street=64+rue+taitbout
&client.address.zip=75009
&client.address.country=France


Play的参数绑定提供数组的支持,可以将对象id作为映射规则,更新一组模型对象。假设Client模型有一组声明为List<Customer>的customers属性,那么更新该属性需要使用如下查询字符串:

?client.customers[0].id=123
&client.customers[1].id=456
&client.customers[2].id=789



2 JPA对象绑定#

通过HTTP参数还可以实现JPA对象的自动绑定。Play会识别HTTP请求中提供的参数user.id,自动与数据库中User实例的id进行匹配。一旦匹配成功,HTTP请求中的其他User属性参数可以直接更新到数据库相应的User记录中:

public static void save(User user){
user.save();
}


和POJO映射类似,可以使用JPA绑定来更改对象,但需要注意的是必须为每个需要更改的对象提供id:

user.id = 1
&user.name=morten
&user.address.id=34
&user.address.street=MyStreet



3 自定义绑定#

绑定机制支持自定义功能,可以按照读者的需求,自定义参数绑定的规则。

@play.data.binding.As

@play.data.binding.As注解可以依据配置提供绑定的支持。下例使用DateBinder指定日期的数据格式:

public static void update(@As("dd/MM/yyyy") Date updatedAt) {
...
}


@As注解还具有国际化支持,可以为每个本地化提供专门的注解:

public static void update(
@As(
lang={"fr,de","en","*"},
value={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"}
)
Date updatedAt
) {
...
}


@As注解可以和所有支持它的绑定一起工作,包括用户自定义的绑定。以下是使用ListBinder的例子:

public static void update(@As(",") List<String> items) {
...
}


上例中的绑定使用逗号将字符串分隔成List。

@play.data.binding.NoBinding

@play.data.binding.NoBinding注解允许对不需要绑定的属性进行标记,以此来解决潜在的安全问题。比如:

//User为Model类
public class User extends Model {
@NoBinding("profile") public boolean isAdmin;
@As("dd, MM yyyy") Date birthDate;
public String name;
}

//editProfile为Action方法
public static void editProfile(@As("profile") User user) {
...
}


在上述例子中,user对象的isAdmin属性始终不会被editProfile方法(Action)所修改,即使有恶意用户伪造POST表单提交user.isAdmin=true信息,也不能修改user的isAdmin权限。

play.data.binding.TypeBinder

@As注解还提供完全自定义绑定的功能。自定义绑定必须是TypeBinder类的实现:

public class MyCustomStringBinder implements TypeBinder<String> {

public Object bind(String name, Annotation[] anns, String value,
Class clazz) {
return "!!" + value + "!!";
}
}


定义完成后,就可以在任何Action中使用它:

public static void anyAction(@As(binder=MyCustomStringBinder.class) String name) {
...
}


@play.data.binding.Global

Play中还可以自定义全局Global绑定。以下是为java.awt.Point类定义绑定的例子:

@Global
public class PointBinder implements TypeBinder<Point> {

public Object bind(String name, Annotation[] anns, String value,
Class class) {
String[] values = value.split(",");
return new Point(
Integer.parseInt(values[0]),
Integer.parseInt(values[1])
);
}
}


因此外部模块很容易通过自定义绑定来提供可重用的类型转换组件。

3、结果返回

Action方法需要对客户端作出HTTP响应,最简单的方法就是发送结果对象。当对象发送后,常规的执行流程就会中断。以下面这段代码为例,最后一句System.out.println的输出不会被执行:

public static void show(Long id) {
Client client = Client.findById(id);
render(client);
System.out.println("This message will never be displayed !");
}


render(…)方法向模板发送client对象,之后的其他语句将不会执行,所以在控制台中,并不会打印出“This message will never be displayed !”。


3.1 返回文本内容#

renderText(…)方法直接将文本内容写到底层HTTP响应中:

public static void countUnreadMessages(){
Integer unreadMessages=MessagesBos.countUnreadMessage();
renderText(unreadMessages);
}


也可以通过Java标准的格式化语法对输出的文本进行处理:

public static void countUnreadMessages(){
Integer unreadMessages=MessagesBox.countUnreadMessages();
renderText("There are %s unread messages",unreadMessages);
}



3.2 返回JSON字符串#

越来越多的应用使用JSON作为数据格式进行交互,Play对此进行了很好的封装,只需要使用renderJSON(…)方法就可以轻松地返回JSON字符串。在使用renderJSON(…)方法时,Play会自动将服务器返回的响应的content type值设置为application/json,并且将renderJSON(...)方法中的参数以JSON格式返回。

在使用renderJSON(...)方法时,可以输入字符串格式的参数,自行指定JSON返回的内容。

public static void countUnreadMessages() {
Integer unreadMessages = MessagesBox.countUnreadMessages();
renderJSON("{\"messages\": " + unreadMessages +"}");
}


以上范例在使用renderJSON(...)方法时,传入了拼接成JSON格式的字符串参数。Play框架会对其进行自动设置,改变content type的值为application/json。

当然,renderJSON(...)方法的功能并不只有这些。因为大部分的应用需求,都会要求服务端返回比较复杂的JSON格式,如果都采用字符串拼接的方式组成JSON内容,就太不人性化了。renderJSON(...)的输入参数还可以是复杂的对象,如果采用这种方式使用renderJSON(...)方法,Play在执行renderJSON(...)时,底层会先调用GsonBuilder将对象参数进行序列化,之后再将复杂的对象以JSON的格式返回给请求。这样开发者就可以完全透明地使用renderJSON(...)方法,不需要做其他的任何操作了,以下代码范例将会展示renderJSON(...)的这个功能。

public static void getUnreadMessages() {
List<Message> unreadMessages = MessagesBox.unreadMessages();
renderJSON(unreadMessages);
}



3.3 返回XML字符串#

与使用renderJSON(...)方法返回JSON内容类似,如果用户希望以XML格式对内容进行渲染,可以在Controller控制器中直接使用renderXml(…)方法。 使用renderXml(...)方法时,Play会自动将服务器返回的响应的content type值设置为application/xml。

在使用renderXml(...)方法时,可以输入字符串格式的参数,自行指定XML返回的内容。

public static void countUnreadMessages() {
Integer unreadMessages = MessagesBox.countUnreadMessages();
renderXml("<unreadmessages>"+unreadMessages+"</unreadmessages>");
}


如果希望将复杂的对象以XML格式进行渲染,可以在使用renderXml(...)方法时输入org.w3c.dom.Document格式的对象,或者直接输入POJO对象。以POJO对象作为参数使用renderXml(...)方法时,Play会使用XStream将其进行序列化操作。同样的,这些序列化操作都不需要由开发者去做,全部交给Play就行,开发者需要做的就是按照规范简单地调用renderXml(...)方法即可。

public static void getUnreadMessages() {
Document unreadMessages = MessagesBox.unreadMessagesXML();
renderXml(unreadMessages);



3.4 返回二进制内容#

Play为开发者提供了renderBinary(...)方法,可以非常方便的返回二进制数据(如存储在服务器里的文件、图片等)给客户端。以下代码范例将会展示如何使用renderBinary(...)方法进行二进制图片的渲染。

public static void userPhoto(long id) {
final User user = User.findById(id);
response.setContentTypeIfNotSet(user.photo.type());
java.io.InputStream binaryData = user.photo.get();
renderBinary(binaryData);
}


首先,开发者需要建立用于持久化的域模型User,该User模型具有play.db.jpa.Blob类型的属性photo。play.db.jpa.Blob是经过Play封装的特有的属性类型,可以很方便的处理二进制数据。之后,在Controller控制器中使用时,需要调用域模型的findById(...)方法加载持久化的数据,并将图片以二进制数据流InputStream的形式进行渲染。


3.5 下载附件功能#

如果开发者希望将存储在服务器端的文件,采用下载的形式渲染给客户端用户,需要对HTTP的header进行设置。通常的做法是通知Web浏览器将二进制响应数据以附件的形式,下载至用户的本地电脑上。在Play中完成这个功能非常简单,只需要在使用renderBinary(...)方法时多传入一个文件名的参数即可。这样做会触发renderBinary(...)的额外功能,提供文件名并设置响应头的Content-Disposition属性。之后二进制文件(包括图片)将会以附件下载的形式,渲染给用户。

public static void userPhoto(long id) {
final User user = User.findById(id);
response.setContentTypeIfNotSet(user.photo.type());
java.io.InputStream binaryData = user.photo.get();
renderBinary(binaryData, user.photoFileName);
}



3.6 执行模板#

如果需要响应的内容比较复杂,那么就应该使用模板来进行处理:

public class Clients extends Controller{
public static void index(){
render();
}
}


模板的名称遵从Play的约束规则,默认的模板路径采用控制器和Action的名称相结合的方式来定义,比如在上述例子中,模板对应的路径为:app/views/Clients/index.html。


3.7 为模板作用域添加数据#

通常情况下模板文件都需要数据进行显示,可以使用renderArg()方法为模板注入数据:

public class Clients extends Controller {

public static void show(Long id) {
Client client = Client.findById(id);
renderArgs.put("client", client);
render();
}

}


在模板执行过程当中,client变量可以被使用:

<h1>Client ${client.name}</h1>



3.8 更简单方式#

这里介绍一种更简单的方式向模板传递数据。直接使用render(…)方法注入模板数据:

public static void show(Long id){
Client client=Client.findById(id);
render(client);
}


以该方式进行数据传递,模板中可访问的变量与Java本地变量的名称(也就是render()方法中的参数名)一致。当然也可以同时传递多个参数:

public static void show(Long id){
Client client=Client.findById(id);
render(id,client);
}


注意:

render()方法只允许传递本地变量。


3.9 指定其他模板进行渲染#

如果读者不希望使用默认的模板进行渲染,那么可以在renderTemplate(…)方法的第一个参数中指定其他自定义的模板路径,例如:

public static void show(Long id) {
Client client = Client.findById(id);
renderTemplate("Clients/showClient.html", id, client);



3.10 重定向URL#

redirect(…)方法产生HTTP重定向响应,可以将请求转发到其他URL:

public static void index(){
redirect("http://www.oopsplay.org");
}



3.11 自定义Web编码#

Play推荐开发者使用UTF-8作为应用开发的编码格式,如果不进行任何设置,Play框架默认使用的也就是UTF-8格式。但是具体情况并不总是这么理想,有些特殊的需求可能要求某些响应(response)的格式为ISO-8859-1,或者要求整个应用都必须保持ISO-8859-1编码。

为当前响应设置编码格式

如果需要改变某一个响应(response)的编码格式,可以直接在Controller控制器中进行修改,具体做法如下所示:

response.encoding = "ISO-8859-1";


当开发表单提交功能时,如果开发者希望某一表单提交的内容采用非框架默认使用的编码(即Play框架采用默认的编码格式UTF-8,而该form表单提交的内容希望采用ISO-8859-1编码格式),Play的做法有一些特殊。在书写form表单的HTML代码时,需要对采用何种编码格式进行两次标识。首先需要在<form>标签中添加accept-charset属性(如:accept-charset="ISO-8859-1"),accept-charset属性会通知浏览器当form表单提交的时候,采用何种编码格式;其次,需要在form表单中添加hidden隐藏域,name属性规定为“_charset_”,value属性为具体需要的编码格式,这样做的目的是当form提交的时候,可以通知服务端的Play采用何种编码方式,具体范例如下:

<form action="@{application.index}" method="POST" accept-charset="ISO-8859-1">
<input type="hidden" name="_charset_" value="ISO-8859-1">
</form>


定义全局编码格式

通常情况下,整个应用应该保持统一的编码格式。如果开发者需要设置应用全局的编码格式,可以在application.conf配置文件中修改application.web_encoding属性,配置相应的编码。

4、Action链

lay中的Action链与Servlet API中的forward不尽相同。Play的每次HTTP请求只能调用一个Action,如果需要调用其他的Action,那么必须将浏览器重定向到相应的URL。在这种情况下,浏览器的URL始终与正在执行的Action保持对应关系,使得后退、前进、刷新操作更加清晰。

调用控制器中其他Action方法也可以实现重定向,框架会拦截该调用并生成正确的HTTP重定向。具体实现如下:

public class Clients extends Controller {

public static void show(Long id) {
Client client = Client.findById(id);
render(client);
}

public static void create(String name) {
Client client = new Client(name);
client.save();
show(client.id);
}
}


相应的路由规则定义如下:

GET            /clients/{id}                              Clients.show
POST           /clients                                   Clients.create


按照定义,Action链的生命周期为:

浏览器向/clients发送POST请求;

路由器调用Clients控制器中的create方法;

create方法直接访问show方法;

Java调用被拦截,路由器逆向生成带有id参数的URL来调用Clients.show;

HTTP响应重定向为:/clients/3132;

浏览器地址栏中URL展现为:/clients/3132;

Action链
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: