动作链

Jul 10, 2017 10:38:44 AM

作者:zozoh

动作链机制概述

Nutz.Mvc 统一采用动作链机制来处理每一个 HTTP 请求。我们认为:

  • 对于一个 HTTP 请求的处理实际上是由一系列子处理构成的
    • 比如根据映射找到入口函数
    • 比如为入口函数生成调用参数
    • 比如调用入口函数
  • 我们希望这一系列子处理可以根据 URL 的不同而不同
  • 我们也希望子处理是可配置的
    因此它能让你的HTTP映射处理具备更大的灵活性。

下面是一张稍微有点复杂的图,根据这张图,我们来详细的解释一下这个机制,在了解了它之后,我相信你掌握更多的复用你代码的手段,从而更合理处理你的 URL 映射关系:

动作链 - 图1

这张图稍微有点复杂,你可能看起来稍微要皱一下眉头。本文的后面几节会详细为你解释,但是你首先要记住:

  • 这种图的流程都是 Nutz.Mvc 在加载时进行的操作
  • 运行时,Nutz.Mvc 将根据 URL 获得 ActionChain 接口,直接执行整条动作链

(A)获取动作链工厂

整个 Nutz.Mvc 的应用,必须有且只能有一个动作链工厂。在主模块上,你可以声明你自己的动作链工厂(通过 @ChainBy 注解)。当然,如果你没有声明这个注解,Nutz.Mvc 会采用默认的动作链工厂实现类(org.nutz.mvc.impl.NutActionChainMaker)

如果你需要定制动作链工厂,你可以通过类似下面的形式,声明自己特殊配置的动作链工厂:

  1. @ChainBy(args={"配置文件A路径", "配置文件B路径"})

你也可以采用自己的动作链工厂实现类

  1. @ChainBy(type=MyChainMaker.class, args={...})

如果你的动作链工厂需要更复杂的配置,你可以交给 Ioc 容器来管理

  1. @ChainBy(type=MyChainMaker.class, args={"ioc:myChianMaker"})

这样,你就可以在 Ioc 容器里,声明一个 "myChainMaker" 对象,来对其做任何你想要的配置。当然,首先你需要在主函数里声明了 Ioc 容器(请参看同 Ioc 容器一起工作一文)

(B)获取动作链工厂配置信息

如果你采用的是 Nutz.Mvc 的默认动作链工厂,它允许你在构造函数中声明动作链的配置文件。你可以增加任意多的动作链配置文件,在一个文件中,你可以声明任意多的动作链。

它有一个默认的配置文件,声明了一个名字为 "default" 的动作链。你的入口函数如果没有声明@Chain 注解的话,就是使用这个动作链。你可以通过自己的配置文件覆盖它。

它的构造函数定义为:

  1. public NutActionChainMaker(String...args) {
  2. ...

接受变参数数组,每个参数,都是你配置文件的路径,可以是类路径,也可以是绝对路径,当然你也可以写成:

  1. @ChainBy(args={"${app.root}/WEB-INF/chain/mychain.js"})

其中 ${app.root} 会被 Nutz.Mvc 替换成你的应用在服务器上的根目录。

(C)解析配置文件

每个配置文件你可以配置多个动作链,每个动作链需要一个名字,以便在入口函数通过 @Chain注解来引用,下面让我们来看看默认动作链配置文件的内容:

  1. {
  2. "default" : {
  3. ps : [
  4. "org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
  5. "org.nutz.mvc.impl.processor.EncodingProcessor",
  6. "org.nutz.mvc.impl.processor.ModuleProcessor",
  7. "org.nutz.mvc.impl.processor.ActionFiltersProcessor",
  8. "org.nutz.mvc.impl.processor.AdaptorProcessor",
  9. "org.nutz.mvc.impl.processor.MethodInvokeProcessor",
  10. "org.nutz.mvc.impl.processor.ViewProcessor"
  11. ],
  12. error : 'org.nutz.mvc.impl.processor.FailProcessor'
  13. }
  14. }

动作链的配置文件采用了 JSON 格式。在上面的文件内只有一个动作链,名字为 "default"。通过例子你可以很容易看出,一个动作链需要两方面的信息:

  • 正常的流程是怎样的?
    • "ps" 属性是一个数组,每个值就是一个处理器接口的实现类
    • 每次该动作链执行时,会按顺序调用这些处理器
  • 遇到错时怎么办?
    • 通过给出的错误处理器的实现类来处理错误

(D)至少还有默认配置文件

默认的配置文件优先级最低,它随着 nutz.jar 一起发布,所以它没打算让你直接修改。你可以通过自己的配置文件覆盖其唯一的动作链 "default"

(E)为入口函数创建动作链

就像前面提到的,在每个入口函数里,你可以通过注解 @Chain 来指定你的这个函数将采用哪个动作链。如果你没有指定,Nutz.Mvc 认为你是希望用 @Chain("default")} 来处理这个入口函数。

需要说明的是,考虑到效率,在 Nutz.Mvc 加载时,它就会为每个入口函数创建 URL 映射关系。即把一个 URL 映射到一个动作链实例上。所以动作链的实例,是在加载时就被创建了。所以如果你自己实现了动作链工厂,请保证工厂生成的每个动作链是线程安全的

(F)每个入口函数的动作链都可以不同

如果你读完了上述小节,本节光看标题就足够了。

但是,我唠叨成性,这里再举个小例子:

  1. @At("/a")
  2. @Chain("abc")
  3. public void funcA(){}
  4. @At("/b")
  5. @Chain("abc")
  6. public void funcB(){}
  7. @At("/c")
  8. public void funcC(){}
  9. @At("/d")
  10. public void funcD(){}

