Parsley允许你构建一个有层次的上下文,可以动态地加载和卸载。不管有没有使用Flex模块,这个层次可以创建,即使是纯AS3应用程序。对于Flex模块Parsley提供了一个额外的集成,它让它更容易处理多个不同的ApplicationDomains。
10.1 模块上下文
在大型应用程序中你可能想把应用程序分割成按需求加载的模块。在这种情况下,整个上下文在应用程序启动的时候加载和初始化这将是非常遗憾的。即使分割为多个文件的配置如 3.7组合多个配置机制 所示也不会有任何帮助。因为这些文件将会被合并成一个单一的上下文装载和初始化,就好像它是一个大型的配置文件。
这是Parsley的另一个方便的特性:当创建一个上下文的时候它可以连接到一个父上下文。父亲可能是加载应用程序的主上下文,子上下文可能是一个根据需求加载的模块需要的。子上下文的配置中你可以使用任何父上下文中依赖注入的对象(反过来不行)。消息还适用于穿过上下文边界,这你派发通过的作用域。你可以创建深度嵌套层次的上下文,但结构通常会变得很乏味,一个根上下文和任意数量的子上下文在同一级别。
有两种不同的方法来初始化一个子上下文:
当你用ContextBuilder API指定一个视图根,或者当你使用引入MXML ContextBuilder标签自动把它上面的文档作为一个视图根,那么ViewManager 关联上下文将使用该视图根有两个目的:它将监听从希望得到连接到上下文的组件派发的冒泡事件,它将监听从视图层级中ContextBuilders 下面派发的冒泡事件。这样你就不必手动指定父上下文或ApplicationDomain,框架会帮你解决它:
<parsley:ContextBuilder config="{BookStoreConfig}"/>
或者
var viewRoot:DisplayObject = ...;
ContextBuilder
.newSetup()
.viewRoot(viewRoot)
.newBuilder()
.config(FlexConfig.forClass(BookStoreConfig))
.build();
在上面的例子中新的上下文将使用视图根自动寻找一个父上下文和监听在视图层次子上下文。在某些场景中,你可能会想保持上下文层次结构和视图层次分开。
当你使用ContextBuilder API的时候,你可以定义一个现有的上下文作为一个新的上下文的父上下文。
var parent:Context = ...;
ContextBuilder
.newSetup()
.parent(parent)
.newBuilder()
.config(FlexConfig.forClass(BookStoreConfig))
.build();
在本例中我们没有指定一个视图根(注意,在本例中你不能动态连接视图到这个上下文),取而代之的是手动指定了一个父亲。你还必须知道的事实是,没有视图根上下文不能够自动检测到ApplicationDomain 来反射。所以如果你不在根ApplicationDomain 工作,而是在已加载的Flex模块的子域中,你也必须传递域到构建方法中。
var parent:Context = ...;
var domain:ApplicationDomain = ...;
ContextBuilder
.newSetup()
.parent(parent)
.domain(domain)
.newBuilder()
.config(FlexConfig.forClass(BookStoreConfig))
.build();
当然这在指定视图根的时候不是必要的,因为构建器就能够自动检测父上下文和ApplicationDomain。
这个特性是从2.4版引进的。在那之前上下文层次仅限于一个树结构,所以每个上下文可能有多个孩子,它总是只有一个父亲。指定多个父亲为创建上下文层次结构添加了更多的灵活性。例如,它可以建立一组库,可以相互依赖和连接他们相应的上下文。
我们假设你正在使用2个模块,可能需要由多个其他(否则不相关的)模块的应用程序,都有自己的上下文:
var messaging:Context = ...;
var persistence:Context = ...;
如果你构建另一个需要从消息和持久化注入的上下文,你可以指示ContextBuilder使用两个上下文作为父亲。
<parsley:ContextBuilder config="{BookStoreConfig}" parents="{[messaging, persistence]}"/>
当使用ContextBuilderd 的时候它也会在视图层次中自动地寻找一个父亲。所以如果它找到一个父亲,那么就有三个父亲。你可以指示ContextBuilder不在层级结构中寻找父亲。
<parsley:ContextBuilder
config="{BookStoreConfig}"
parents="{[messaging, persistence]}"
findParentInView="false"
/>
最后,当然ContextBuilder API也允许你使用此功能。你只需调用父方法多次:
ContextBuilder
.newSetup()
.viewRoot(viewRoot)
.parent(messaging)
.parent(persistence)
.newBuilder()
.config(FlexConfig.forClass(BookStoreConfig))
.build();
上下文依赖查找优先级
当一个上下文查找一个依赖项,它首先会查找如果它发现它自己中的定义。如果没有成功,他会开始在父亲中一个接一个的找。顺序跟你指定构建上下文是相同的。当还有一个自动在视图层级中发现的父亲的时候,它总是最后一个被查找的上下文。
当通过类型使用依赖注入(不指定一个id),依赖不能模糊是很重要的。意思就是一个上下文不应该有超过一个匹配类型的托管对象。不论其他上下文是否有匹配的类型,当在上下文发现第一个匹配的实例的时候,查找就在这个上下文停止了。
共享上下文的ApplicationDomains
实际上一个共享上下文的唯一选择是存在于根ApplicationDomain中。如果你使用多个模块加载到同一个子ApplicationDomain中,不在根ApplicationDomain中,这些模块之间共享任何东西都变得非常困难。这是因为同级ApplicationDomains看不到对方的类定义。这也意味着,这样的一个共享模块一旦它被使用就不能卸载。当为非常大的应用程序规划最大内存消耗的时候这是很重要的。
根据需求加载和初始化共享上下文
2.4版本功能的介绍仅仅是开始。该版本增加了低级钩子(low-level hooks),在核心API可以构建一个灵活的上下文层次结构,每个上下文可以有多个父亲。如你看到上面给出的例子,你仍然需要自己管理这些共享上下文。所以你应用程序的责任是知道哪个父亲被哪一个上下文需要,以及如何获取和构建这些上下文实例。所以对于一个非常大的应用程序,你很可能在现有Parsley特性上仍然建立某种程度的基础设施。
在将来(发布Parsley 3.0后的某个时间),将会有一个单独的Parsley扩展项目,该项目建立在此之上。它将允许你在一个简单的XML清单中指定共享上下文,框架将加载和初始化父亲当第一次需要他们的时候。
如果你加载多个上下文实例作为模块就像在前面部分中描述的,当你卸载模块的时候你可能想要摆脱他们——而不影响父亲。实际上这是这是很容易的,这样做就是:
context.destroy();
当连接一个上下文层次结构到视图层次结构中甚至更容易了。你不必显式地销毁上下文,当最后一个上下文关联的视图根从舞台被删除的时候,它会自动摧毁。 这是ViewManager的默认行为,可以修改如果你不希望上下文的生命周期取决于视图在舞台上存在的时间。你随时可以使用ViewManagerFactory的属性调整。
当一个上下文被摧毁了发生以下动作:
l 所有配置在被销毁的上下文中的对象都会停止接受Parsley中央MessageRouter派发的消息。这影响到所有用MessageHandler,MessageBinding,CommandResult 等等注解的的元素。
l 如果任何声明在被销毁的上下文中的对象声明了ManagedEvents ,他们从现在起会被忽略,不再通过Parsleys 消息传递系统派发。
l 在被销毁上下文中的任何对象中所有用[Destroy] 注解的方法(或相应的MXML或者XML标签)都会得到调用
l 损毁的上下文将删除所有配置对象的内部引用,所以他们有资格获得垃圾收集。(当然,你必须确保你的应用程序不保留任何这些对象的引用)。
l 调用销毁后上下文可能不再使用了。这个上下文上任何后续的方法再调用都会抛出错误。被销毁上下文的父亲(如果有的话)不会受到影响,并且可能继续正常运行。
在版本2.0.x中框架提供了一个特定的ContextModule MXML标签来指定模块的配置。这不再需要了,框架指定ContextModule和ModuleLoader标签已经被删除了。取而代之的是完全透明的支持了Flex 模块。使用常规的Flex ModuleLoader标签或者Flex ModuleManager加载模块。然后你可以在模块内部的任何地方创建子上下文,它并不会发生在根模块组件中。如果你连接上下文层次结构到视图层次结构就像它并不会发生在根模块组件,子上下文将自动确定父上下文和模块的ApplicationDomain 。