MTS
简述
·MTS是一种中间层COM组件宿主环境
·中间层组件应当是无状态的
·依靠无状态模式,MTS可以缓冲资源,在客户端之间共享组件
·COM和MTS组合起来,进化成为COM+
·利用类和向导,Delphi简化了MTS组件的创建
·从ActiveX类库入手--MTS组件必须是进程内组件
·创建MTS对象,选择线程模型和交易控制
·在类型库编辑器中定义对象接口
·调用SetComplete和SetAbort,告知MTS何时结束方法
·使用ObjectContext.CreateInstance在同一个事务中创建其它MTS组件
·使用OnActivate和OnDeactivate初始化和清除对象
·把组件安装到MTS包,并在正式使用之前测试
·最后,配置安全设置
介绍
本文将说明如何创建NT4的MTS组件和Windows2000的COM+组件。首先我想解释MTS是什么,还有一些关于它的理论。然后描述如何建立MTS组件,包括关于如何减少错误发生率的窍门。要提及的问题包括错误处理、调试、安全配置和安装等。
我将用Delphi来实现一个简单的COM数据库组件--用ADO连结ACCESS数据库。
什么是MTS
MTS--Microsoft Transaction Server(微软事务服务器)远不止于一个事务服务器那么简单。它是一个COM组件的宿主环境:·管理系统资源(比如:进程、线程、连接等等);·管理服务器对象的创建、执行和删除;·自动初始化和控制事务·进行安全管理,防止未经授权用户存取应用程序;·提供工具,用于配置、管理、发布应用程序的组件
关键的方便之处在于,开发者可以不去考虑中间层程序的多个方面,让他们集中于业务逻辑的实现。
原理
MTS是如何运作的呢?
它为分布式系统构建一个中间层,包容COM组件。在一个典型的系统中(图一),它将:
图一一个MTS应用程序
·从数据库取得数据;·传送到(瘦)客户端应用程序;·从客户端接收反馈数据;·实现业务逻辑操作;·保存到数据库。
考察客户端应用程序操作。大多数时候它们在等待用户输入。所以,如果每个客户端程序都在中间层拥有一个单独的组件和数据库连接,多数时候这些资源被浪费了。
MTS要做的,就是通过共享资源来彻底减少组件数量和数据库连接。在小系统中这无足轻重,但是在大系统(微软建议超过100个客户端)中,就大大不同了。
什么是组件
上文提到组件。什么是组件?这里所说的组件,不是在面向对象分析意义上的组件。
一个典型的对象拥有方法和属性。试考察一个安装到中间层的对象,如果我们想通过它获取客户姓名、地址和电话号码,那么,就要调用某个方法、传递客户信息参数、获取姓名属性,然后是地址,最后才轮到电话号码。每个请求都需要一个网络周期,一共四个周期--非常缓慢、缺乏效率。
实践上,一般只调用一个方法,返回所有需要的数据。尽管有些查询结果是多余的,但是性能却提高了。
使用单个方法调用还有另外一个好处,也就是MTS所利用的好处。在通常使用的"对象模型"中,对象总是要保存数据以供查询。这就意味着只能为一个客户端服务。假如一次性返回所有数据,对象就无需保存任何信息--进入无状态模式。这时,对象就能为其它客户端服务。这就是MTS的核心了。
这样,业务对象变成了代码库,美其名曰--组件。
资源管理
实际上,当组件完成一个方法调用时,MTS就毁掉它。听起来有些疯狂。组件每次都要重新创建,包括线程、数据库连接等等资源都是如此。特别是后者更加令人费解,因为数据库连接的创建成本高昂。为了节约成本,MTS缓存了很多资源。当一个新组建要求连接数据库,MTS就提供一个现存的连接。组件完成工作后,MTS把连接取回,而不是关闭它。运行组件的线程也用这样的方式被缓存。
所以,重复创建和毁坏组件的成本就这样减低了。如果客户端连接数量足够多,资源节省完全足以抵消成本。数据库负载也减轻了。
了解MTS创建和释放组件、以及缓存支持资源的方式,是创建高效MTS组件的关键。
注意:数据库连接能否被缓存,取决于数据库引擎是否支持。
COM+
MTS在Microsoft NT4上运行。对于Windows 2000,微软把MTS整合进COM架构,构建了COM+.COM+包括MTS所有特性,还有额外的支持。MTS包在Windows 2000中变成了COM+应用程序。但是,要在MTS在Windows 2000上运行,无需做任何修改。本文将不做区分。
COM+的新特性之一是组件和数据库连接都可以被缓存。这将带来明显的性能提高。
上下文(Context)
理论讲得差不多了。在被请求的时候,MTS组件被动态创建和释放,这称作即时活动。组件需要的资源被缓存,并为所有组件共享。
在开始用Delphi创建MTS组件之前,必须理解一个关于MTS运作的细节。
当客户端创建COM对象,它会收到一个指向被创建对象的接口。对象被释放后,这个接口不再合法,对它的方法调用就会失败。MTS要做点什么来避免发生这种情况。同时,它还要保存一些客户端状态信息,像安全配置和事务信息等等。
于是就引入了context(上下文)对象。我觉得可以不是那么准确地把它看作是MTS组件的替身,使得客户端接口始终保持合法性。如图二所示。
图二MTS Context对象
客户端初次创建MTS组件的时候,同时也创建了context对象。在客户端保留组件接口的时候,这个context组件一直存在。当客户端发出调用方法请求时,context对象就创建组件、运行方法、然后释放组件。Context对象的实际运作相当复杂,不过原理就是如此。
看起来好像为每个客户端保留一个context对象违背了即时活动减低资源消耗的初衷。其实,context对象非常小,给MTS服务器带来的负荷,远比真正的MTS组件来得小。
在Delphi中创建MTS组件
为了保证上述操作的正确性,所有创建的MTS组件都要通过context对象与MTS联系。而它本身也是一个通过接口存取的COM对象。
Delphi中有一系列的类和向导,可以让我们不必考虑这些问题。我们可以从Delphi中继承的基类(TmtsAutoObject和TmtsDataModule)已经封装了和context对象的协调工作,而我们只需要调用继承的方法就万事大吉。这两个类也实现了IobjectControl接口,MTS用这个接口来控制组件。
第一步,要决定COM组件是进程内还是进程外服务器。如果是MTS则没得选--必须是进程内服务器。所以,首先要创建一个ActiveX Library。这样做:从File|New对话框的ActiveX页选取ActiveX Library。
图三 选取ActiveX Library
然后创建一个MTS组件。有两种选择:要么是MTS Object,要么是MTS Data Module,它们都在Multitier页。由于MTS Data Module主要用来创建MTS MIDAS应用程序,所以我们用MTS Object。
图四 选取MTS Object,进入MTS Object向导
图五 MTS Object向导
我们需要定义COM对象的CoClass名称和线程模型。MTS中,一般用Apartment线程模型。在COM+里,也可以用Apartment,但是不支持组件pooling(缓存)。我们可以用Both来达到这个目的。Both线程模型将创建多线程(Multi Threaded)Apartment COM对象,同时也支持单线程(Single Threaded)Apartment。STA和MTA之间的区别,超出了本文讨论范围,按下不表。
在这里可以指定事务模型,或者在组件安装到MTS中再改变也可以。事务模型确定MTS是否在每次方法调用时为组件创建一个事务控制。稍后我们再讨论这个问题。
通常来说,如果组件需要更新数据库,那就选择"Requires a transaction(需要事务处理)",否则选择"Supports transactions(支持事务处理)",这样可以提高性能,因为MTS使用分布式事务处理、可以在事务中更新多个数据库。不幸的是,这样做成本负荷太高。最好是把所有的更新方法放到单独的组件中,事务只在需要的时候被创建。
Event support(事件支持)对于MTS组件一般不需要。
向导完成后,可以在类型库编辑器中创建方法。组件是无状态的,所以一般不用属性。
在本例中我将创建一个方法,称作GetData。这个方法返回一个Variant值,但实质上是一个ADO Recordset。可以在类型库编辑器的Uses页包括ADO类型库,指定返回值为Recordset。但是variant类型在本例中更简单实用。
图六 类型库编辑器--定义GetData方法
注意:如果返回类型不是数据集,Delphi 4将会引起一个异常,导致组件崩溃。最好不要用D4开发MTS组件。
编写方法
定义好方法之后,开始写代码。最重要的是要告诉MTS组件工作已经完成,让MTS释放组件或放到缓存。同样也需要告诉MTS事务是否应该提交或者回滚。如果我们省略这一步骤,MTS就会把组件看作是有状态的,资源节省就无从谈起了。
TmtsAutoObject提供了七种可调用的方法,用来和context对象打交道。
·DisableCommit 状态组件使用,超出本文范围
·EnableCommit 状态组件使用,超出本文范围
·IsCallerInRole 组件内安全检查
·IsInTransaction 检查MTS是否创建事务
·IsSecurityEnabled 检查安全查看是否活动
·SetAbort 告诉MTS组件完成工作,放弃事务
·SetComplete 告诉MTS组件完成工作,提交事务
后两个就是我们要用的了。下面提供一种通用的MTS组件方法编码结构:
function TMTSDemo1.GetData: OleVariant;
begin
try
SetComplete;
except
SetAbort;
Raise;
end;
end;
如果出错,MTS马上就知道,同时引发一个异常,通知客户端。
SetAbort和SetComplete的作用,就像对事务处理的结果进行投票。一个事务可以为多个组件分享。如果其中一个组件调用了SetAbort,整个事务就被回滚。在这之后,所有数据库操作均不被允许进行。所以,只在必须时调用SetAbort。如果组件试图存取数据库,就会引发一个$8004E004异常。这是在MTX单元中定义的。
因为必须确保SetAbort和SetComplete只被调用一次,而且一个方法有可能被组件内的其它方法调用,所以我通常把每个方法主要代码放在内部例程,而用一个外部例程来调用它。用一个下划线前缀表示内部例程,像这样:
function TMTSDemo1.GetData: OleVariant;
begin
try
Result := _GetData;
SetComplete;
except
SetAbort;
Raise;
end;
end;
通过这个方法,我用ADO COM对象直接取得一个关闭的数据集,返回到客户端。这是一个典型的MTS组件方法。我们需要它从客户端存取编辑的数据集,并更新数据库。
function TMTSDemo1._GetData: OleVariant;
var
Data : Recordset;
StrConnectionString : string;
Begin
strConnectionString := 'DSN=DelphiMTSDemo';
Data := CoRecordset.Create;
try
Data.CursorLocation := adUseClient;
Data.Open ('SELECT * from CUSTOMERS', StrConnectionString,
adOpenKeyset, adLockBatchOptimistic, adCmdText);
Data.Set_ActiveConnection(nil);
Result := Data;
Finally
Data := nil;
end;
end;
因为MTS缓存了数据库连接,所以尽量不要太早打开数据库连接,而且尽早释放它,降低其它组件创建新连接的可能性。如果缓存池中没有可用连接,就会发生这种情况。
调用其它MTS组件
MTS应用程序的设计目的之一是集中多个简单组件,创建比较复杂的系统。这种模式便于开发,可重用性也较高。那么,在组件中调用其它MTS组件要注意些什么呢?
如果是在MTS环境中,如果第二个组件和第一个组件分享一个事务,就必须通过context对象告诉MTS.很不幸,Borland没有把它作为context对象的一个方面封装进TmtsAutoObject类。
对象上下文接口(object context interface)有一个叫做CreateInstance的方法,用这个方法,我们可以要求创建其它MTS对象。如果事务模型是"Requires a Transaction"或者"Support Transactions",新创建的对象将被列入同一个事务中。如果事务模型是"Requires a New Transaction(要求新事务)",就会有一个新事务被创建。
TmtsAutoObject有一个保护的属性--ObjectContext,利用它,就能够得到context对象接口。要创建MTSDemo1组件的一个实例,用下面的代码:
ObjectContext.CreateInstance(CLASS_MTSDemo1, IMTSDemo1, obj)
CreateInstance得到要创建的类的GUID,返回的接口和接口变量作为它的参数。因为是直接的COM调用,所以要用OLECheck函数来包装它,在出错的时候引起一个Delphi异常。
下面我们创建第二个MTS对象(MTSDemo2)。这个对象也有一个GetData方法,调用MTSDemo1的GetData方法:
function TMTSDemo2._GetData: OleVariant;
var
obj: IMTSDemo1;
begin
OLECheck(ObjectContext.CreateInstance(CLASS_MTSDemo1, IMTSDemo1, obj))
try
Result := obj.GetData;
finally
obj := nil;
end;
end;
初始化
如果需要运行某些代码来初始化MTS组件,不要把这些代码放在Delphi constructor(构造器)或者COM Initialize方法中,而应该放在MTS组件的OnActivate方法里面。这是TmtsAutoObject的一个保护方法,我们要在组件中重载它。组件创建后,MTS将调用这个方法,在真正的方法调用之前做好准备。它总是在方法调用之前被调用,即使组件是从对象缓存池(COM+)中取回的。
这样做的原因是对象缓存。对于一个被缓存的对象,无论它刚被创建、或是从缓存池中取回,都应该有同样的表现。所以,以同样的方式初始化对象,是非常重要的。
要做最后的清理工作,在OnDeactivate方法里面操作。
在上述两个方法中,最好不要出现异常。MTS认为初始化过程不应该失败,所以没有很好地处理这个事件。客户端只会看见一个灾难性失败:没有任何其它可用信息。
所以,一定要用try/except块封装代码。而且,在OnActivate方法中,如果处理了错误,就应该把相关信息记录到组件中,方法本身应该检查这些信息,如果需要,引发一个异常。
对象缓存
假如是为COM+环境开发组件,想利用对象缓存(Object Pooling)的话,请依照下列步骤操作:
第一,用Both线程模型。
第二,重载TmtsAutoObject.CanBePooled方法,返回True.缺省值是False,关闭缓存功能。
第三,无论对象是刚被创建还是从缓存池中取回,它都应该初始化为同样状态。
最后,可能是最困难的,确保对象是线程安全的,这已经超出本文讨论范围。
把组件安装到MTS环境
我们已经做好一个MTS组件,现在需要测试它了。首先要把它安装到MTS环境。如果在Delphi中,简单地选择"Run|Install MTS Object"就可以了。"安装MTS对象(Install MTS Object)"向导将会询问容纳组件的MTS包(COM+应用程序)名称。
图七 MTS组件安装向导--选择组件
选定要安装的组件,在接着出现的对话框中,你可以选择一个已有的包,或者指定名称、创建一个新包。只需要输入名字即可,当然也可以输入关于包的描述。其它都交给向导完成。
图八 MTS对象安装向导--指定容纳组件的包
MTS包
什么是包?(什么是Windows 2000中的COM+应用程序?)
MTS组件是进程内DLL,所以需要一个应用程序进程来运行。对于MTS,这个程序就是mtx.exe;对于COM+,就是dllhost.exe。如果所有组件都在同一个进程空间运行,任何致命异常都会导致所有MTS应用程序失败。MTS把组件放到包里面,每个包都在独立的进程中运行,这样来尽可能地限制损坏发生。
一个包可以包括来自多个dll的组件,一个dll中的组件可以分散到多个包。唯一的限制是一个组件只能在一个包中出现。
在MTS环境中,除了提供进程隔离,包还可以用来定义安全分界。所有来自外部的调用都要经过检查,看是否有足够的权限。包内调用不被检查。在COM+中,安全检查在更适宜的层次进行,甚至可以细致到对每个方法进行安全设置。
调试
现在组件已经安装到MTS中了,如何运行、调试它呢?
我们需要一个调用组件方法的客户端程序。我总是为每个组件/组件组创建一个测试程序,这样便于调试和测试。
对于MTSDemo1组件,我们要做一个简单的窗体,包括一个DBGrid,一个DataSource,一个ADODataset和一个按钮。按钮单击代码:
procedure TForm1.Button1Click(Sender: TObject);
var
obj : variant;
begin
obj := CreateOleObject ('MTSDemo1Lib.MTSDemo1');
ADOTable1.Recordset := IUnknown(obj.GetData) as _Recordset; ;
end;
(把项目保存为MTSDemo1Lib)。
为了调试MTS组件,需要指定组件的宿主程序和参数。MTS的宿主程序是mtx.exe,在system32目录中。对于COM+,宿主程序是dllhost.exe,也是在system32目录。
二者的参数不同:
MTS: /p:"".
COM+: /ProcessID:{}.
这样,就可以从Delphi中运行组件,用任意客户端来调用,调试组件。为了同时调试组件和测试程序,用"Project|Build All Projects"菜单项进行编译。然后,运行组件和测试程序。
发布
完全测试好组件之后,准备发布组件。在MTS环境,用Transaction Server Explorer(事务服务器浏览);在COM+环境,用Component Services(组件服务)。这两个工具具有类似资源管理器的界面,非常易于使用:
图九 组件服务
在左边的树状视图中,右击一个包,选择"导出(export)",就可以导出包。我们得到一个包文件和dll的拷贝,还有全部安全设置。同时,MTS也创建一个客户端安装程序,用来在客户端计算机注册COM组件。
注意:客户端安装将注册导出包的服务器。一般我们从开发环境导出包,先安装到产品机,然后从产品机导出包,创建客户端安装程序。
一旦把包安装到产品机,就要指定组件事务模型,进行安全设置。
MTS的安全模型规则适用于NT用户组和组用户。规则授权对组件(或COM+中的单独方法)的存取。如有需要,可以用TmtsAutoObject方法IsCallerInRole和IsSecurityEnabled来进行组件内的精确控制,不过一般不需要这样做。
在Transaction Server Explorer中,利用上下文菜单,可以进行规则的创建、用户和组的指派以及组件规则的指定。