ActFramework r1.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 打开的页面是一样的:

但是前者的 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;
    
    ...
}

因为Order.Dao类被标注为@Stateless,即使上面的代码没有使用@Global,ActFramework 也将会把 OrderService 当作无状态的单例控制器处理

相关链接

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xdecode

Spring MVC注解式开发

MVC注解式开发即处理器基于注解的类开发, 对于每一个定义的处理器, 无需在xml中注册. 只需在代码中通过对类与方法的注解, 即可完成注册. 定义处理器 @C...

2738
来自专栏不会写文章的程序员不是好厨师

Spring源码初探-IOC(1)-Bean的初始化

对于一名Java码农来说,Spring真是太重要了。目前Spring的体系已经发展得非常壮大,其底层基石core,beans,context非常牢固。

831
来自专栏大闲人柴毛毛

Spring速查手册(二)——Bean的作用域

Bean的四种作用域 单例(Singleton):整个应用中,只创建一个bean。 原型(Prototype):每次注入或请求要给bean的时候都创建一个新的...

3238
来自专栏云霄雨霁

SpringMVC--Json数据交互笔记

1373
来自专栏chenssy

【死磕 Spring】----- IOC 之 IOC 初始化总结

前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。

571
来自专栏Linyb极客之路

Spring Cloud开发注意事项

如果provider中需要引入其他feign client的接口,需在 provider的启动类添加注解 @EnableFeignClients(basePac...

1343
来自专栏日常分享

抛开深层次底层,快速入门SpringMVC

SpringMVC主要有三个核心部分组成,DispatcherServlet、Controller、ViewResolver。      Dispatche...

793
来自专栏Phoenix的Android之旅

Dagger2 Android应用:@Scope和@Subcomponent

这部分会介绍Dagger2中比较莫名的概念,同样也不涉及Android的具体代码。 Dagger2使用中的核心技巧包括@Subcomponent和@Scope,...

892
来自专栏chenssy

【死磕 Spring】----- IOC 之 IOC 初始化总结

前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。

674
来自专栏程序之美

spring bean的作用域

Bean的作用域 参考:spring FrameWork官方文档 spring4.x中官方有7中作用域,如果使用的是applicationContext作为...

842

扫码关注云+社区