java带有上下箭头的编辑框_现代化的Java(十七)——Clojure Actor 与状态管理

前面的章节我们讨论过,Clojure Actor 的主要意义在于可以用 clojure 的 multimethod 编写 actor 的消息响应逻辑,并且可以向各个 handler 注入 clojure 代码。那么如何管理 actor 中的状态呢?

如果一个项目本身就有比较多的 Java 逻辑,那么再多实现几个带有自定义字段的 Java 类,例如就简单的派生 ClojureActor 类型,都是不难实现的。但是如果我们只是想快速的写一段 clojure 代码,这样做并不经济。

最后我决定为 Clojure Actor 加入一个字段解决这个问题,这个字段应该易于clojure 代码使用,能够修改数据状态,那么我想到的是,增加一个 agent 对象。

Agent 是 clojure 中几个内置的“引用”类型之一。它允许我们用 send/send-off 向其发送函数,并将函数的返回值作为自己的新状态保存,这个过程通过一个任务队列确保并发安全。Agent 也允许我们用 @ ——即 deref 函数 —— 获取状态。

我们扩展一下 ClojureActor 的定义,加入一个 Agent 字段:

public class ClojureActor extends AbstractActor {
    static private String actor_namespace = "liu.mars.actor";
    static {
        CR.require(actor_namespace);
    }
    private Agent state;
...

为了简化代码,我们有一些用 Clojure 封装的状态操作,这些代码通过上例中的 require 操作引入,例如构造一个空状态的 new-state :

(ns liu.mars.actor)

(defn new-state
  []
  (agent {}))

(defn deref-state
  [state]
  @state)

(defn get-state
  [state k]
  (get @state k))

(defn get-state-in [state path]
  (get-in @state path))

CR (Clojure Runtime)工具类来自我的工具库 jaskell [MarchLiu/jaskell] 。用来简化一些琐碎的操作。

其它几个函数,主要针对一些在 Java 里面直接写不方便,但是可能会用到的操作,便于将来使用。

一开始,我把这几个操作封装成了工具方法,但是在写测试的时候我发现,如果想要覆盖 agent 丰富的功能,要封装的 Java 代码太多,但是收益并不大,我预期这个类型还是主要用于 clojure 用户,那么只要能够在 clojure 代码中引用到这个 agent 即可,这只需要一个 get 方法:

...
    public Agent getState() {
        return state;
    }
}

相应的,构造 actor 的 props 函数也做了扩展:

    public static Props props(MultiFn fn){
        return Props.create(ClojureActor.class, () -> {
            var result = new ClojureActor(fn);
            result.state = (Agent) CR.invoke(actor_namespace, "new-state");
            return result;
        });
    }

    public static Props propsWithInit(IFn initiator, MultiFn fn){
        return Props.create(ClojureActor.class, () -> {
            var result = new ClojureActor(fn);
            result.state = (Agent) CR.invoke(actor_namespace, "new-state");
            initiator.invoke(result);
            return result;
        });
    }

    public static Props propsWithStateInit(IFn initiator, Agent state, MultiFn fn){
        return Props.create(ClojureActor.class, () -> {
            var result = new ClojureActor(fn);
            result.state = (Agent) state;
            initiator.invoke(result);
            return result;
        });
    }

这三个静态构造函数分别对应:

- 默认构造时只需要给出消息响应逻辑,程序调用 new-state 构造一个空字典作为初始状态 `(agent {})`

- 用户可以在 initiator 里给出初始化逻辑

- 如果我们需要更复杂的设定,例如指定 agent 的校验函数,那么我们可以在 clojure 里构造好 agent,通过 propsWithStateInit 函数指定给构造逻辑。

通过下面这段测试代码,我们可以看到用 clojure 维护数据状态的过程。虽然每次都要 (.getState this) 确实不够简练,但是我们在生产环境里,编写复杂的程序逻辑时,通过 doto 、let 或箭头宏 -> 、 ->> 等技巧,完全可以把它写的轻巧一些。

(ns liu.mars.state-actor-test
  (:require [clojure.test :refer :all])
  (:import (akka.actor ActorSystem)
           (liu.mars ClojureActor)
           (akka.testkit.javadsl TestKit)
           (java.util.function Supplier Function)))

(defmulti receiver "receive matchers for basic workflow"
          (fn [_ message]
            (:order message)))
(defmethod receiver :get [this message]
  (println (str "receive a get message " message " from " this))
  (.tell (.getSender this) (get @(.getState this) (:key message)) (.getSelf this)))
(defmethod receiver :get-in [this message]
  (println (str "receive a get in message " message " from " this))
  (.tell (.getSender this) (get-in @(.getState this) (:path message)) (.getSelf this)))
(defmethod receiver :post [this message]
  (let [fun (:function message)]
    (println (str "receive a post order " fun " : " (class fun)))
    (send (.getState this) fun)))

(testing "tests for clojure state actor by creator"
  (let [system (ActorSystem/create "test")
        test-kit (TestKit. system)
        await #(.awaitCond test-kit (reify Supplier (get [this] (.msgAvailable test-kit))))
        self (.getRef test-kit)
        text-message "a text message save in state"
        runtime-message "this data post in runtime"
        initiator (fn [actor]
                    (doto actor
                      (.setPreStart #(println (str % " is going to start")))
                      (.setPostStop #(println (str % " stopped"))))
                    (send (.getState actor) #(assoc % :data text-message)))
        actor (.actorOf system (ClojureActor/propsWithInit initiator receiver))]
    (try
      (.tell actor {:order :get :key :data} self)
      (await)
      (.expectMsgPF test-kit "check get messsage" (reify Function
                                                    (apply [this message]
                                                      (is (= text-message message)))))
      (.tell actor {:order :post :function #(assoc % :post-data runtime-message)} self)
      (.tell actor {:order :get-in :path [:post-data]} self)
      (await)
      (.expectMsgPF test-kit "check get in" (reify Function
                                              (apply [this message]
                                                (is (= runtime-message message)))))
      (.stop system actor)
      (finally
        (TestKit/shutdownActorSystem system)))))

这部分代码已经上传到 [MarchLiu/akka-clojure] 并发布为 `[liu.mars/akka-clojure "0.1.1"] ` 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值