您的位置:首页 > 其它

Play framework 控制器(Controllers)

2017-08-02 20:23 106 查看
业务逻辑在域模型层中进行管理。作为客户端(通常是Web浏览器)不能直接调用此代码,一个域对象的功能暴露为代表的URI的资源。一个域对象的功能暴露为代表的URI的资源。客户机使用HTTP协议提供的统一API来操纵这些资源,并隐含底层业务逻辑。然而,这种映射资源域对象不是一个双向映射,粒度可以表示在不同的级别,有些资源可能是虚拟的,对于某些资源别名可以定义…这正是控制器所起的作用,在域对象模型和传输层时间之间提供粘合,作为模型层,控制器是纯java编写的,因此可以很容易访问和修改模型对象,像HTTP接口,控制器程序是面向Request/Response的;不同的设计模型有不同的策略,有些协议允许你直接访问域模型对象,这通常是EJB或者CORBA协议做的,在这些情况下设计风格RPC(远程过程调用),这些通信方式很难与Web体系结构兼容。像SOAP这样的一些技术试图通过Web提供对模型对象域的访问。然而,SOAP只是另一种RPC样式协议,在这种情况下,使用HTTP作为传输协议。它不是一个应用程序协议。Web的原理并不是从根本上面向对象的。因此需要一个层来适应HTTP到您喜欢的语言。Controller控制器概述一个Controller是一个java 类,封装在
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();
}

}
控制器中的每个公共静态方法称为动作。动作方法的签名总是:
public static void action_name(params...);
你可以在action方法签名中定义参数,这些参数将由来自相应HTTP参数的框架自动解析。通常,动作方法不包含返回语句。方法出口是通过调用结果方法完成的。在这个例子中,render(…)是一个结果方法,它执行并显示一个模板。检索HTTP参数HTTP请求包含数据,这些数据可以从中提取:URI路径, /clients/1541,1541是URI模式动态部分。查询字符串,/clients?id=1541请求体:如果请求是从一个HTML形式发送,请求体包含表单数据编码为x-www-urlform-encoded。在所有情况下,Play都会提取这些数据并构建一个包含所有HTTP参数的map<String,String[]>,主键是参数名,参数名是从:URI的动态部分的名称(在路由中指定)从查询字符串中获取的名称值对的名称部分。一个x-www-urlform-encoded体内容。使用参数mapparams对象可用于任何控制器类(它是在play.mvc.controller超类中定义的)),此对象包含当前请求所找到的所有HTTP参数。例如:
public static void show() {
String id = params.get("id");
String[] names = params.getAll("names");
}
你也可以要求Play为你做类型转换:
public static void show() {
Long id = params.get("id", Long.class);
}
但是,等等,有更好的方法来做到这一点:从动作方法签名您可以直接从action方法签名中检索HTTP参数。java的参数的名称必须为HTTP参数的相同。例如在这个请求中
/clients?id=1451
action方法可以通过在签名中声明参数id来检索id参数值:
public static void show(String id) {
System.out.println(id);
}
你可以使用其他的java类型,在这种情况下java将会把参数转换成对应的类型。
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);
}
}
高级的HTTP java绑定简单类型所有的基本类型和封装类型的java类型自动绑定。
int
long
boolean
char
byte
float
double
Integer
Long
Boolean
Char
String
,
Byte
Float
Double
.注意:如果HTTP请求中缺少参数,或者如果自动转换失败,包装类将被设置为null,原始类型将被设置为默认值。如果日期的字符串表示与下列模式之一匹配,则可以自动绑定日期对象:yyyy-MM-dd’T’hh:mm:ss’Z' // ISO8601 + timezoneyyyy-MM-dd’T’hh:mm:ss" // ISO8601yyyy-MM-ddyyyyMMdd’T’hhmmssyyyyMMddhhmmssdd'/‘MM’/'yyyydd-MM-yyyyddMMyyyyMMddyyMM-dd-yyMM'/‘dd’/'yy例如:
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将会根据你的本地使用默认的格式,指定默认的日期格式:设置一个默认的日期格式使用java.text.SimpleDateFormat模式,例如:
date.format=dd-MM-yyyy
这个属性也会影响到 
${date.format()}在模板中的显示,它也是模板中绑定日期时的默认格式:
默认:yyyy-MM-dd你也可以为不同的语言设置不同的日期格式,例如:
date.format.fr=dd-MM-yyyy
Calender日历结合日期一起工作,除了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);
}
创建的文件和原始文件有相同的名称,它存储在一个临时文件目录中并且会在request请求结束后删除掉,因此你要把它复制到一个安全目录中否则他会丢失。上传文件的MIME类型通常应该由HTTP请求的内容类型头指定。然而,当使用web浏览器上传文件时,这种情况将不会发生在不常见的类型之上。在这种情况下,你可以映射文件名的扩展MIME类型,使用play.libs.mimetypes类。
String mimeType = MimeTypes.getContentType(attachment.getName());
支持数组或集合类型所有支持的类型都可以作为数组或集合对象来检索:
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) {
…
}
一个查询字符串如下:
?client.name=John&client.phone=111-1111&client.phone=222-2222
将会把client的两个变量绑定到map上,第一个元素使用key name和值John,第二个元素使用key phone和值111-111,222-222POJO对象绑定Play也自动使用同一个简单的命名约定规则绑定任何模型类。
public static void create(Client client ) {
client.save();
show(client);
}
查询字符串来创建一个客户端使用这个action的样子:
?client.name=Zenexity&client.email=contact@zenexity.fr
Play创建一个Client实例并且解析HTTP参数名在Client对象属性,未解析的参数被安全的忽略掉,类型未匹配的也会被安全的忽略掉。参数绑定是递归完成的,这意味着您可以处理完整的对象图:
?client.name=Zenexity
&client.address.street=64+rue+taitbout
&client.address.zip=75009
&client.address.country=France
为了更新模型对象的列表,使用数组符号并引用对象的ID,例如:假设Client模型有一个Customer列表模型声明为List Customer customers模型。去更新Customers列表你应该提供一个查询字符串如下:
?client.customers[0].id=123
&client.customers[1].id=456
&client.customers[2].id=789
JPA对象绑定你可以自动绑定一个JPA对象使用HTTP来绑定java对象。你可以提供你自己的HTTPuser.id参数,当Play发现id参数,在编辑之前它加载匹配的实例从数据库中,HTTP请求参数提供的其它参数是被使用的,所以你可以保存它。
public static void save(User user) {
user.save(); // ok with 1.0.1
}
你可以使用JPA绑定修改以同样的方式完成对象图为POJO映射的作品,但是你 必须提供id对于你的每个子操作。
user.id = 1
&user.name=morten
&user.address.id=34
&user.address.street=MyStreet
自定义绑定结合系统 现在支持更多的定制@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允许你,去标记非绑定字段,解决安全潜在的问题,例如:
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) {
…
}
在这种情况下isAdmin字段将不会被绑定到
editProfile
 action上,即使用户恶意包含user.isAdmin=true字段以假冒的方式。play.data.binding.TypeBinder / TypeUnbinder@As注释完全支持你自定义注释,一个自定义的绑定和解绑是
