一个设计优秀的工具或框架,应该都有一个易用、强大的插件或扩展体系,akka也不例外。

  akka的扩展方法非常简单,因为只涉及到两个组件:Extension、 ExtensionId。其中Extension在每个ActorSystem中只会加载一次,然后被akka管理。你可以在ActorSystem启动的时候以编程的方式加载,也可以通过配置的方式自动加载。由于Extension是在ActorSystem层面的扩展,所以需要开发者自己处理线程安全的问题。ExtensionId可以理解为Extension的一个唯一标志,ActorSystem会根据它来判断Extension是否被加载过,以确保Extension只能加载一次。

/**
* The basic ActorSystem covers all that is needed for locally running actors,
* using futures and so on. In addition, more features can hook into it and
* thus become visible to actors et al by registering themselves as extensions.
* This is accomplished by providing an extension—which is an object
* implementing this trait—to `ActorSystem.registerExtension(...)` or by
* specifying the corresponding option in the configuration passed to
* ActorSystem, which will then instantiate (without arguments) each FQCN and
* register the result.
*
* The extension itself can be created in any way desired and has full access
* to the ActorSystem implementation.
*
* This trait is only a marker interface to signify an Akka Extension.
*/
trait Extension

  上面是Extension的定义,可以看出它非常简单,简单到就是一个trait,没有任何字段和方法。也就是说我们实现的对akka的扩展可以是任意形式的类,而且会被保证加载一次,那么是如何保证只会加载一次的呢?ExtensionId也许可以回答这个问题。

/**
* Identifies an Extension
* Lookup of Extensions is done by object identity, so the Id must be the same wherever it's used,
* otherwise you'll get the same extension loaded multiple times.
*/
trait ExtensionId[T <: Extension] { /**
* Returns an instance of the extension identified by this ExtensionId instance.
*/
def apply(system: ActorSystem): T = {
java.util.Objects.requireNonNull(system, "system must not be null!").registerExtension(this)
} /**
* Returns an instance of the extension identified by this ExtensionId instance.
* Java API
* For extensions written in Scala that are to be used from Java also,
* this method should be overridden to get correct return type.
* {{{
* override def get(system: ActorSystem): TheExtension = super.get(system)
* }}}
*
*/
def get(system: ActorSystem): T = apply(system) /**
* Is used by Akka to instantiate the Extension identified by this ExtensionId,
* internal use only.
*/
def createExtension(system: ExtendedActorSystem): T override final def hashCode: Int = System.identityHashCode(this)
override final def equals(other: Any): Boolean = this eq other.asInstanceOf[AnyRef]
}

  ExtensionId也很简单,首先这是一个trait,且有一个类型变量T,要求T是Extension的子类。其中有一个apply,通过system返回一个T的实例。createExtension没有实现。那需要继续深入registerExtension的代码。

  /**
* Registers the provided extension and creates its payload, if this extension isn't already registered
* This method has putIfAbsent-semantics, this method can potentially block, waiting for the initialization
* of the payload, if is in the process of registration from another Thread of execution
*/
def registerExtension[T <: Extension](ext: ExtensionId[T]): T

  通过registerExtension的定义来看,官方注释写的也很清楚,它就是在注册一个extension,并且创建一个实例。如果这个extension已经注册过,就不再注册。

  @tailrec
final def registerExtension[T <: Extension](ext: ExtensionId[T]): T = {
findExtension(ext) match {
case null ⇒ //Doesn't already exist, commence registration
val inProcessOfRegistration = new CountDownLatch(1)
extensions.putIfAbsent(ext, inProcessOfRegistration) match { // Signal that registration is in process
case null ⇒ try { // Signal was successfully sent
ext.createExtension(this) match { // Create and initialize the extension
case null ⇒ throw new IllegalStateException(s"Extension instance created as 'null' for extension [$ext]")
case instance ⇒
extensions.replace(ext, inProcessOfRegistration, instance) //Replace our in process signal with the initialized extension
instance //Profit!
}
} catch {
case t: Throwable ⇒
extensions.replace(ext, inProcessOfRegistration, t) //In case shit hits the fan, remove the inProcess signal
throw t //Escalate to caller
} finally {
inProcessOfRegistration.countDown //Always notify listeners of the inProcess signal
}
case other ⇒ registerExtension(ext) //Someone else is in process of registering an extension for this Extension, retry
}
case existing ⇒ existing.asInstanceOf[T]
}
}

  我们来看看registerExtension的具体实现,它首先通过findExtension查找对应的ExtensionId是否已经注册,如果已经注册,则直接返回找到的结果,否则就进行创建。在case null分支中,有一个CountDownLatch。我们有必要简要介绍一下这个类的作用和使用方法。

