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 :

(nsliu.mars.actor)

(defnnew-state

[]

(agent{}))

(defnderef-state

[state]

@state)

(defnget-state

[state k]

(get@state k))

(defnget-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 或箭头宏 -> 、 ->> 等技巧,完全可以把它写的轻巧一些。

(nsliu.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)))

(defmultireceiver "receive matchers for basic workflow"

(fn[_ message]

(:order message)))

(defmethodreceiver :get [this message]

(println(str"receive a get message " message " from " this))

(.tell (.getSender this) (get@(.getState this) (:key message)) (.getSelf this)))

(defmethodreceiver :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)))

(defmethodreceiver :post [this message]

(let[fun (:function message)]

(println(str"receive a post order " fun " : " (classfun)))

(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]

(dotoactor

(.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"] ` 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值