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链
相关文章推荐
- play框架使用起来(17)
- play框架使用起来(6)
- play框架使用起来(15)
- play框架使用起来(10)
- play框架使用起来(16)
- play框架使用起来(5)
- play框架使用起来(12)
- play框架使用起来(13)
- play框架使用起来(14)-高级指南
- play框架使用起来(18)
- play框架使用起来(8)
- play框架使用起来(11)
- play! 框架的搭建与基本使用方法
- play 1.x框架的配置与使用
- play使用起来(4)
- Play框架的验证码使用
- play框架配置使用过程中遇到的各种疑难问题汇总(学习同事经验)
- Web自动化框架LazyUI使用手册(2)--先跑起来再说(第一个测试用例-百度搜索)
- Web自动化框架LazyUI使用手册(2)--先跑起来再说(第一个测试用例-百度搜索)
- Play框架的基本使用