16 故障诊断指南
本章旨在总结的一些比较常见的错误场景。它是基于在论坛上提问编译的,而且很有可能会随着时间的推移而增长。在你遇到问题的时候,如果你遵循这一章指导方针并且在论坛上发帖子之前做一些调查这将是很好的。请从16.1配置日志开始,因为日志应该在调试的时候打开,然后找到处问题的部分。
16.1配置日志
一些在Parsley中的服务和Flex绑定通常不重新抛出错误。因此如果日志不打开,你会对某些类型的错误摸不着头脑。调试Parsley应用程序通常指定 org.spicefactory.parsley.*的过滤和禁止Spicelib底部的日志已经足够了,作为反射缓存发出一些非常详细的日志,会干扰你感兴趣的东西。
Parsley的日志输出包括了上下文创建或者销毁,对象被配置或者从上下文删除,消息被派发或者视图被连接。
日志通常做在*应用程序类中设置。这要根据环境的不同:
Flex 4
<fx:Declarations>
<s:TraceTarget
includeCategory="true"
includeLevel="true"
includeTime="true"
level="{LogEventLevel.DEBUG}"
>
<s:filters>
<fx:String>org.spicefactory.parsley.*</fx:String>
</s:filters>
</s:TraceTarget>
</fx:Declarations>
Flex 3
<mx:TraceTarget
includeCategory="true"
includeLevel="true"
includeTime="true"
level="{LogEventLevel.DEBUG}"
>
<mx:filters>
<mx:String>org.spicefactory.parsley.*</mx:String>
</mx:filters>
</mx:TraceTarget>
16.2 不报告的错误
尽管你像前面描述的一样配置了日志后,你仍然可能会遇到这样的情况:一个预期的行为不执行,日志输出也没有动静。因为Parsley一般会记录所有错误,这通常暗示了一个问题,框架甚至没有尝试执行所需的操作。这通常是由某种错误配置引起的。下面的章节列表最常见导致失败的场景:
对象不是托管的
一些开发人员刚接触IOC容器,只是简单地添加元数据比如[Inject] 一个类,然后new一个实例,让人大感意外的是,什么也不会发生。这样的实例框架是不会知道的,从而将永远不会得到配置。另一个常见的错误是按照需求添加类到Parsley配置文件或者类中,然后用new创建一个实例。这是一个类似的问题:你现在有同一个类的两个实例,但是用new创建的不被框架托管。
如果你不熟悉托管对象的概念,请首先阅读 8.1 关于托管对象。
如果你不确定对象是否被托管,你可以使用框架API来解决这个问题:
var obj:SomeClass = ...; // the object that does not work
trace("managed? " + ContextUtil.isManaged(obj));
除了这个你还会发现日志记录了所有对象正在从上下文添加或者删除。他们看起来像:
21:09:07.967 [INFO] org.spicefactory.parsley.core.lifecycle.impl.DefaultManagedObjectHandler
Configure managed object with [ObjectDefinition(type = com.foo::MyClass, id = someId)]
and 2 processor(s)
元数据不编译到SWF或SWC
另一个很常见的错误。当编译元数据到SWFs中的时候,Flex SDK的mxmlc编译器有一个令人困惑的不一致的行为。你合并Parsley到代码中或将其指定为外部库的结果是不同的。比如,通常在没有额外的配置步骤的*应用程序正常工作,但是当用于Flex模块会失败。
找出元数据是否已经编译到你的应用程序中,你可以trace Flash Player为这个类的反射输出。
var obj:SomeClass = ...; // the object that does not work
trace(describeType(obj));
在这里你可以看到你期望的属性或方法上的元数据是否存在。对于Flex模块或RSLs,元数据通常必须显式地指定。有关详细说明见 3.1 AS3元数据配置,特别是小标题 编译自定义元数据到SWFs中 的部分。
private 或者protected 成员上的元数据
Flash Player上的反射功能只会考虑public成员上的元数据。这不是Parsley框架的一个限制,这是Flash Player的工作方式。因此以下只会被忽略,你不会得到任何错误信息:
[Inject]
private var service:RemoteObject;
元数据打错了
尽管如此,很明显这是值得一提的,这也会导致隐蔽错误。当你在写Parsley元数据标签的时候拼错一个属性,框架会抛出一个错误,但是当你拼错元数据的名字,错误会被忽略。
对象初始化的时间问题
也是很常见的场景。依赖注入在这种情况下确实会执行,只是不在开发者期望的时间上。最极端的例子是试图访问一个对象的构造方法中标签为 [Inject]的变量的值。很明显这个时候注入不可能已经执行了。
不太明显,但仍然很常见的是使用Flex组件事件像addedToStage或creationComplete,然后在事件处理程序中访问期望注入了的值,请不要这样做!有很多理由不使用addedToStage为组件初始化,其中一些甚至跟Parsley无关。原因请查看 9.7组件的生命周期,特别是 小心组件初始化逻辑 章节。
作为一个该章节非常简短摘录,放置一些初始化逻辑的安全的地方是标签 [Init]的方法。这只会在所有注入完成后调用:
[Inject]
public var service1:SomeService;
[Inject]
public var service2:SomeOtherService;
[Init]
public function init () : void {
// here it is guaranteed that service1 and service2 have been set already
}
16.3 视图连接问题
对于连接组件重要的是要理解Flex组件的生命周期与Parsley对象管理的关联。其中一个问题与错位的初始化逻辑(这并不只是影响视图)已经在前一节中描述 对象初始化的时间问题。这个部分列出的一些常见问题纯粹只跟连接视图有关。
当一个视图重新添加到舞台的错误
已连线的视图的默认行为是(无论你是否正在使用视图自动装配或 <Configure>标签),一旦视图从舞台中删除,这个视图就从上下文删除。这是最方便的选择,因为这就是需要在许多场景视图不重用开发。但当你重用一个视图并且不知道这种行为你会遇到意想不到的行为。依赖关系比如表示层模型可能会第二次注入,导致视图失去它的内部状态。
幸运的是有一些方法可以避免这一点,因为这只是默认行为。更多细节见9.7组件的生命周期,最好包括谨防内存泄漏的部分,因为改变默认的行为可能确实解决你的问题,但如果做得不合适同时会导致泄漏。
如果< ContextBuilder >标签本身被放置在一个视图中,甚至可能遇到当你从舞台删除视图,整个上下文被销毁了的情况。这仅仅是默认的,可以关闭,在9.7组件的生命周期解释。
Error: Object of type [SomeViewClass] is already managed
Parsley2.4引入检查以防止相同的对象得两次连接,潜在的两个不同的上下文中,从而可能导致不确定性行为。如果你看到这个错误可能有以下原因:
l 你没有使用最新版本。如果你使用的是一个显式连接机制如Configure 标签和API,你至少需要2.4.0版,如果你使用的是视图自动装配你至少需要2.4.1版。
l 你可能确实有复制配置。这可能发生在你结合了一个显式的机制比如Configure 标签结合视图自动装配(设置ViewSettings标签的autowireComponents属性为true)。
l 你可能使用了一个老的视图连接机制,在2.2到2.4引入的,不再支持和不提供完整的生命周期特性。这包括手动派发configureIOC或者configureView类型的事件。
Problems with popups and AIR windows
当在弹出窗口或者AIR Windows中的时候需要执行一些额外的步骤,因为这些类型的组件并不是放在正常的视图层级中的根Application 组件下的。最基本的是解释在 9.8 Flex Popups and Native AIR Windows。如果你遵循这些说明仍然遇到问题,他们可能是由于下列问题:
l You get errors claiming that dependencies could not be found.:这甚至可能发生在你连接弹出或窗口到一个Parsley上下文,如果上下文不是正确的那个。你连接窗口到需要从上下文获取依赖的那个上下文,这在一个多上下文的应用程序是很重要的。每个上下与他们的父亲共享依赖,反过来则不行。所以如果你连接你的窗口到根上下文,定义在任何子上下文的任何依赖项都不会被看到。
l You get reflection errors claiming that some class cannot be found in a particular ApplicationDomain:这是一个非常类似的问题:当你使用模块和windows,尤其重要的是要连接窗口到正确的上下文。如果window shell是定义在application 中并且window加载了一个模块,它是完全足以连接窗口到根上下文中只要加载的模块在自己内部定义一个上下文。但是如果window 或者popup从一个已加载的模块中启动,你必须确保窗口连接到了模块中的上下文。请参见下一小节进一步使用模块的常见的错误场景。
16.4 模块问题
加载模块的应用程序通常也是多上下文的应用程序,因为每个模块通常至少在模块中创建一个内部上下文。使用模块产生错误通常发生在:上下文在不同的层次没有正确的‘连接’或者视图连接到了错误的上下文上。这往往出乎意料,因为当你加载一个模块和/或创建一个子上下文的时候引擎下发生了很多魔法。Parsley通常自动连接他们,创建一个匹配上下文中创建的视图层级的上下文层级。这样任何子上下文总是会自动从父上下文分享依赖关系。在某些场景中这魔法不工作,经常由于配置问题。在少数情况下,它甚至可能需要手动指定父上下文和/或ApplicationDomain,这可以通过<ContextBuilder>标签的属性来完成。
Reflection Errors ("Specified ApplicationDomain does not contain the class MyClass")
这只会发生在当你加载一个模块到子ApplicationDomains中。反射错误通常有以下两个原因:
l The module does not declare its own Context internally.当你想连接视图到一个模块,这个模块加载到了子ApplicationDomain中,那么这个模块就必须创建它自己的上下文。这是因为每个上下文用一个ApplicationDomain进行内部反射。如果你只想连接视图并且不想在模块上下文声明任何模型或服务,你可以简单地添加一个空的< ContextBuilder / >标签来根模块组件。
l The view is wired to the wrong Context.这可能发生在即使模块创建了自己的上下文并且这个Context 也有正确的ApplicationDomain 来进行反射。最后可能的原因是视图没有连接到模块上下文,但是连接到了根应用程序上下文。视图通过冒泡事件连接到最近的上下文,这通常按预期工作。只有少数的场景,可能会导致问题(参见问题 Problems with popups and AIR windows提供了一个例子)。你可以使用日志输出,在下一节中演示的验证视图是否连接到正确的上下文。
Missing Dependencies
如果你看到无法找到一个特定的依赖的错误,尽管你确定你在一个上下文配置文件中声明过了,那么可能这些视图连接到了错误的上下文。共同的错误场景看Problems with popups and AIR windows。
在这种情况下检查日志也有帮助,因为它让你轻松的找出哪个上下文连接到哪个父亲和哪个视图连接到哪个上下文。例如我们假设你使用MyRootConfig作为MXML配置类 创建了根应用程序上下文,然后MyModuleConfig 作为模块的配置。在模块里你有一个需要被连接的视图类型MyWindow。如果一切顺利,你应该在日志输出中找到这三个条目(与其他日志交织在一起)
22:18:54.953 [INFO] org.spicefactory.parsley.core.bootstrap.impl.DefaultBootstrapConfig
Creating Context [Context(FlexConfig{MyRootConfig})] without parent
[...]
22:19:01.270 [INFO] org.spicefactory.parsley.core.bootstrap.impl.DefaultBootstrapConfig
Creating Context [Context(FlexConfig{MyModuleConfig})]
with parent [Context(FlexConfig{MyRootConfig})]
[...]
22:19:01.544 [DEBUG] org.spicefactory.parsley.core.view.impl.DefaultViewConfigurator
Add view 'My_Module.ApplicationSkin2._AppSkin_Group1.contentGroup.TestModule23.MyWindow25'
to [Context(FlexConfig{MyModuleConfig})]
在这里你可以检查该模块上下文发现它的父亲(the root ApplicationContext)和视图MyWindow 连接到的模块上下文而不是根上下文。你也可以提高日志的可读性,当你显式地设置ContextBuilder的description 属性:
<parsley:ContextBuilder config="{MyRootConfig}" description="root"/>
这将把输出 [Context(FlexConfig{MyRootConfig})] 变成 [Context(root)]。如果你只使用一个配置类这不会有很大的影响,但是如果你使用多个配置,默认描述会把他们全部列出来。