控制器层(Controllers)
本章译者:
业务逻辑代码通常位于模型(model)层。客户端(⽐如浏览器)⽆法直接调⽤其中的代码,所以模型对象提供的功能,必须作为资源以URI⽅式暴露给外部。
客户端使⽤HTTP协议来操作这些资源,从⽽调⽤了内部的业务逻辑。但是,这种从资源到模型之间的映射是单向的:我们可以根据需要提供不同粒度的资源,可以虚拟出⼀些资源,还可以给某些资源起别名...
Controller层就是专门做这件事的:在模型层与传输层之间搭起⼀座桥梁。它使⽤与模型层同⼀种语⾔,以便访问和修改模型对象,但同时它⼜跟HTTP接⼝⼀样,是⾯向请求(Request)和响应(Response)的。
Controller层减少了HTTP与模型层之间的“阻抗不匹配”。
注意
不同的模型⽅案使⽤了不同的策略。有⼀些可以让我们直接访问模型对象,⽐如EJB或者Corba协议,它们使⽤RPC(远程过程调⽤)。这种交互⽅式与web很难兼容。
另⼀些技术如SOAP,尝试通过Web来访问模型层,但它也是⼀种PRC风格的协议,只是以HTTP为传输协议。它也不是⼀种程序协议。
Web的理念在根本上与⾯向对象不同,所以我们需要⼀个层来协调。
Controller综述
⼀个Controller就是⼀个位于controllers包中的类,其继承于play.mvc.Controller:
⽰例:
package controllers;
import models.Client;
import play.mvc.Controller;
public class Clients extends Controller {
public static void show(Long id) {
Client client = Client.findById(id);
render(client);
}
public static void delete(Long id) {
Client client = Client.findById(id);
client.delete();
}
}
Controller中每⼀个public static⽅法都被称为⼀个action。它的签名形如:
public static void action_);
你可以在action的⽅法签名中定义各种参数。Play会⾃动从相应的HTTP参数中,取出对应的值赋过去(并进⾏恰当的转换),这⼀点⾮常⽅便。
通常⼀个action不需要返回值。当我们在action中调⽤了⼀个能产⽣“结果”的⽅法后,action就退出了。在本例中,render(…)就是⼀个显⽰⼀个模板的可产⽣结果的⽅法。
获取HTTP参数
HTTP请求中可包含数据,这些数据可位于:
URI路径中: 如/clients/1541, 1541就是⼀个动态产⽣的参数.
Query String: /clients?id=1541.
request body: 如果提交了⼀个HTML表彰,将request body中将包含以x-www-urlform-encoded⽅式转换过的数据。
对于这些情况,Play都可以取得数据,并⽣成⼀个Map<String, String[]>。Key是参数名,来源于:
在conf/routes⽂件中定义的规则中的动态参数名
Query String中的name=value
通过表单提交的数据的name.
使⽤参数map
在所有的Controller类中都可以直接使⽤params这个对象。它是在play.mvc.Controller中定义的。这个对象中,包含了当前请求的所有数据。
⽰例:
public static void show() {
String id = ("id");
String[] names = All("names");
}
你还可以转换参数值的类型:
public static void show() {
Long id = ("id", Long.class);
}
其实,还有更好的办法来转换 :)
直接利⽤action⽅法的参数类型定义来转换
我们可以直接从action⽅法的参数定义中,直接拿到对应的参数值。⽅法中的参数名必须跟http传过来的参数名相同。
⽐如,对于如下的URL:
/clients?id=1451
我们可以定义⼀个包含id参数的action:
public static void show(String id) {
System.out.println(id);
}
我们还可以使⽤其它的类型,play会⾃动进⾏转换:
public static void show(Long id) {
System.out.println(id);
}
如果同⼀个参数有多个值,还可以把它声明为数组:
public static void show(Long[] id) {
for(String anId : id) {
System.out.println(anid);
}
}
甚⾄集合:
public static void show(List<Long> id) {
for(String anId : id) {
System.out.println(anid);
}
}
这⼀功能⾮常⽅便,其它的框架都很少提供(有⼀些提供了,但要求每个参数前都要加⼀个annotation,不太⽅便)。
内部原理是,Play会对每个action进⾏扫描,得到其参数信息(包括类型与参数名)。能过反射很容易得到参数类型,但得不到参数名,因为javac在编译java代码时,会忽略参数名信息。⽽Play内置了eclipse的编译器,并在编译时打开相关选项以记录参数名信息,所以才能成功取到。
异常
如果action⽅法中定义的某个参数,在http请求中不到对应的数据,则将会把它设为默认值(对象类型设为null,基础数字类型设为
0,boolean类型设为false)。如果到了,但是其值⽆法转换为指定的Java类型,则会将这⼀错误记录在validation对象中,并使⽤默认值代替。
HTTP to Java ⾼级绑定
简单类型
Java中所有基础和常⽤类型,都可以⾃动绑定:
int, long, boolean, char, byte, float, double, Integer, Long, Boolean, Char, String, Byte, Float,Double.
注意如果某个参数HTTP请求中没有提供,或者⾃动转换失败,则Object类型会被设为null,⽽基础类型会被设为它们的默认值。
Date
如果⼀个⽇期格式如下,则它可以⾃动转换并绑定:
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. 注意语种可⽤逗号分隔。注意参数lang的个数必须与value的个数相等。
如果没有使⽤@As注解,则Play将使⽤你的区域对应的默认⽇期格式。我们可在“f”中使⽤“date.format”为key来配置该格式.
⽇历(Calendar)
Calendar的绑定与date⼏乎完全⼀样,除了play将会根据你的区域设置选择相应的Calendar对象。@Bind注解也可在这⾥使⽤。
⽂件(File)
在Play中处理⽂件上传⾮常简单。使⽤经multipart/form-data编码的请求将⽂件post到服务器端,同时使⽤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);
}
Play将先取得上传的⽂件,保存在临时⽬录下,并使⽤上传的⽂件名。当这个request结束后,该⽂件将被删除,所以我们必须将它拷贝到⼀个安全的⽬录中,否则就不到了。
上传的⽂件的MIME类型通常应该在HTTP request的head中,以Content-type⽅式指定。但当我们通过浏览器上传⽂件时,⼀些不常见类型的⽂件不会指定。在这种情况下,我们可以使⽤play.libs.MimeTypes类将该⽂件的后缀名映射到⼀个MIME类型上。
String mimeType = Name());
play.libs.MimeTypes类在$PLAY_HOME/framework/src/play/libs/mime-types.properties中以⽂件的后缀为来寻对应的MIME类型。
你也可以通过来增加你⾃⼰的类型。
数组或集合
所有⽀持的类型,都可以通过数组或集合的形式取得:
public static void show(Long[] id) {
…
}
或者:
public static void show(List<Long> id) {
…
}
或者:
public static void show(Set<Long> id) {
…
}
Play还可以处理像Map<String, String>这样的绑定:
public static void show(Map<String, String> client) {
…
}
⼀个如下的query string:
client.name=John&client.phone=111-1111&client.phone=222-2222
将会把变量client绑定到⼀个含有两个元素的map上。第⼀个元素的key是name ,值是John,第⼆个key是phone值是111-1111, 222-2222. POJO对象绑定
Play还可以使⽤简单的命名约定将参数绑定到⼀个pojo对象上。
public static void create(Client client ) {
client.save();
show(client);
}
可以使⽤像下⾯这样的query string,来调⽤该action以创建⼀个client对象:
client.name=Zenexity&ail=contact@zenexity.fr
Play创建⼀个Client的实例,然后将HTTP参数中与Client对象属性同名的值赋过去。⽆法处理的参数将被安全的忽略,类型不匹配的也将安全忽略。
参数绑定是递归的,我们可以通过query string来创建⼀个完全的对象图:
client.name=Zenexity
&client.address.street=64+rue+taitbout
&client.address.zip=75009
&untry=France
如果我们想更新⼀列对象,我们可以使⽤数组形式来引⽤对象的ID。举例来说,假设Client模型有⼀列Customer模型,并声明
为List<Customer> customers形式。为了更新这⼀列Customers,我们应该提供⼀个如下的query string:
client.customers[0].id=123
&client.customers[1].id=456
&client.customers[2].id=789
JPA对象绑定
我们可以将⼀个JPA对象与HTTP⾃动绑定起来。
我们可以在HTTP参数中提供user.id字段。当Play发现这个字段时,它会先到数据库中取出相应的实例,然后把HTTP请求中其它的参数赋过去。所以我们可以直接save它。
public static void save(User user) {
user.save(); // ok with 1.0.1
}
我们可以使⽤同样的⽅式来更新完整的对象图,但是必须对每⼀个⼦对象提供ID:
user.id = 1
&user.name=morten
&user.address.id=34
&user.address.street=MyStreet
⾃定义绑定
绑定系统还⽀持⾃定义。
@play.data.binding.As
⾸先要讲的是@play.data.binding.As这个新注解,使⽤它,我们可以配置⼀个绑定。看下⾯的例⼦,我们使⽤它来指定⼀个Date的格式(该格式将被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注解可以与所有⽀持它的binder⼀起使⽤,包括你⾃⼰的定义的。例如,使⽤ListBinder:
public static void update(@As(",") List<String> items) {
…
}
它将会把⼀个以逗号分隔的字符串绑定到⼀个List上。
@play.data.binding.NoBinding
新的@play.data.binding.NoBinding注解允许我们定义⼀些“不应该被绑定”的字段,以防出现安全问题。例如:
public class User extends Model {
@NoBinding("profile") public boolean isAdmin;
@As("dd, MM yyyy") Date birthDate;
public String name;
}
public static void editProfile(@As("profile") User user) {
…
}
在这种情况下,在editProfile action中,就算某个居⼼不良的⽤户通过伪造请求提交了⼀个包含user.isAdmin=true的字段, isAdmin字段也不会被绑定.
play.data.binding.TypeBinder
@As 注解同样允许我们⾃定义⼀个完整的binder. ⼀个⾃定义的binder是TypeBinder的⼦类,我们可以在⾃⼰的项⽬中定义它。例如:public class MyCustomStringBinder implements TypeBinder<String> {
public Object bind(String name, Annotation[] anns, String value, Class clazz) { return "!!"; } }
我们可以在任意⼀个action中使⽤它:
public static void anyAction(@As(binder=MyCustomStringBinder.class)
String name) {
…
}
@play.data.binding.Global
mvc的controller我们还可以⾃定义⼀个全局的binder来处理某⼀个特定的类型。⽐如,我们给java.awt.Point类定义了⼀个这样的binder:
@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), Integer.parseInt(values) ); } }
你可以看到这个全局binder是⼀个典型的binder,只是使⽤了*@play.data.binding.Global*注解。我们在外部模块中定义这种binder,以在不同的项⽬中复⽤。
结果类型
⼀个action⽅法必须产⽣⼀个HTTP响应。最简单的⽅式就是⽣成⼀个Result对象。⼀旦某⼀个Result对象⽣成,该⽅法将⽴刻返回(后⾯的代码将不会被执⾏)。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论