在上面的例子中,四个入口函数,其中:

  • 每个入口函数都各自有一份动作链实例
  • 具体的实例是由动作链工厂决定的
  • 每个动作链实例的生命周期范围是 App(ServletContext) 级别
    原因不解释。

配置示例1,引用ioc里面的bean

chain.js的代码

  1. {
  2. "default" : {
  3. "ps" : [
  4. "org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
  5. "org.nutz.mvc.impl.processor.EncodingProcessor",
  6. "org.nutz.mvc.impl.processor.ModuleProcessor",
  7. "ioc:wxProcessor", // 引用一个叫wxProcessor的ioc bean, 必须是prototype,即singleton=false
  8. "org.nutz.mvc.impl.processor.ActionFiltersProcessor",
  9. "org.nutz.mvc.impl.processor.AdaptorProcessor",
  10. "org.nutz.mvc.impl.processor.MethodInvokeProcessor",
  11. "org.nutz.mvc.impl.processor.ViewProcessor"
  12. ],
  13. "error" : 'org.nutz.mvc.impl.processor.FailProcessor'
  14. }
  15. }

WxProcessor类, 标准的注解式Ioc, 确保IocBy扫描到这个类哦

  1. // 注意, 确保是非单例哦
  2. @IocBean(singleton=false) //默认生成的名字就是类名然后首字母小写,即wxProcessor,对应了chain.js中的配置
  3. public class WxProcessor extend AbstractProcessor {
  4. @Inject Dao dao;
  5. @Inject XXXService xxxService;
  6. // 其他方法.....
  7. }

配置示例2,不需要ioc注入

chain.js的代码

  1. {
  2. "default" : {
  3. "ps" : [
  4. "org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
  5. "org.nutz.mvc.impl.processor.EncodingProcessor",
  6. "org.nutz.mvc.impl.processor.ModuleProcessor",
  7. "net.wendal.nutzbook.mvc.LogTimeProcessor", // 直接写类名
  8. "org.nutz.mvc.impl.processor.ActionFiltersProcessor",
  9. "org.nutz.mvc.impl.processor.AdaptorProcessor",
  10. "org.nutz.mvc.impl.processor.MethodInvokeProcessor",
  11. "org.nutz.mvc.impl.processor.ViewProcessor"
  12. ],
  13. "error" : 'org.nutz.mvc.impl.processor.FailProcessor'
  14. }
  15. }

LogTimeProcessor类

  1. package net.wendal.bootstrap.mvc;
  2. import javax.servlet.http.HttpServletRequest;
  3. import org.nutz.lang.Stopwatch;
  4. import org.nutz.log.Log;
  5. import org.nutz.log.Logs;
  6. import org.nutz.mvc.ActionContext;
  7. import org.nutz.mvc.impl.processor.AbstractProcessor;
  8. public class LogTimeProcessor extends AbstractProcessor {
  9. private static final Log log = Logs.get();
  10. public LogTimeProcessor() {
  11. }
  12. @Override
  13. public void process(ActionContext ac) throws Throwable {
  14. Stopwatch sw = Stopwatch.begin();
  15. try {
  16. doNext(ac);
  17. } finally {
  18. sw.stop();
  19. if (log.isDebugEnabled()) {
  20. HttpServletRequest req = ac.getRequest();
  21. log.debugf("[%-4s]URI=%s %sms", req.getMethod(), req.getRequestURI(), sw.getDuration());
  22. }
  23. }
  24. }
  25. }
  26.  

用代码方式配置

不想用js配置,觉得繁琐,要不试试代码方式配置,因为@ChainBy只需要一个实现了ActionChainMaker的类哦

  1. public class MyActionChainMaker implements ActionChainMaker {
  2. // 该接口只有一个方法
  3. public ActionChain eval(NutConfig config, ActionInfo ai) {
  4. // 提醒: config可以获取ioc等信息, ai可以获取方法上的各种配置及方法本身
  5. // 正常处理的列表
  6. List<Processor> list = new ArrayList<>();
  7. list.add(new UpdateRequestAttributesProcessor()); // 设置base/msg等内置属性
  8. list.add(new EncodingProcessor()); // 设置编码信息@Encoding
  9. list.add(new ModuleProcessor()); // 获取入口类的对象,从ioc或直接new
  10. list.add(new ActionFiltersProcessor()); // 处理@Filters
  11. list.add(new AdaptorProcessor()); // 处理@Adaptor
  12. list.add(new MethodInvokeProcessor()); // 执行入口方法
  13. list.add(new ViewProcessor()); // 对入口方法进行渲染@Ok
  14. for (Processor p : list) {
  15. p.init(config, ai);
  16. }
  17. // 最后是专门负责兜底的异常处理器,这个处理器可以认为是全局异常处理器,对应@Fail
  18. Processor error = new FailProcessor();
  19. error.init(config, ai);
  20. return new NutActionChain(list, error, ai);
  21. }
  22. }
  23. @ChainBy(type=MyActionChainMaker.class, args={})
  24. public class MainModule{}

自定义处理器的一些提醒

  • 尽量不要吃掉异常,如果需要catch并忽略,严重建议打印一下日志
  • ActionContext可以获取的信息,取决于所在的位置,例如AdaptorProcessor之前的处理器在doNext之前拿不到方法参数,但之后可以获取.
  • doNext是继续执行下一个处理器的方法,如果要终止调用,不执行该方法就可以了
  • 绝大多数情况下处理器都不是单例,也不应该是单例!!如果从ioc容器获取,务必确保该处理器声明为singleton=false

本页面的文字允许在知识共享 署名-相同方式共享 3.0协议GNU自由文档许可证下修改和再使用。

原文: http://nutzam.com/core/mvc/action_chain.html