1.2.0版的新特性

#136 @With 注解现在可以应用于方法上了

在 r1.2.0 之前 @With 注解用来将拦截器应用于某个控制器上,例如:

  1. public class MyInterceptor extends Controller.Util {
  2. @Before
  3. public void logRequest(H.Request req) {
  4. Act.LOGGER.trace("<<< req to %s", req.fullUrl());
  5. }
  6. @After
  7. public void logRequestDone(H.Request req) {
  8. Act.LOGGER.trace(">>> req to %s", req.fullUrl());
  9. }
  10. }
  1. @With(MyInterceptor.class)
  2. public class MyController {
  3. ...
  4. }

上述代码表示拦截器 MyInterceptor 里面所有的拦截方法都将被应用于控制器 MyController 里面所有的响应方法上。现在 r1.2.0 允许应用将 @With 注解应用于具体的响应方法上,例如:

  1. public class MyControllerV2 {
  2. @With(MyInterceptor.class)
  3. @GetAction("/foo")
  4. public void handlerNeedsAuditing() {
  5. ...
  6. }
  7. @GetAction("/bar")
  8. public void handlerWithoutAuditing() {
  9. ...
  10. }
  11. }

改动之后的 MyControllerV2 上拦截器只作用于发送到 /foo 的请求,而发送到 /bar/ 的请求则不会应用拦截器

#152 允许将拦截器标注为全局有效

以前如果你想应用一个拦截器到控制器上,必须在控制器上使用 @With 注解来引入拦截器。现在 r1.2.0 允许我们声明某个拦截器类或者方法为全局有效:

  1. @Global
  2. public class MyInterceptor extends Controller.Util {
  3. @Before
  4. public void logRequest(H.Request req) {
  5. Act.LOGGER.trace("<<< req to %s", req.fullUrl());
  6. }
  7. @After
  8. public void logRequestDone(H.Request req) {
  9. Act.LOGGER.trace(">>> req to %s", req.fullUrl());
  10. }
  11. }

上面的代码告诉 ActFramework MyInterceptor 所有的拦截器方法均全局有效

你也可以将 @Global 放在某个特定的拦截器方法上面表示该拦截器方法需要全局有效:

  1. public class MyInterceptor extends Controller.Util {
  2. @Global
  3. @Before
  4. public void logRequest(H.Request req) {
  5. Act.LOGGER.trace("<<< req to %s", req.fullUrl());
  6. }
  7. @After
  8. public void logRequestDone(H.Request req) {
  9. Act.LOGGER.trace(">>> req to %s", req.fullUrl());
  10. }
  11. }

上面的代码表示只有 @Before 拦截器方法才是全局有效的。

#153 在 @DbBind 的时候使用 @NotNull 注解

在 ActFramework 应用里面我们可以使用 @DbBind 来绑定某个请求/URL/表单变量到响应方法(或者拦截器方法)参数上,例如:

  1. @GetAction("/order/{id}/price")
  2. public double update(@DbBind("id") Order order) {
  3. notFoundIfNull(order);
  4. return order.getPrice();
  5. }

上述代码中 notFoundIfNull(order); 是告诉 ActFramework 在传入的 id 找不到对应的 Order 的时候返回 404 Not Found 响应。如果没有这一句,那当找不到绑定数据的时候你会在 return order.getPrice(); 这一行得到一个 NullPointerException,而触发一个 500 内部错误 响应。

现在 r1.2.0 我们引入了一种更加简洁的方式来描述上述逻辑:

  1. @GetAction("/order/{id}/price")
  2. public double update(@DbBind("id") @NotNull Order order) {
  3. return order.getPrice();
  4. }

上面的 NotNull 是 Java 标准的数据有效性检验框架提供的注解.

ActFramework 还改进了(开发模式下的)错误页面,这样可以让开发人员非常清晰地看到是什么原因造成的 404 返回:

源码

code

当 ID 不正确时的错误页面

error

#157 路由支持 SEO

现在我们可能在一些网站上发现针对搜索引擎优惠的 URL, 比如下面两个 URL 打开的页面是一样的:

但是前者的 URL 带的 actframework-run-error-org-osgl-exception-unexpectedexception-app-not-found 让 URL 能够被搜索引擎理解并做相应的处理。 ActFramework r1.2.0 提供了一种机制允许应用创建类似的 URL:

  1. @GetAction("/article/{id}/...")
  2. public Article getArticle(@DbBind("id") Article article) {
  3. return article;
  4. }

上面代码中的 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 字段而无需声明:

  1. public class MyController extends Controller.Base {
  2. @GetAction("/foo")
  3. public String foo() {
  4. return context.i18n("foo");
  5. }
  6. }

注意Controller.Base 派生出的控制器类可以直接使用 Controller.Util 所有的方法,因为 Controller.Base 继承 了 Controller.Util

看到这里大家可能会问,为什么不直接在 Controller.Util 类里面声明 protected ActionContext context 字段呢? 原因在于 ActionContext context 字段是有状态的,即每次请求带来的 context 都是不同的. 因此 ActFramework 在响应新请求的时候必须创建控制器的新实例. 而并非所有的控制器都需要一个 ActionContext 类型的字段。在这种情况下保持 Controller.Util 的无状态性可以让无状态的控制器从其派生而不至于损失单例的资格。

#161 提供一种机制标注注入字段为无状态的

ActFramework 的灵动之处体现在很多地方,其中一处是自动检测到没有声明字段的控制器类的时候使用同样的实例来响应不同的请求,这很酷. 不过我们需要做到更进一步,在某些时候我们注入的对象本身是无状态的,比如

  1. public class OrderService {
  2. @Inject
  3. private Order.Dao dao;
  4. ...
  5. }

上面的OrderService 控制器注入了一个字段 Order.Dao dao, 但是因为 Order.Dao 是无状态的(假设如此), 或者说我们注入的每个 Order.Dao 都是同行一个实例,在这种情况下,我们没有理由为 OrderService 控制器对每个请求创建一个新实例,完全可以将其当作单例处理. r1.2.0版我们提供了两种方式实现上述需求

方法一, 在注入的字段上添加 @Global 注解:

  1. public class OrderService {
  2. @Inject
  3. @Global
  4. private Order.Dao dao;
  5. ...
  6. }

上面的 @Global 注解告诉 ActFramework dao 实例是跨所有的 OrderService实例存在的, 因此 ActFramework 不需要因为这个字段而将OrderService` 控制器考虑为有状态控制器。

方法二 如果你能控制注入类,比如这个例子中的 Order.Dao 类, 你可以在类上加上 @Stateless 注解:

  1. @Entity("order")
  2. public class Order {
  3. ...
  4. @Stateless
  5. public static class Dao extends EbeanDao<Order> {
  6. ...
  7. }
  8. }

现在你在注入 OrderDao dao 字段的时候不需要加上 @Global 注解,ActFramework 自动根据 Order.Dao 类上的 @Stateless 注解推断出了这个字段的无状态性:

  1. public class OrderService {
  2. @Inject
  3. private Order.Dao dao;
  4. ...
  5. }

即使是以上代码,ActFramework 也将会把 OrderService 当作无状态的单例控制器处理