“CountDownLatch典型用法1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒”

  也就是说registerExtension是会保证线程安全的,以保证Extension只被加载一次。extensions会通过putIfAbsent方法插入ExtensionId与inProcessOfRegistration的键值对,当然了extensions是一个ConcurrentHashMap。如果key不存在,即第一次注册的时候,则把键值对插入并返回null。所以第一次注册会命中case null,然后把当前ActorSystem传给createExtension方法创建Extension实例。如果创建成功,就会替换extensions中ExtensionId对应的value为新创建的Extension实例(替换之前是inProcessOfRegistration这个CountDownLatch),最后执行countDown,计数器变成0。如果创建失败呢?会抛出一个IllegalStateException异常或其他异常,收到异常后,会把ExtensionId对应的value变成对应的Throwable信息。那么如果putIfAbsent插入失败呢,也就是ExtensionId已经有对应的value了,会递归执行registerExtension重新注册,既然有值了为啥还要重新注册呢?因为对应的值有三种情况:Extension实例、Throwable、CountDownLatch。所以需要重新注册。

  另外CountDownLatch一定会有await,那么啥时候await呢。别急,还有findExtension没有分析呢。

 /**
* Returns any extension registered to the specified Extension or returns null if not registered
*/
@tailrec
private def findExtension[T <: Extension](ext: ExtensionId[T]): T = extensions.get(ext) match {
case c: CountDownLatch ⇒
c.await(); findExtension(ext) //Registration in process, await completion and retry
case t: Throwable ⇒ throw t //Initialization failed, throw same again
case other ⇒
other.asInstanceOf[T] //could be a T or null, in which case we return the null as T
}

  很显然,findExtension会对查询到的结果进行判断,如果是CountDownLatch就调用await进行等待,等待其他线程的registerExtension执行完毕,然后递归调用findExtension;如果其他线程注册完了返回异常,则此处也简单的抛出异常;如果返回其他类型的数据,则把它转化成T的一个实例,也就是我们自定义的Extension,那如果返回null呢?那就返回null喽。

  至此registerExtension分析完毕,它以线程安全的方式保证Extension被加载一次,也就是createExtension方法只被调用一次。那么如何根据ActorSystem创建我们自定义的Extension就非常灵活了。

  我们来看一下官网的例子。

class CountExtensionImpl extends Extension {
//Since this Extension is a shared instance
// per ActorSystem we need to be threadsafe
private val counter = new AtomicLong(0) //This is the operation this Extension provides
def increment() = counter.incrementAndGet()
}

  上面是我们自定义的一个Extension,它非常简单,就是一个计数器,且increment()保证线程安全。

object CountExtension
extends ExtensionId[CountExtensionImpl]
with ExtensionIdProvider {
//The lookup method is required by ExtensionIdProvider,
// so we return ourselves here, this allows us
// to configure our extension to be loaded when
// the ActorSystem starts up
override def lookup = CountExtension //This method will be called by Akka
// to instantiate our Extension
override def createExtension(system: ExtendedActorSystem) = new CountExtensionImpl /**
* Java API: retrieve the Count extension for the given system.
*/
override def get(system: ActorSystem): CountExtensionImpl = super.get(system)
}

  上面是一个ExtensionId,还继承了ExtensionIdProvider,ExtensionIdProvider源码如下,其实就是用来查找ExtensionId的,这样就能够通过配置文件自动加载了。

