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类定义绑定的例子:

@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链
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值