play框架使用起来(7)
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类定义绑定的例子:
@Globalpublic 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.showPOST /clients Clients.create
按照定义,Action链的生命周期为:
- 浏览器向/clients发送POST请求;
- 路由器调用Clients控制器中的create方法;
- create方法直接访问show方法;
- Java调用被拦截,路由器逆向生成带有id参数的URL来调用Clients.show;
- HTTP响应重定向为:/clients/3132;
- 浏览器地址栏中URL展现为:/clients/3132;
play框架使用起来(7)相关推荐
- ssh(Struts+spring+Hibernate)三大框架整合-简述
ssh(Struts+spring+Hibernate)三大框架配合使用来开发项目,是目前javaee最流行的开发方式,必须掌握: 注意: 为了稳健起见,每加入一个框架,我们就需要测试一下,必须通过才 ...
- Gin 框架学习笔记(03)— 输出响应与渲染
在 Gin 框架中,对 HTTP 请求可以很方便有多种不同形式的响应.比如响应为 JSON . XML 或者是 HTML 等. Context 的以下方法在 Gin 框架中把内容序列化为不同类型写 ...
- Gin 框架学习笔记(02)— 参数自动绑定到结构体
参数绑定模型可以将请求体自动绑定到结构体中,目前支持绑定的请求类型有 JSON .XML .YAML 和标准表单 form数据 foo=bar&boo=baz 等.换句话说,只要定义好结构体, ...
- QT学习之状态机框架
状态机框架 创建状态机
- 【Spring】框架简介
[Spring]框架简介 Spring是什么 Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Asp ...
- 开源自动化机器学习框架
20211101 在 Airbnb 使用机器学习预测房源的价格 https://blog.csdn.net/weixin_33735077/article/details/87976278?spm=1 ...
- Keras框架下的保存模型和加载模型
在Keras框架下训练深度学习模型时,一般思路是在训练环境下训练出模型,然后拿训练好的模型(即保存模型相应信息的文件)到生产环境下去部署.在训练过程中我们可能会遇到以下情况: 需要运行很长时间的程序在 ...
- Adam那么棒,为什么还对SGD念念不忘 (1) —— 一个框架看懂优化算法
机器学习界有一群炼丹师,他们每天的日常是: 拿来药材(数据),架起八卦炉(模型),点着六味真火(优化算法),就摇着蒲扇等着丹药出炉了. 不过,当过厨子的都知道,同样的食材,同样的菜谱,但火候不一样了, ...
- 一个框架看懂优化算法之异同 SGD/AdaGrad/Adam
Adam那么棒,为什么还对SGD念念不忘 (1) -- 一个框架看懂优化算法 机器学习界有一群炼丹师,他们每天的日常是: 拿来药材(数据),架起八卦炉(模型),点着六味真火(优化算法),就摇着蒲扇等着 ...
- 大三后端暑期实习面经总结——SSM微服务框架篇
博主现在大三在读,从三月开始找暑期实习,暑假准备去tx实习啦!总结下了很多面试真题,希望能帮助正在找工作的大家!相关参考都会标注原文链接,尊重原创! 目录 1. mvc.mvp.mvvm MVC架构 ...
最新文章
- 国际顶级学术会议SIGIR 2020开幕在即,重量级嘉宾带你窥探信息检索前沿
- 撩课-Java每天5道面试题第11天
- 一些今天看到的好句子
- ZOJ 38727(贪心)
- 扫地机器人粘住老鼠板怎么办_家里老鼠的危害性及如何有效灭鼠
- 【网址收藏】达内Django视频笔记收藏
- 关于CefSharp的坎坷之路
- uniapp的目录结构反思与整理 app.vue【base】pages.json【配置】main.json【框架入口文件】
- 传感器工作原理_荧光氧气传感器工作原理简介
- 统计学习方法读书笔记3-感知机SVM
- 怎么屏蔽还有照片_朋友圈该不该屏蔽父母?网友:发个自拍还被嫌丑,我能怎么办.........
- python 使打开的浏览器最大化
- 【毕业设计】深度学习人脸表情识别系统 - python
- 云桌面优缺点_相比传统PC,云桌面优缺点在哪里?
- Word长篇文档排版技巧
- Apache安全配置
- mysql全称_mysql全称
- errorCode 1045,state 28000: Access denied for user 'mysql'@'localhost' (using password: YES)
- docker学习笔记---基础入门
- reactive() 函数