TypeBinder
 / 
TypeUnbinder的子类,你在项目中定义的,例如:
public class MyCustomStringBinder implements TypeBinder<String>, TypeUnbinder<String>  {

public Object bind(String name, Annotation[] anns, String value,
Class clazz) {
return "!!" + value + "!!";
}

@Override
public void unBind(Map<String, Object> result, Object src, Class<?> srcClazz,
String name, Annotation[] annotations) throws Exception {
String value = (String) src;
if(value != null){
value = value.replaceAll("^!!", "").replaceAll("!!$", "");
}
result.put(name, value);
}
}
你可以在action使用,如:
public static void anyAction(@As(binder=MyCustomStringBinder.class)
String name) {
…
}
@play.data.binding.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]));}}
正如您所见,一个全局绑定器是一个带有注释的经典绑定器@play.data.binding.Global。外部模块可以为项目提供粘合剂。这使得定义可重用的绑定器扩展成为可能。结果类型action方法必须生成HTTP响应,做到这一点最简单的方法是发出一个结果对象。当一个结果对象被返回,正常的执行流被中断方法返回。
public static void show(Long id) {Client client = Client.findById(id);render(client);System.out.println("This message will never be displayed !");}
render(…)
 方法返回一个结果,并且停止更深层次的执行。返回一些文本内容renderText(…)方法返回一个简单的结果事件,它将一些文本直接写入到底层的HTTP响应;例如:
