1、拦截器
控制器中可以定义拦截方法(也可称之为拦截器),为控制器及其子类的所有Action提供服务。当所有的Action都需要进行通用的处理时,该功能就显得非常有用:比 如验证用户的合法性,加载请求范围内的信息等。
读者在使用时需要注意的是,这些拦截器方法不能定义为public,但必须是static,并通过有效的拦截标记进行注解。
1.1 @Before#
使用@Before注解的方法会在每个Action调用之前执行。如创建具有用户合法性检查的拦截器:
public class Admin extends Application {
@Before
static void checkAuthentification() {
if(session.get("user") == null) login();
}
public static void index() {
List<User> users = User.findAll();
render(users);
}
...
}
如果不希望@Before注解拦截所有的Action方法,那么可以使用unless参数列出需要排除的方法:
public class Admin extends Application {
@Before(unless="login")
static void checkAuthentification() {
if(session.get("user") == null) login();
}
public static void index() {
List<User> users = User.findAll();
render(users);
}
...
}
或者直接使用only参数把需要拦截的方法列举出来:
public class Admin extends Application {
@Before(only={"login","logout"})
static void doSomething() {
...
}
...
}
unless和only参数对@After,@Before以及@Finally注解都适用。
1.2 @After#
使用@After注解的方法会在每个Action调用之后执行:
public class Admin extends Application {
@After
static void log() {
Logger.info("Action executed ...");
}
public static void index() {
List<User> users = User.findAll();
render(users);
}
...
}
1.3 @Catch#
如果有Action方法抛出了异常,那么使用@Catch注解的方法就会执行,且抛出的异常会以参数的形式传递到@Catch注解的方法中。具体实现如下:
public class Admin extends Application {
@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);
}
}
使用@Catch注解和普通的Java异常处理程序一样,捕获父类往往可以获得更多的异常类型。如果拥有多个需要捕获的方法,可以通过指定优先级来确定他们的执行顺序。具体实现如下:
public class Admin extends Application {
@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);
}
}
1.4 @Finally#
@Finally注解的方法总是在每个Action调用之后执行(无论Action是否成功执行):
public class Admin extends Application {
@Finally
static void log() {
Logger.info("Response contains : " + response.out);
}
public static void index() {
List<User> users = User.findAll();
render(users);
}
...
}
如果@Finally注解的方法中包含的参数是可抛出的异常,其方法中的内容还是可以继续执行的,具体如下:
public class Admin extends Application {
@Finally
static 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);
}
1.5 使用@with注解增加更多拦截器#
如果某个控制器是其他一些类的父类,那么该控制器中定义的所有拦截器会影响到所有子类。由于Java不允许多重继承,对单纯通过继承来使用拦截器造成了一定的局限性。Play可以通过@With注解,调用其他控制器中已经定义好的拦截方法,从而突破这一局限。比如创建Secure控制器,定义checkAuthenticated()拦截方法验证用户合法性:
public class Secure extends Controller {
@Before
static void checkAuthenticated() {
if(!session.containsKey("user")) {
unAuthorized();
}
}
}
在其他的控制器中,可以通过@With(Secure.class)注解将其包含进来:
@With(Secure.class)
public class Admin extends Application {
...
}
1.6 Session和Flash作用域
在Play开发中,如果需要在HTTP请求之间保存数据,可以将数据保存在Session或者Flash内。保存在Session中的数据在整个用户会话中都是有效的,而保存在Flash的数据只对下一次请求有效。
特别需要注意的是,Session和Flash作用域中的数据都是采用Cookie机制添加到随后的HTTP响应中的(并没有存储在服务器上的),所以数据大小非常有限(不能超过4K),而且只能存储字符串类型的数据。
由于Cookie是使用密钥签名过的,所以客户端不能轻易修改Cookie的数据(否则会失效)。不要将Play的Session当作缓存来使用,如果需要在特定的会话中缓存一些数据,那么可以使用Play内置的缓存机制,并将session.getId()作为缓存的key进行储存。
public static void index() {
List messages = Cache.get(session.getId() + "-messages", List.class);
if(messages == null) {
// 处理缓存失效
messages = Message.findByUser(session.get("user"));
Cache.set(session.getId() + "-messages", messages, "30mn");
}
render(messages);
}
Session在用户关闭浏览器后就会失效,除非修改配置文件中的application.session.maxAge属性。设置方法如下:
application.session.maxAge=7d # Remember for one week.
使用Play内置的Cache缓存时需要注意,Cache与传统Servlet的HTTP Session对象是不同的。框架无法保证这些缓存对象会一直存在,所以在业务代码中必须处理缓存失效的问题,以便保持应用完全无状态化。
控制层基本的工作流程为:接收HTTP请求的参数,通过对域模型的操作来实现业务逻辑,最终调用视图进行渲染。有时候控制器所做的仅仅是简单的查询操作,有时候需要改变域模型的状态(更新数据),或者通过Action链将处理转发至其他控制器方法。值得注意的是,Play将Session数据以Cookie的机制存放在客户端,而服务器端则用Cache的方式进行数据缓存。