1.2.0版的新特性
#136 @With
注解现在可以应用于方法上了
在 r1.2.0 之前 @With
注解用来将拦截器应用于某个控制器上,例如:
public class MyInterceptor extends Controller.Util {
@Before
public void logRequest(H.Request req) {
Act.LOGGER.trace("<<< req to %s", req.fullUrl());
}
@After
public void logRequestDone(H.Request req) {
Act.LOGGER.trace(">>> req to %s", req.fullUrl());
}
}
@With(MyInterceptor.class)
public class MyController {
...
}
上述代码表示拦截器 MyInterceptor
里面所有的拦截方法都将被应用于控制器 MyController
里面所有的响应方法上。现在 r1.2.0 允许应用将 @With
注解应用于具体的响应方法上,例如:
public class MyControllerV2 {
@With(MyInterceptor.class)
@GetAction("/foo")
public void handlerNeedsAuditing() {
...
}
@GetAction("/bar")
public void handlerWithoutAuditing() {
...
}
}
改动之后的 MyControllerV2
上拦截器只作用于发送到 /foo
的请求,而发送到 /bar/
的请求则不会应用拦截器
#152 允许将拦截器标注为全局有效
以前如果你想应用一个拦截器到控制器上,必须在控制器上使用 @With
注解来引入拦截器。现在 r1.2.0 允许我们声明某个拦截器类或者方法为全局有效:
@Global
public class MyInterceptor extends Controller.Util {
@Before
public void logRequest(H.Request req) {
Act.LOGGER.trace("<<< req to %s", req.fullUrl());
}
@After
public void logRequestDone(H.Request req) {
Act.LOGGER.trace(">>> req to %s", req.fullUrl());
}
}
上面的代码告诉 ActFramework MyInterceptor
所有的拦截器方法均全局有效
你也可以将 @Global
放在某个特定的拦截器方法上面表示该拦截器方法需要全局有效:
public class MyInterceptor extends Controller.Util {
@Global
@Before
public void logRequest(H.Request req) {
Act.LOGGER.trace("<<< req to %s", req.fullUrl());
}
@After
public void logRequestDone(H.Request req) {
Act.LOGGER.trace(">>> req to %s", req.fullUrl());
}
}
上面的代码表示只有 @Before
拦截器方法才是全局有效的。
#153 在 @DbBind 的时候使用 @NotNull 注解
在 ActFramework 应用里面我们可以使用 @DbBind
来绑定某个请求/URL/表单变量到响应方法(或者拦截器方法)参数上,例如:
@GetAction("/order/{id}/price")
public double update(@DbBind("id") Order order) {
notFoundIfNull(order);
return order.getPrice();
}
上述代码中 notFoundIfNull(order);
是告诉 ActFramework 在传入的 id
找不到对应的 Order
的时候返回 404 Not Found
响应。如果没有这一句,那当找不到绑定数据的时候你会在 return order.getPrice();
这一行得到一个 NullPointerException
,而触发一个 500 内部错误
响应。
现在 r1.2.0 我们引入了一种更加简洁的方式来描述上述逻辑:
@GetAction("/order/{id}/price")
public double update(@DbBind("id") @NotNull Order order) {
return order.getPrice();
}
上面的 NotNull
是 Java 标准的数据有效性检验框架提供的注解.
ActFramework 还改进了(开发模式下的)错误页面,这样可以让开发人员非常清晰地看到是什么原因造成的 404 返回:
源码
当 ID 不正确时的错误页面
#157 路由支持 SEO
现在我们可能在一些网站上发现针对搜索引擎优惠的 URL, 比如下面两个 URL 打开的页面是一样的:
http://stackoverflow.com/questions/43406011/actframework-run-error-org-osgl-exception-unexpectedexception-app-not-found
http://stackoverflow.com/questions/43406011
但是前者的 URL 带的 actframework-run-error-org-osgl-exception-unexpectedexception-app-not-found
让 URL 能够被搜索引擎理解并做相应的处理。
ActFramework r1.2.0 提供了一种机制允许应用创建类似的 URL:
@GetAction("/article/{id}/...")
public Article getArticle(@DbBind("id") Article article) {
return article;
}
上面代码中的 URL 路径 /article/{id}/...
以 ...
结束, 表示在 /article/{id}/
之后的任何字符都将被忽略。这样可以让开发人员构造针对 SEO 优化的 URL
#160 在 Controller.Base
中植入 ActionContext
字段
Controller.Util
类提供了丰富的工具方法来帮助应用程序,因此应用程序的控制器类通常会从 Controller.Util
类继承下来。另一方面应用程序经常会在响应方法中使用到 ActionContext
对象,而获得该对象的办法通常有两种,一种声明一个依赖注入字段,二是在响应方法的参数列表里面声明该对象。
现在 r1.2.0 我们引入了一个新的控制器基类:Controller.Base
。该基类和 Controller.Util
的区别是前者声明了一个依赖注入字段 protected ActionContext context;
这样应用从该基类派生出的控制器类自动拥有了 context
字段而无需声明:
public class MyController extends Controller.Base {
@GetAction("/foo")
public String foo() {
return context.i18n("foo");
}
}
注意 从 Controller.Base
派生出的控制器类可以直接使用 Controller.Util
所有的方法,因为 Controller.Base
继承 了 Controller.Util
看到这里大家可能会问,为什么不直接在 Controller.Util
类里面声明 protected ActionContext context
字段呢? 原因在于 ActionContext context
字段是有状态的,即每次请求带来的 context 都是不同的. 因此 ActFramework 在响应新请求的时候必须创建控制器的新实例. 而并非所有的控制器都需要一个 ActionContext
类型的字段。在这种情况下保持 Controller.Util
的无状态性可以让无状态的控制器从其派生而不至于损失单例的资格。
#161 提供一种机制标注注入字段为无状态的
ActFramework 的灵动之处体现在很多地方,其中一处是自动检测到没有声明字段的控制器类的时候使用同样的实例来响应不同的请求,这很酷. 不过我们需要做到更进一步,在某些时候我们注入的对象本身是无状态的,比如
public class OrderService {
@Inject
private Order.Dao dao;
...
}
上面的OrderService
控制器注入了一个字段 Order.Dao dao
, 但是因为 Order.Dao
是无状态的(假设如此), 或者说我们注入的每个 Order.Dao
都是同行一个实例,在这种情况下,我们没有理由为 OrderService
控制器对每个请求创建一个新实例,完全可以将其当作单例处理. r1.2.0版我们提供了两种方式实现上述需求
方法一, 在注入的字段上添加 @Global
注解:
public class OrderService {
@Inject
@Global
private Order.Dao dao;
...
}
上面的 @Global
注解告诉 ActFramework dao
实例是跨所有的 OrderService实例存在的, 因此 ActFramework 不需要因为这个字段而将
OrderService` 控制器考虑为有状态控制器。
方法二 如果你能控制注入类,比如这个例子中的 Order.Dao
类, 你可以在类上加上 @Stateless
注解:
@Entity("order")
public class Order {
...
@Stateless
public static class Dao extends EbeanDao<Order> {
...
}
}
现在你在注入 OrderDao dao
字段的时候不需要加上 @Global
注解,ActFramework 自动根据 Order.Dao 类上的 @Stateless
注解推断出了这个字段的无状态性:
public class OrderService {
@Inject
private Order.Dao dao;
...
}
即使是以上代码,ActFramework 也将会把 OrderService
当作无状态的单例控制器处理