谨以此系列文章进行学习、记录、分享。
注:参考了play官方文档 https://www.playframework.com/documentation/1.2.x/home
controller其实就是一个调度员,在一个请求过来的时候,来协调多个资源(对象),它使用与模型层同一种语言,以便访问和修改模型对象,但同时它又跟HTTP接口一样,是面向请求(Request)和响应(Response)的。
controller主要做的事情就是:接受请求参数,处理请求,响应请求。
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_name(params...);
参数的接收
1.params对象
它是在 play.mvc.Controller
中定义的。这个对象中,包含了当前请求的所有数据。
示例:
public static void show() {
String id = params.get("id");
String[] names = params.getAll("names");
}
你还可以转换参数值的类型:
public static void show() {
Long id = params.get("id", Long.class);
}
2.直接利用action方法的参数类型定义来转换
我们可以直接从action方法的参数定义中,直接拿到对应的参数值。方法中的参数名必须跟http传过来的参数名相同。
比如,对于如下的URL:
/clients?id=1451
我们可以定义一个包含 id
参数的action:
public static void show(String id) {
System.out.println(id);
}
内部原理是,Play会对每个action进行扫描,得到其参数信息(包括类型与参数名)。能过反射很容易得到参数类型,但得不到参数名,因为javac在编译java代码时,会忽略参数名信息。而Play内置了eclipse的编译器,并在编译时打开相关选项以记录参数名信息,所以才能成功取到。
HTTP to Java 高级绑定
简单类型
Java中所有基础和常用类型,都可以自动绑定:
int
, long
, boolean
, char
, byte
, float
, double
, Integer
, Long
, Boolean
, Char
, String
, Byte
, Float
, Double
.
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);
}
如果没有使用@As
注解,则Play将使用你的区域对应的默认日期格式。
文件(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结束后,该文件将被删除,所以我们必须将它拷贝到一个安全的目录中,否则就找不到了。
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&client.email=contact@zenexity.fr
JPA对象绑定
我们可以将一个JPA对象与HTTP自动绑定起来。
我们可以在HTTP参数中提供 user.id
字段。当Play发现这个字段时,它会先到数据库中取出相应的实例,然后把HTTP请求中其它的参数赋过去。所以我们可以直接save它。
public static void save(User user) {
user.save(); // ok with 1.0.1
}
我们可以使用同样的方式来更新完整的对象图,但是必须对每一个子对象提供ID
结果的返回
返回模板:
render(…) 自动以控制器和操作名称解析默认模板路径
将数据添加到模板范围(域)1.普通方法
通常模板需要数据。可以使用renderArgs对象将这些数据添加到模板范围:
public class Clients extends Controller {
public static void show(Long id) {
Client client = Client.findById(id);
renderArgs.put("client", client);
render();
}
}
将数据添加到模板范围(域)2.简单方法
可以使用render(…)方法参数将数据直接传递到模板,这种情况下页面访问的变量名和java变量具有相同的名称。只能以这种方式传递局部变量。
指定另一个模板
如果你不想使用默认模板,可以通过在 renderTemplate(…)
中的第一个参数中指定另一个模板名称。例如:
renderTemplate("Clients/showClient.html", id, client);
重定向
redirect(…)
方法会产生一个重定向事件,接着会产生一个HTTP Redirect响应。
返回一些文本内容
renderText(…)
返回一个JSON字符串
renderJSON(…)
返回XML字符串
renderXml(…)
方法返回内容类型设置为的XML字符串text/xml
。在这里,您可以指定自己的XML字符串,传递org.w3c.dom.Document
对象,或传递将由XStream序列化程序序列化的POJO。
返回二进制内容
renderBinary(…) 例如返回图片
下载文件作为附件
您可以设置HTTP标头来指示Web浏览器将二进制响应视为“附件”,通常会导致Web浏览器将文件下载到用户的计算机。为此,将文件名作为参数传递给renderBinary方法,这将使Play设置Content-Disposition响应头,提供文件名。例如,假设User上一个示例中的模型作为一个photoFileName属性:
renderBinary(binaryData, user.photoFileName);
拦截器(Interceptions)
一个controller可以定义多个拦截器方法。拦截器作用于一个controller及其所有子类的所有action方法上。对于定义一些所有action共用的操作时,使用拦截器非常有用,比如:检查用户是否已经登录(有没有访问权),截入request范围内的数据,等等。
这些方法必须是static
但不一定是public
。您必须使用有效的拦截标记来注释这些方法。
@Before
如果方法上有@Before
注解,则它将在该controller中的每一个action被调用前被执行。
@After
使用@After
注释注释的方法将在此Controller的每个操作调用后执行。
@Catch
@Catch
如果另一个动作方法抛出指定的异常,则会调用注释的方法。抛出的异常作为参数传递给@Catch方法。(就是其他方法抛异常了我来处理)
@Finally
使用@Finally
注释的方法总是在对此Controller执行每个操作后执行。
@With注释添加更多拦截器
因为Java不允许多重继承,所以依靠Controller层次结构应用拦截器可能非常有限。但是您可以在完全不同的类中定义一些拦截器,并将它们与使用@With注释的任何控制器进行链接。
会话和Flash范围
如果您必须在多个HTTP请求之间保留数据,则可以将其保存在会话或Flash范围中。存储在会话中的数据在整个用户会话期间可用,并且存储在闪存范围内的数据仅可用于下一个请求。
了解Session和Flash数据不会存储在服务器中,而是使用Cookie机制添加到每个后续HTTP请求中非常重要。因此,数据大小非常有限(最多4 KB),您只能存储String值。
当然,cookie是用密钥签名的,所以客户端不能修改cookie数据(否则会被无效)。播放会话不旨在用作缓存。如果需要缓存与特定会话相关的某些数据,则可以使用Play内置缓存机制,并使用session.getId()键来保持与特定用户会话相关。
例:
public static void index() {
List messages = Cache.get(session.getId() + "-messages", List.class);
if(messages == null) {
// Cache miss
messages = Message.findByUser(session.get("user"));
Cache.set(session.getId() + "-messages", messages, "30mn");
}
render(messages);
}
关闭Web浏览器时,会话将过期,除非您配置application.session.maxAge。
缓存与传统的Servlet HTTP会话对象具有不同的语义。您不能假定这些对象将始终位于缓存中。所以它强制你处理缓存未命中的情况,并保持你的应用程序完全无状态。