public static void countUnreadMessages() {Integer unreadMessages = MessagesBox.countUnreadMessages();renderText(unreadMessages);}
你可以格式化消息会用标准的java格式化语法:
public static void countUnreadMessages() {Integer unreadMessages = MessagesBox.countUnreadMessages();renderText("There are %s unread messages", unreadMessages);}
返回一个JSON字符串Play配有一个简单的返回字符串的方法,通过使用renderJSON(…)方法,这些方法返回一个JSON字符串并且设置响应内容类型为application/json。你可以指定自己的JSON字符串,或者传递一个Object对象,都会被GsonBuilder序列化。例如:
public static void countUnreadMessages() {Integer unreadMessages = MessagesBox.countUnreadMessages();renderJSON("{\"messages\": " + unreadMessages +"}");}
另外,如果你有一个更复杂的对象结构,您可能希望创建JSON使用GsonBuilder。
public static void getUnreadMessages() {List<Message> unreadMessages = MessagesBox.unreadMessages();renderJSON(unreadMessages);}
如果你需要更多的控制在JSON生成器时传递一个对象到renderjson(…)的方法,你也可以通过在serialisers gson和类型对象的自定义输出。返回一一个XML对象就像使用JSON方法一样,有几种方法直接从控制器呈现XML。renderxml(…)方法返回的XML字符串设置为text/xml内容类型。这里你可以指定自己的XML字符串,传递一个
org.w3c.dom.Document
 对象,或者传递一个POJO对象都将被 XStreamserialiser序列化。例如:
public static void countUnreadMessages() {Integer unreadMessages = MessagesBox.countUnreadMessages();renderXml("<unreadmessages>"+unreadMessages+"</unreadmessages>");}
另外,你也可以使用org.w3c.dom.Document对象:
public static void getUnreadMessages() {Document unreadMessages = MessagesBox.unreadMessagesXML();renderXml(unreadMessages);}
返回二进制内容提供二进制数据,例如数据存储在服务器端,使用
renderBinary
 方法,例如:如果你有一个User模型使用 