/**
* To be able to load an ExtensionId from the configuration,
* a class that implements ExtensionIdProvider must be specified.
* The lookup method should return the canonical reference to the extension.
*/
trait ExtensionIdProvider {
/**
* Returns the canonical ExtensionId for this Extension
*/
def lookup(): ExtensionId[_ <: Extension]
}

  可以看出createExtension就是new了一个CountExtensionImpl,没有把ExtendedActorSystem传给CountExtensionImpl。其实在稍微复杂点的Extension里面是可以接收ExtendedActorSystem参数的,有了对ExtendedActorSystem的引用,我们就可以调用ExtendedActorSystem的所有公开的方法。如果你要问我ExtendedActorSystem都有哪些公开的方法或者说,有了ExtendedActorSystem可以做什么,我是拒绝回答的。有了ExtendedActorSystem你还不是想干啥就干啥?哈哈。

akka-extensions

CountDownLatch的简单理解

Akka源码分析-Extension的更多相关文章

  1. Akka源码分析-Cluster-Metrics

    一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...

  2. Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster

    在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...

  3. Akka源码分析-Persistence

    在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...

  4. Akka源码分析-Cluster-ActorSystem

    前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...

  5. Akka源码分析-Akka Typed

    对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...

  6. Akka源码分析-Akka-Streams-概念入门

    今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...

  7. Akka源码分析-Cluster-Singleton

    akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...

  8. Akka源码分析-local-DeathWatch

    生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...

  9. Akka源码分析-Persistence Query

    Akka Persistence Query是对akka持久化的一个补充,它提供了统一的.异步的流查询接口.今天我们就来研究下这个Persistence Query. 前面我们已经分析过Akka Pe ...

随机推荐

  1. 反片语(Ananagrams,Uva 156)

    输入一些单词,找出所有满足如下条件的单词:该单词不能通过字母重排,得到输入文 本中的另外一个单词.在判断是否满足条件时,字母不分大小写,但在输出时应保留输入中 的大小写,按字典序进行排列(所有大写字母 ...

  2. UVA - 514 Rails(栈模拟)

    题目: 给出一个序列,问将1,2,3,4……按从小到大的顺序入栈,能否得到给出的序列. 思路: 用stack模拟就可以了. 当前的cnt如果小于a[i],就将cnt入栈,否则就判断栈顶是不是和a[i] ...

  3. Python介绍以及Python 优缺点

    Python是先编译成字节码,然后在解释执行的一门语言,而不是单纯的解释型语言 Python应用场景: Web应用开发. 操作系统管理,服务器运维的自动化脚本, 网络爬虫 科学计算 桌面软件 游戏 服 ...

  4. 03 Python的那些事

    目录: 1) 创始人以及重要发展历程 2) Python语言的特点 3) TIOBE排名 4) 解释器 5) Python后缀名 6) 变量规则和约定 7) 常量 8) 注释 9) 缩进 10) Py ...

  5. Spring核心技术(二)——Spring的依赖及其注入

    本文将继续前文,描述Spring IoC中的依赖处理. 依赖 一般情况下企业应用不会只有一个对象(或者是Spring Bean).甚至最简单的应用都要多个对象来协同工作来让终端用户看到一个完整的应用的 ...

  6. 【Codeforces 300C】Beautiful Numbers

    [链接] 我是链接,点我呀:) [题意] 让你找到长度为n的数字 这个数字只由a或者b组成 且这n个数码的和也是由a或者b组成的 求出满足这样要求的数字的个数 [题解] 枚举答案数字中b的个数为y,那 ...

  7. 这可能是vue-cli最全的解析了……

    题言: 相信很多vue新手,都像我一样,只是知道可以用vue-cli直接生成一个vue项目的架构,并不明白,他究竟是怎么运行的,现在我们一起来研究一下... 一.安装vue-cli,相信你既然会用到v ...

  8. [bzoj1001]狼爪兔子[平面图的最小割等于其对偶图的最短路]

    一定要仔细算内存,,,又少写一个零.. #include <bits/stdc++.h> using namespace std; template<const int _n,con ...

  9. 苹果树(codevs 1228)

    题目描述 Description 在卡卡的房子外面,有一棵苹果树.每年的春天,树上总会结出很多的苹果.卡卡非常喜欢吃苹果,所以他一直都精心的呵护这棵苹果树.我们知道树是有很多分叉点的,苹果会长在枝条的 ...

  10. Cocos2d-x 3.x 图形学渲染系列十一

    笔者介绍:姜雪伟.IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...