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 条评论
登录 后参与评论

相关文章

来自专栏LuckQI

SpringBoot~SpringBatch 使用

Spring Batch 提供了大量可重用的组件,包括了日志、追踪、事务、任务作业统计、任务重启、跳过、重复、资源管理。对于大数据量和高性能的批处理任务,Spr...

1223
来自专栏haifeiWu与他朋友们的专栏

阿里 RPC 框架 DUBBO 初体验

最近研究了一下阿里开源的分布式RPC框架dubbo,楼主写了一个 demo,体验了一下dubbo的功能。

2962
来自专栏Danny的专栏

【EJB学习笔记】——有状态的会话Bean和无状态的会话Bean

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

2391
来自专栏何俊林

插件开发之360 DroidPlugin源码分析(三)Binder代理

Hook机制中Binder代理类关系图 Hook机制中Binder代理时序图 MyServiceManager ServiceManagerCacheBinde...

2119
来自专栏微信公众号:Java团长

学习SpringMVC——如何获取请求参数

  @RequestParam,你一定见过;@PathVariable,你肯定也知道;@QueryParam,你怎么会不晓得?!还有你熟悉的他(@CookieV...

832
来自专栏JavaEdge

@Autowired 与@Resource之争

37210
来自专栏全栈架构

Spring Boot 与 Kotlin Web应用的统一异常处理

做Web应用,请求处理过程中发生错误是非常常见的。 SpringBoot提供了一个默认的映射: /error,当处理中抛出异常之后,会转到该请求中处理,并且该请...

1072
来自专栏偏前端工程师的驿站

Eclipse魔法堂:任务管理器

一、前言                                  Eclipse的任务管理器为我们提供一个方便的入口查看工程代办事宜,并定位到对应的代...

2088
来自专栏JackieZheng

学习SpringMVC——如何获取请求参数

  @RequestParam,你一定见过;@PathVariable,你肯定也知道;@QueryParam,你怎么会不晓得?!还有你熟悉的他(@CookieV...

2765
来自专栏Java架构师历程

Spring工作原理

      内部最核心的就是IOC了,动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射,反射其实就是在运行时动态的去创建、...

1982

扫码关注云+社区