play.db.jpa.Blobphoto属性,添加一个控制器方法加载模型对象并且使用存储的MIME 类型渲染图片。
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);} 
下载一个文件作为附件你可以设置一个HTTP头来指示web浏览器将二进制响应视为“附件”,这通常导致web浏览器将文件下载到用户的计算机上,给renderBinary方法传递一个文件名,可以设置 
Content-Disposition头来设置文件名,
renderBinary(binaryData, user.photoFileName);
执行模板如果生成的内容是复杂的,你应该使用一个模板来生成响应。
public class Clients extends Controller {public static void index() {render();}}
模板名称是从Play规则中自动推断出来的,使用Controller和action解析默认模板的路径。在这个例子中调用的模板是:
app/views/Clients/index.html
在模板的范围内添加数据模板经常需要数据,你可以使用
renderArgs
 添加数据到模板范围。
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>
一个简单的方法来添加数据到模板范围你可以使用render(…)方法参数之间传递数据到模板:
public static void show(Long id) {Client client = Client.findById(id);render(client);}
在这种情况模板访问java中的变量名称必须保持相同。重要!你只能通过这种方式传递局部变量。指定另一个模板如果你不想使用默认模板,你可以使用renderTemplate(…)方法来指定自己的模板文件,通过模板名称作为第一个参数。
public static void show(Long id) {Client client = Client.findById(id);renderTemplate("Clients/showClient.html", id, client);}
重定向到另一个URLredirect(…)方法发出一个重定向事件,该事件又生成一个重定向响应。
public static void index() {redirect("http://www.zenexity.fr");}
Action链接没有等同于Servlet API的forward,一个HTTP请求只能调用一个action,如果你需要去调用另外一个action,你不得不重定向的浏览器去调用另外一个action,这样浏览器URL始终与执行的操作一致,并且 Back/Forward/Refresh操作会更简单。你可以发送重定向响应给任何一个action,只是调用一个java方法,java调用是由框架和正确的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 
自定义网页编码Play要求使用UTF-8编码,但有些情况下,有些响应或整个应用程序必须使用不同的编码。为当前响应自定义编码为当前响应指定编码,你可以这样做在你的控制器中。
response.encoding = "ISO-8859-1";
当使用除服务器默认值以外的编码发布表单时,你应该包含encoding/charset在表单中两次,在接收accept-charset属性和一个特殊的隐藏表单字段命名_charset_,accept-charset属性告诉浏览器要使用那种编码,_charset_属性告诉Play使用的是那种编码。
<form action="@{application.index}" method="POST" accept-charset="ISO-8859-1"><input type="hidden" name="_charset_" value="ISO-8859-1"></form>
拦截器一个控制器能够定义一个拦截器方法,控制器及其子类的所有action方法调用之前都会调用拦截器,这是一个有效的定义预处理方法并且对所有的action方法通用,验证一个用户是否被认证过,加载请求范围信息。这些方法必须是static的但不是public的,你必须使用有效的注释对这些方法进行标记。@Before控制器的action方法执行之前会先执行@before注解标记的方法。
public class Admin extends Controller {@Beforestatic void checkAuthentification() {if(session.get("user") == null) login();}public static void index() {List<User> users = User.findAll();render(users);}…}
如果你不想@Before注解标注的拦截器拦截所有的action请求,你可以指定一个排除过滤的action列表:
public class Admin extends Controller {@Before(unless="login")static void checkAuthentification() {if(session.get("user") == null) login();}public static void index() {List<User> users = User.findAll();render(users);}…}
或者如果你只想让@Before注解指定的action被拦截器拦截,可以指定如下:
public class Admin extends Controller {@Before(only={"login","logout"})static void doSomething() {…}…}
参数unless、only适用于@After、@Before和@Finally注解。@After注解@After会在控制器的每个action方法调用之后执行。
public class Admin extends Controller {@Afterstatic void log() {Logger.info("Action executed ...");}public static void index() {List<User> users = User.findAll();render(users);}…}
@Catch注解@Catch注解方法会在其他的action方法抛出异常的时候执行,抛出的异常会作为参数传递个@Catch注解方法。
public class Admin extends Controller {@Catch(IllegalStateException.class)public static void logIllegalState(Throwable throwable) {Logger.error("Illegal state %s…", throwable);}public static void index() {List<User> users = User.findAll();if (users.size() == 0) {throw new IllegalStateException("Invalid database - 0 users");}render(users);}}
与正常的java异常处理相比,你可以捕获一个超类来捕获更多的异常,如果你有多个捕获异常方法,你可以指定他们的优先级,它们可以按照优先级来执行。
public class Admin extends Controller {@Catch(value = Throwable.class, priority = 1)public static void logThrowable(Throwable throwable) {// Custom error logging…Logger.error("EXCEPTION %s", throwable);}@Catch(value = IllegalStateException.class, priority = 2)public static void logIllegalState(Throwable throwable) {Logger.error("Illegal state %s…", throwable);}public static void index() {List<User> users = User.findAll();if(users.size() == 0) {throw new IllegalStateException("Invalid database - 0 users");}render(users);}}
@Finally使用@Finally注解的方法会在控制器的action方法执行之后执行,无论控制器的action方法执行成功或者失败都会执行。
public class Admin extends Controller {@Finallystatic void log() {Logger.info("Response contains : " + response.out);}public static void index() {List<User> users = User.findAll();render(users);}…}
如果@Finally注解方法带有一个参数Throwable,异常将会被传入注解方法。
public class Admin extends Controller {@Finallystatic void log(Throwable e) {if( e == null ){Logger.info("action call was successful");} else{Logger.info("action call failed", e);}}public static void index() {List<User> users = User.findAll();render(users);}…}
控制器等级如果一个控制器类是另外一个控制器类的子类,拦截器充分利用拦截层次控制。使用@With注解添加更多的拦截器,因为JAVA不允许多重继承,依靠控制器层次结构来应用拦截器是非常有限的,但是你可以在一个完全不同的类中定义一些拦截器,并使用@With注解将他们与任何控制器连在一起。
public class Secure extends Controller {@Beforestatic void checkAuthenticated() {if(!session.containsKey("user")) {unAuthorized();}}} 
@With(Secure.class)public class Admin extends Controller {…}
Session和Flash范围如果必须在多个HTTP请求中保存数据,你可以保存数据在session或者Flash范围中,数据存储在Session范围中会在用户的整个会话中有效,数据存储在Flash范围中只会在下一个请求中有效。理解Session和Flash数据不是存储在服务器端是很重要的,而是被添加到每个子请求之中,使用cookie机制,因此数据大小是被限制的(最大4kb),并且你只能存储字符串数据。当然,cookie是使用秘钥签名的,因此客户端不能修改cookie数据(否则它将失效),Play session的目的不是用作缓存,如果需要缓存一些与特定会话相关的数据,你可以使用Play内置缓存机制,使用session.getId()键值使他们与特定的会话相关联:
public static void index() {List messages = Cache.get(session.getId() + "-messages", List.class);if(messages == null) {// Cache missmessages = Message.findByUser(session.get("user"));Cache.set(session.getId() + "-messages", messages, "30mn");}render(messages);}
如果你的浏览器关闭你的会话将会过期,除非你配置了application.session.maxAge.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: