08.Play.libs库包
play.libs包包含了很多有用的库,以帮助实现通用的编程任务。
大多数据库都是简单的帮助类,而且非常易懂易用:
下面的内容着重介绍一些非常重要的库。
用XPath解析XML
XPath或许是最容易解析XML文档的工具了,而且不需要使用代码生成工具。play.libs.XPath库为高效完成解析任务提供了所有需求。
XPath可以操作所有的org.w3.dom.Node类型:
org.w3.dom.Document xmlDoc = … // 找到 a Document somewhere
for(Node event:XPath.selectNodes("events//event", xmlDoc)) {
String name =XPath.selectText("name", event);
String data =XPath.selectText("@date", event);
for(Node place:XPath.selectNodes("//place", event)) {
Stringplace = XPath.selectText("@city", place);
…
}
…
}
Web Service client
play.libs.WS提供了一个强大的http客户端,Under the hood it uses Async HTTP client.
创建一个请求非常容易:
HttpResponse res =WS.url("http://www.google.com").get();
一旦获得HttpResponse对象后,就可以访问所有的response属性了:
int status = res.getStatus();
String type = res.getContentType();
也可获得不同类型的内容体:
String content = res.getString();
Document xml = res.getXml();
JsonElement json = res.getJson();
InputStream is = res.getStream();
也可以非阻塞方式使用async API来创建一个http请求。然后就可以得到一个Promise
Promise
"http://www.google.com"
).getAsync();
Functional programming with Java功能扩展?
play.libs.F库从功能编程(functional programming)带来了许多非常有用的概念constructs。这些概念用于处理复杂的抽象环境。这些概念之前也有涉及:
Option
在某些情况下,函数可能不会返回结果(比如find方法),通常做法(非常糟糕)是返回null,这样做实际上非常危险,因为函数的返回类型不清晰。Option
/* 安全除法(绝不抛出ArithmeticException运行时错误) */
public Option
if (b == 0)
returnNone();
else
returnSome(a / b);
}
用法:
Option
if (q.isDefined()) {
Logger.info("q = %s", q.get()); // "q = 8.4"
}
这儿还有更便利的语法,这是因为Option
for (double q : div(42, 5)) {
Logger.info("q = %s", q); // "q = 8.4"
}
仅在div成功的情况下,for循环才被执行一次。
Tuple类包装了两种类型的对象A和B。使用_1和_2域可以分别找回A和B,如:
public Option
final Matchermatcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email);
if(matcher.matches()) {
returnSome(Tuple(matcher.group(1), matcher.group(2)));
}
return None();
}
然后:
for (Tuple
Logger.info("name = %s", email._1); // "name = foo"
Logger.info("server = %s", email._2); // "server =bar.com"
}
T2类是Tuple的别名。处理三个元素时使用T3类,一直可到T5。
Pattern Matching模式匹配
有些时候我们需要在java里进行模式匹配。遗憾的是java没有内建的模式匹配机制,java也缺乏功能性概念,所以很难增加到库里。在这里,我们采取的方案并不算太坏。
我们的主意是使用最后一次“for loop”语法来实现基本的模式匹配任务。模式匹配必须检测对象是否匹配必须的条件,还要能提取感兴趣的值。play使用的模式匹配库位于play.libs.F:
示例,假设已经有一个对象类型的引用,现在想检测这个对象是否是String类型,并且是以‘command:’字符串开始的。
标准方式为:
Object o = anything();
if(o instanceof String &&((String)o).startsWith("command:")) {
String s =(String)o;
System.out.println(s.toUpperCase());
}
使用Play模式匹配库,就可以这样写:
for(String s:String.and(StartsWith("command:")).match(o)) {
System.out.println(s.toUpperCase());
}
只有在条件符合的情况下,for循环才会被执行一次,并且会自动提取字符串值,而不需要进行转换。这是因为每个对象都是类型安全的,并且由编译器进行检测,不需要进行转换。
Promises
Promise是play定制的“将来Future”类型。事实上一个Promise
Promise实例可以用在任何Future实例里(比如Jobs, WS.async,等待)。
Promises还可进行组合,比如:
Promise p = Promise.waitAll(p1, p2, p3)
Promise p = Promise.waitAny(p1, p2, p3)
Promise p = Promise.waitEither(p1, p2, p3)
OAuth
OAuth 是一个开源协议的安全API验证, 应用地桌面和web应用程序。
这里有两种不同的定义: OAuth 1.0和OAuth 2.0。Play提供库来以消费者方式连接至可能的服务。
标准步骤为:
play会自动完成许多处理过程。
OAuth 1.0
OAuth 1.0功能库是通过play.libs.OAuth类提供的,这个类基于oauth-signpost。主要用于Twitter 和Google的验证服务。
要想连接到一个服务,你需要使用下面的信息创建一个OAuth.ServiceInfo实例,以获取service provider:
access token可通过如下方式找到:
public static void authenticate() {
// TWITTER 是OAuth.ServiceInfo对象
// getUser() 用于返回当前用户
if(OAuth.isVerifierResponse()) {
// 得到verifier检验者
// 然后使用请求tokens得到access tokens
OAuth.Response resp = OAuth.service(TWITTER).retrieveAccessToken(
getUser().token, getUser().secret);
// 存储它们并返回index
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
index();
}
OAuth twitt =OAuth.service(TWITTER);
Response resp =twitt.retrieveRequestToken();
//得到未经授权的标志tokens
//在继续之前先要保存
getUser().token= resp.token; getUser().secret = resp.secret;
getUser().save()
// 跳转用户到验证页
redirect(twitt.redirectUrl(resp.token));
}
现在可以使用token对通过分配的请求执行下面的调用:
mentions = WS.url(url).oauth(TWITTER, getUser().token,getUser().secret).get().getString();
尽管这个事例没有进行错误检测,但在生产环境,还是应该进行检测。OAuth.Response对象拥有一个error域,错误发生的时候,其中含有错误的内容。很多情况下不能给用户授权的原因是提供者已经下线或错误太多。
完整的示例见samples-and-tests/twitter-oauth。
OAuth 2.0
OAuth 2.0比OAuth 1.0更简单,这是因为它不需要调用signing请求。主要用于Facebook 和37signals。
play.libs.OAuth2类提供了该支持。
为了连接到服务,你需要使用下面的信息创建一个OAuth2实例,从服务提供者获取:
public static void auth() {
// FACEBOOK 是一个OAuth2对象
if(OAuth2.isCodeResponse()) {
// authUrl必须和retrieveVerificationCode调用一致
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl);
//如果有错误发生则为null
StringaccessToken = response.accessToken;
//调用成功则为null
OAuth2.Error = response.error;
//存储accessToken,在请求服务时要用到
index();
}
// authUrl是一个包含了服务绝对URL的字符串
// 应该跳转回来
// 下面将触发一个跳转
FACEBOOK.requestVerificationCode(authUrl);
}
一旦获取当前用户的access token,就可以用它来查询服务:
WS.url(
"https://graph.facebook.com/me?access_token=%s", access_token
).get().getJson();
完整示例见samples-and-tests/facebook-oauth2。
OpenID
OpenID 是一个开源的分散式身份认证系统。在你的系统里不需要保存特定用户的信息就可以很容易接受新的用户。只需通过它们的OpenID跟踪验证用户即可。
本示例提供一个轻量级的演示,用于演示如果使用OpenID验证用户:
play.libs.OpenID类提供了OpenID功能:
@Before(unless={"login","authenticate"})
static void checkAuthenticated() {
if(!session.contains("user")){
login();
}
}
public static void index() {
render("Hello %s!", session.get("user"));
}
public static void login() {
render();
}
public static void authenticate(String user) {
if(OpenID.isAuthenticationResponse()) {
UserInfoverifiedUser = OpenID.getVerifiedID();
if(verifiedUser == null) {
flash.error("Oops. Authentication has failed");
login();
}
session.put("user", verifiedUser.id);
index();
} else {
if(!OpenID.id(user).verify()) { // will redirect the user
flash.error("Cannot verify your OpenID");
login();
}
}
}
login.html模板代码为:
#{if flash.error}
#{/if}
路由定义为:
GET / Application.index
GET /login Application.login
* /authenticate Application.authenticate