NRabbit

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  102 随笔 :: 0 文章 :: 4 评论 :: 0 引用

公告

2010年5月15日 #

飞信2010,beta2版,用C++重构,下载安装,测试后发现性能的确提升不少(最大占用内存最多达到40多M),下图

 image

1、支持语音视频聊天;
2、C++重写,运行效率提升、资源占用下降;
3、全新界面设计,更清爽、更易用;
4、优化文件传输性 能;
5、群发可达64人;6、新增飞信游戏功能

全新架构PC客户端体验中心发布,欢迎大家下载体验:http://ue.fetion.com.cn/profile?t=1&backurl=/experience/detail/29
下载:飞信2010 Beta2.0

image
posted @ 2010-05-15 08:37 kevin.nrabbit 阅读(536) 评论(2) 编辑

2010年5月8日 #

    1:EF支持的映射场景

     

    简单的映射

    场景:实体数据模型(EDM)中的每个实体都映射到了存储端(即数据库)的单个表。这是实体数据类型和存储架构间最简单的 映射情况。

     

    例子:

     

    实体数据模型:

    实体容器:Test_Simple_Model_Northwind

    实体集Ccategories

    实体Ccategory

    {

    Int CategoryID;

    String CategoryName;

    String Description;

    }

    存储架构:

    存储实体容器:Test_Simple_Target_Northwind

    表:dbo.Northwind.Scategories

    {CagetoryID(int),CategoryName(nvarchar(10)),Description

    (nvarchar(max))}

    映射(MSL)文件:

     

     

    备注:使用 EdmGen 工具对现有数据库生成文件时都是简单(一对一)的映射。

     

    图一   单 表单实体映射

    备注:相关的CSDLSSDL文件请查看附录 A

     

    纵向实体分割

    纵向实体分割就是创建具有实体部分字段的表和存储实体剩余字段的其它表。Normalization本质上就是一个将实体垂 直分割的过程。在纵向分割中,EDM中的一个表可以被映射到两个或多个不同的表格。

    备注:在纵向实体分割场景中,存储端中只有主键和主键联合(Join)是允许的。

    下面这个例子中,EDM中定义的实体Category映射了两个表:SCategories1ScategoriesDate1Scategories1表中包含CategoryID(主键),CategoryNameDescripion这几个字段,而在SCategoriesDate1表中包含CategoryID(重复了!)和CreatedDate两个字段。

    图二

    映射MSL)文件:

     

    基于EDM端条件的横向实体分割

    横向分割就是把不同的实体实例放到不同的表中。譬如高优先级别的订单会被存储在HighPriortyOrders表中,同时低优先级别的订单就被保存在别的表中。这两部分的表就是HighPriorityOrdersRegularOrders,同时在EDM端里通过两个表的联合来提供一个所有订单 的整体视图。

     

    横向实体分割基于一个在MSL文件中定义的布尔条件。现在实体框架只能基于布尔条件,不过在将来也许会有更多的条件类型。< /p>

     

    图三 基于EDM端条件的横向实体分割

    例子中的映射(MSL)文件:

     

    关联

     

    关联定义于EDM中,它被用来定义两个实体类型之间的点对点关系。关联可以支持实体类型之间的真实关系。以下是EDM模型中所支持的关系类型:

    • 1-1
    • 1-*
    • 0..1-1
    • 0..1-*
    • *-*
    • Self association

     

    举个例子,在一个CSDL文件中,ProductCategory之间的关联这样定义:

     

    基数(Cardinality:上面 这个例子中每个Product关联到0或者1Category,每个Category都有个1或 者更多的Products

     

    业务行为(Operational BehaviorOnDelete 中的 cascade标志表示一个Category被删除了,那么它关联到的一个或者多个Products也会被删除。

     

    关联集

     

    关联中包含了指定的关联类型的关系实例。一个关联实例连接了用关联集关联的两个属于实体集的实体实例;譬如:给实体类型 E1 E2 一个关联类型 R,就给了关联E1(属 于实体集 ES1 E2(属 于实体集 ES2 R 的一个关联集。

    映射架构语言 (MSL)可以将EDM中定义的关联映射到数据库中包含的外键上。

    图四     实体关联的映射

    例子中的映射文件(MSL):

    1-1 关联

    实体数据模型:

    EDM的 定义片段(CSDL):

    映射定义片段(MSL):

     

    1对 多、多对多、自身关联部分的介绍省略)

     

    备注:完整的CSDLSSDLMSL文件可以到附录A中查看。

     

    衍生类型之间的关联

     

    实体类型的衍生类 型可以被指定为某关联类型的终端成员类型。

     

    例子:

    模型中定义了以下 的实体类型:

    说明:在TPHTable Per Hierarchy)和TPTTable Per Type)映射策略中支持此映射功能。TPCTable Per Concrete Class)则不支持。TCP中独有的限制使得它无法将关联指到子类型 中。通常不允许用TCP策略来映射关联;TCP的这种限制主要是源于关联的终端一定要映 射到单个表这个原因。

    结尾部分有更多关于TPHTPTTPC映射策略的内容。

     

    情形1TPH 映射架构

    如果存储是通过“TPH”实现的,那么MSL文件中“AssociationSetMapping”的定义部分里,外键必须要有条件:IsNull = false”:

    例如:

    情形2TPT映射架构

    使用TPT的时候没有指定条件

     

    映射抽象类型

    抽象类型只能用“IsTypeOf”关键字来映射。在TPH中,抽象类型的映射是可选的。即使被映射 了,抽象类型映射也没有指到它的映射对象的一个标志位。在TPT中,抽象类型需要有映射片段对应。TPC中 则不需要抽象类型映射。

     

    如果一个抽象类型 没有具体的实例化对象,映射这个抽象类型时映射加载器就会抛出一个错误。

     

    这有个在EDM中使用抽象类型的简单例子。这使用的是TPH, 因此我们不需要映射抽象类型。

    备注:附录A中有关于映射抽象类型的更多例子。

     

     

    CSDL中这个实体模型定义为:

    相应的MSL文件:

     

    继承

    TPH(Table Per Hierarchy)

    TPH中,每个级别(Hierarchy)中的所有类型都映射到在后 台存储端中的实际表中。为了实现TPH,创建一个表,它的结构包含了级别中的所有实体的属性。每个实体实例都代表一个元组(row),如果该实体没有这种属性,那么该元组的值为空(null)。这个表结构必须有主键,或者称之为 条件字段,通过它来定义一种条件,根据这个条件确定该行记录是那种类型。这个条件字段必须满足以下规则:

    • 所有定义类型映射的条件必须 是互斥的(即不相同)
    • 所有定义类型映射的条件必须 列举出所有基表中的记录
    • 用了映射条件的主键字段不能 映射到实体属性上

    CSDL 文件中的片段:

    MSL文 件中的片段:

    备注:附录A中含CSDLSSDLMSL文件的完整部分

     

    TPT(Table Per Type)

    在一个继承级别 中,所有该等级的实体都被映射到了不同的表,每个对应表都含有对应实体的所有属性,包括这些实体的父实体属性及自身添加的属性。

    CSDL 片段:

    MSL片段:

     

    Table Per concrete Class

    每个继承级别的非 抽象实体类型都被映射到不同的表中。对每个非抽象实体,表结构中含有所有实体的属性,包括从父类型中继承的。

    CSDL 片段:

    MSL片段:

    复式类型映射:

    复式类型提供采用 富(结构化)有效载荷创建属性的机制。一个复式类型可以包含在别的复式类型中。服饰类型定义中包含它的名称和有效载荷;通过所属实体类型来定义它的区别。 复式类型的有效载荷与下面这个例子中的抽象实体类型很相似:

    MSL片段(映射到单表)

     

    上面这个例子中复式类型映射到实体所映射到的表中;我们也可以将它映射到别的表里,那MSL就 是:

     

    嵌套复式类型:

    CSDL片段:

    MSL片段(映射到3个不同的表)

     

posted @ 2010-05-08 22:39 kevin.nrabbit 阅读(81) 评论(0) 编辑

2010年5月5日 #

ADO.net,Linq to SQL和Entity Framework性能实测分析

摘自:http://cid-79cf5e75e6e5fd50.spaces.live.com/blog/cns!79CF5E75E6E5FD50!379.entry

  测试环境当然就是我这台笔 记本了,受限与硬盘转速,运行起来一定是不如台式机的,但至少保证了三个方案相同的软硬件环境:Windows Server 2008,Visual Studio 2008,MS SQL Server 2008,清一色的最新产品。

  测试分成六个阶段,数据量分别为 10,10,100,1千,1万,10万逐级增长,分别测试了读取、写入、更改、删除四个基本的操作的耗时,结果如下(时间单位:秒):

第一次读写10条数据
读写方式 读取耗时 添加耗时 修改耗时 删除耗时 平均耗时
当前机制(简化) 0.007 0.35 0.02 0.014 0.09775
LINQ to SQL 0.023 0.083 0.102 0.068 0.069
Entity Framework 0.238 3.084 0.009 0.006 0.83425

image

第二次读写10条数据
读写方式 读取耗时 添加耗时 修改耗时 删除耗时 平均耗时
当 前机制(简化) 0.002 0.034 0.011 0.020 0.01675
LINQ to SQL 0.003 0.011 0.043 0.058 0.02875
Entity Framework 0.004 0.006 0.005 0.004 0.00475

image

操作100条数据
读写方式 读取耗时 添加耗时 修改耗时 删除耗时 平均耗时
当 前机制(简化) 0.005 0.202 0.103 0.062 0.093
LINQ to SQL 0.003 0.083 0.350 0.298 0.1835
Entity Framework 0.004 0.035 0.030 0.021 0.0225

image

操作1000条数据
读写方式 读取耗时 添加耗时 修改耗时 删除耗时 平均耗时
当 前机制(简化) 0.044 2.086 1.056 0.720 0.9765
LINQ to SQL 0.006 0.805 3.035 2.925 1.69275
Entity Framework 0.010 0.392 0.296 0.209 0.22675

image

操作10000条数据
读写方式 读取耗时 添加耗时 修改耗时 删除耗时 平均耗时
当 前机制(简化) 0.435 21.069 10.328 6.925 9.68925
LINQ to SQL 0.02 7、973 29.985 28.891 16.71725
Entity Framework 0.029 4.142 3.321 2.434 2.47925

image

操作100000条数据
读写方式 读取耗时 添加耗时 修改耗时 删除耗时 平均耗时
当 前机制(简化) 4.525 213.603 100.668 82.203 100.25
LINQ to SQL 0.207 80.789 305.912 290.481 169.347
Entity Framework 0.387 42.402 38.497 24.36 26.4115

image

【测试总结】

  第一阶段测试结果非常出人意料,ADO.net和LINQ to SQL操作数据的时间都控制在0.5秒以内,非常的迅速,但是Entity Framework在添加这步表现非常差,由于这五步是连续测试,其中添加数据是第一步操作,而EF在在进行第一步操作的时候足足延迟了3秒钟!这3秒钟 到底EF在做什么?

  从第二阶段开始,性能的优劣就非常明显的展现在我们面前,第二阶段到第六阶段,不论操作数据量的大小,图中的耗时 比例几乎是相同的。Entity Framework无可争议的以极高的效率在三种方案中脱颖而出,而LINQ to SQL的龟速修改和删除操作消耗的时间几乎是EF的10倍,ADO.net在添加数据上的表现实在不尽如人意,这也跟我们项目底层写法有关。

   从上面的测试结果可以看出,除去EF在初次操作数据是延迟的3秒钟(初步认为是初始化时间),EF的平均效率是LINQ to SQL的6倍,是当前项目机制的4倍,这是非常可观的效率提升,不难理解为什么微软几乎放弃了LINQ to SQL,全力支持EF了。

【深 入分析为什么第一次执行Entity Framework非常慢的原因】(转)

第一次创建 ObjectContext并查询数据时耗费了大量的时间,原因是什么?有没有什么优化的方法?本文将给出一个合理的解释。

下面这个饼 状图给出了第一次创建ObjectContext并用其访问数据库时各种操作所占的时间比

PerfBlogImage2_thumb

从中可以看出仅仅 View Generation一个操作就占用了56%的时间,不过令人欣慰的是,这个操作只出现在第一次查询的时候,之后生成好的View会被缓存起来供以后使 用。一个View.cs文件的样本如下:

无标题_thumb

我们可以使用 EDMGen2.exe来自己生成View.cs,然后把它加入到工程中编译,这样会大大缩减View Generation操作所占的时间比。根据ADO.NET TEAM 的测试,自己编译View大概会节省28%的时间。不过我在自己电脑上测试的结果没有那么理想,大概是8%左右。

posted @ 2010-05-05 12:00 kevin.nrabbit 阅读(36) 评论(0) 编辑

2010年4月30日 #

转:http://www.cnblogs.com/skynet/archive/2010/04/29/1724020.html
前几天逛codeproject时,遇到一篇比较好文章,不敢独享,故译之于君共享。Outline如下:

  • 1、引言
  • 2、两个处理步骤
    • 2.1、创建ASP.NET环境
    • 2.2、用触发的MHPM事件处理请求
  • 3、什么事件中应该做什么
  • 4、示例代码
  • 5、深入ASP.NET页面事件

1、引言

这篇文章我们将试图理解,从用户发送一个请求直到请求呈现到浏览器发生的事件的差异。因此,我们首先将介绍解ASP.NET请求的两个概括的步骤,接下来我们将介绍‘HttpHandler’,‘HttpModule’和ASP.NET页面对象发出的事件的差异。随着我们的事件旅程,我们将理解这些事件的逻辑。

2、两个处理步骤

ASP.NET请求处理可以总结为如下所示的两个处理步骤。用户发送一个请求到IIS:

  • ASP.NET创建处理请求的环境。换句话说,创建应用程序对象、request、response和context对象去处理请求。
  • 一旦环境已经创建,请求通过使用modules、handlers和page对象的一系列事件处理。为了简化可以称为MHPM(module、handler、page、module event),我们将在后面详细讨论。

1

图1、ASP.NET请求处理的两个步骤

在接下来的各节,我们将知道更多关于这两个步骤的细节。

2.1、创建ASP.NET环境

step 1:用户发送一个请求到IIS。IIS首先检查哪个ISAPI扩展可以处理这个请求,这取决于请求的文件扩展名。举例来说,如果请求页面是‘.ASPX’,它将被传送到‘aspnet_isapi.dll’来处理。

step 2:如果这是www站点的第一个请求,ApplicationManager类将创建一个应用程序域,www站点运行于其中。我们都知道在同一个IIS上,两个web应用程序的应用程序域是独立的(隔离的)。因此一个应用程序域中问题不会的影响到其它应用程序域。

step 3:新建的应用程序域创建宿主环境,如HttpRuntime对象。一旦宿主环境被创建,必要的ASP.NET核心对象如HttpContextHttpRequestHttpRespone对象也被创建。

step 4:一旦所有的ASP.NET核心对象被创建,HttpApplication对象将被创建去处理请求。如果系统中有global.asax文件,global.asax文件对象将被创建。请注意:global.asax文件继承自HttpApplication类。

注意:第一次ASP.NET页面连接到应用程序,一个HttpApplication新实例将被创建。为了最大化性能,HttpApplication实例可能被多个请求重用。

step 5:接下来HttpApplication对象分配给核心ASP.NET对象来处理页面。

step 6:然后HttpApplication通过HttpContextHttpRequestHttpRespone事件开始处理请求。它触发MHPM事件处理请求。更多细节

2

图2、创建ASP.NET环境

下图解释了ASP.NET请求的内部对象模型。最高层是ASP.NET运行时,它已经创建一个应用程序域(AppDomain),相应地有HttpRuntime包括request、respone、context对象。

3 图3、ASP.NET请求的内部对象模型

2.2、用触发的MHPM事件处理请求

一旦创建了HttpApplication,它开始处理请求,它经历3个不同的部分HttpModule、Page、HttpHandler。随着它移动到这些部分,将调用不同的事件,开发人员可以扩展和定制同一逻辑。在我们前进之前让我们了解什么是HttpModuleHttpHandlers。他们帮组我们在ASP.NET页处理的前后注入自定义逻辑。他们之间的主要差别是:

  • 如果你想要注入的逻辑是基于像‘.ASPX’、‘.HTML’这样的文件扩展名,使用HttpHandler。换句话说HttpHandler是基于处理器的扩展。

4

  • 如果你想在ASP.NET管道事件中注入逻辑,使用HttpModule。换言之是基于处理器的事件。

5

你可以点这了解他们之间更多的差异。下面是怎样处理请求的逻辑流。有四个重要的步骤MHPM,解释如下:

Step 1(M   HttpModule):客户端请求处理开始。ASP.NET引擎开始和创建HttpModule发出事件(你可以注入定制逻辑)之前,有6个重要事件你可以使用:BeginRequestAuthenticateRequestAuthorizeRequestResolveRequestCacheAcquireRequestStatePreRequestHandlerExecute

Step 2(H   HttpHandler):一旦上面6个事件触发,ASP.NET引擎将调用ProcessRequest事件,即使你已经在项目中执行了HttpHandler。

Step 3(P   ASP.NET page):一旦HttpHandler逻辑执行,ASP.NET page对象被创建。ASP.NET page对象被创建,许多事件被触发,你可以在这些页面事件中写我们自定义的逻辑。有6个重要事件给我们提供占位,在ASP.NET页中写逻辑:InitLoadValidateEventRenderUnload。你可以记住单词SILVER来记这些事件,S-Start(没有任何意义,仅仅是为了形成一个单词),I(Init)、L(Load)、V(Validate)、E(Event)、R(Render)。

Step 4(M   HttpModule):一旦页面对象执行了且从内存中卸载,HttpModule提供发送页面执行事件,它们可用于注入自定义post-处理逻辑。有4个重要的post-处理事件,PostRequestHandlerExecutePostRequestStateUpdateRequestCacheEndRequest

下图展示了上面的过程。

6

3、什么事件中应该做什么

下面的表格展示了什么事件中做什么逻辑或代码。

Section Event Description
HttpModule BeginRequest 此事件标志着一个新的请求,他保证在每个请求中都有。
HttpModule AuthenticateRequest 此事件标志ASP.NET运行时准备验证用户。任何身份验证代码都可以在此注入。
HttpModule AuthorizeRequest 此事件标志ASP.NET运行时准备授权用户。任何授权代码都可以在此注入。
HttpModule ResolveRequest 在ASP.NET中我们通常使用OutputCache指令做缓存。在这个事件中,ASP.NET运行时确定是否能够从缓存中加载页面,而不是从头开始生成。任何缓存的具体活动可以被注入这里。
HttpModule AcquireRequestState 此事件标志着ASP.NET运行时准备获得会话变量。可以对会话变量做任何你想要的处理。
HttpModule PreRequestHandlerExecute 恰好在ASP.NET 开始执行事件处理程序前发生。可以预处理你想做的事。
HttpHandler ProcessRequest HttpHandler逻辑被执行。在这个部分我们将为每个页面扩展名写需要的逻辑。
Page Init 此事件发生在ASP.NET页面且可以用来:
1、动态地创建控件,如果你一定要在运行时创建控件;
2、任何初始化设置
3、母版页及其设置
在这部分中我们没有获得viewstate、postedvalues及已经初始化的控件。
Page Load 在这部分ASP.NET控件完全被加载且在这里你可以写UI操作逻辑或任何其他逻辑。
Page Validate 如果在页面上你有验证器,你同样想在这里检查。
Page Render 是时候将输入发送到浏览器。如果你想对最终的HTML做些修改,你可以在这里输入你的HTML逻辑。
Page Unload 页面对象从内存中卸载。
HttpModule PostRequestHandlerExecute 可以注入任何你想要的逻辑,在处理程序执行之后。
HttpModule ReleaseRequestState 想保存更新某些状态变量,如会话变量。
HttpModule UpdateRequestCache 在结束之前是否更新你的缓存。
HttpModule EndRequest 这是将输出发送到客户端浏览器之前的最后一个阶段。

4、示例代码

点击下载代码,示例代码展示了事件是怎样触发的。代码中我们创建了一个HttpModule和HttpHandler,且我们显示一个简单的响应在所有的事件中。下面是HttpModule类,跟踪所有的事件且添加到全局集合。

HttpModule类

 

下面是HttpHandler的代码片段,它跟踪ProcessRequest事件。

HttpHandler代码片段

 

我们也追踪ASP.NET页面的所有事件。

asp.net页面事件

 

下面显示上面讨论的所有事件的执行顺序:

7

5、深入ASP.NET页面事件

在前面部分我们已经知道ASP.NET页面请求的整体事件流,但是我们没有详细讨论,因此本节我们将深入了解。任何ASP.NET页面有2个部分,一个是显示在浏览器上的页面,它有HTML标记、viewstate形式的隐藏值、HTML inputs上的数据。当页面被发送时,在服务器上这些HTML标记被创建到ASP.NET控件且viewstate和表单数据捆绑在一起。一旦你得到这些服务器控件的后台代码,你可以执行和写你自己的逻辑和呈现返回给浏览器。

8 现在这些HTML控件在服务器上作为ASP.NET控件,ASP.NET页面发出一些事件,我们可以注入自己的逻辑。根据任务/你要执行的逻辑,我们需要把这些逻辑放入适当的事件中。

注意:大部分开发者直接使用Page_Load方法执行一切,这不是一个好的方法。因此,不是填充控件、设置viewstate、应用主题等一切都发生在页面加载上。因此,如果我们能在适当的事件中放入逻辑,将真正使你的代码干净。

 

Seq Events 控件初始化 Viewstate可用 表单数据可用 什么逻辑可以写在这里?
1 Init No No No 注意:你可以通过使用ASP.NET请求对象访问表单数据等,但是不是通过服务器控件。
动态地创建控件,如果你一定要在运行时创建;任何初始化设置;母版页及其设置。在这部分中我们没有获得viewstate、postedvalues及已经初始化的控件。
2 Load View State Not guaranteed Yes Not guaranteed 你可以访问View State及任何同步逻辑,你希望viewstate被推倒后台代码变量可以在这里完成。
3 PostBackdata Not guaranteed Yes Yes 捏可以访问表单数据。任何逻辑,你希望表单数据被推倒后台代码变量可以在这里完成。
4 Load Yes Yes Yes 在这里你可以放入任何你想操作控件的逻辑,如从数据库填充combox、对grid中的数据排序等。这个事件,我们可以访问所有控件、viewstate、发送的值。
5 Validate Yes Yes Yes 如果你的页面有验证器或者你想为你的页面执行验证,那就在这里做吧。
6 Event Yes Yes Yes 如果这是通过点击按钮或下拉列表的改变的一个回发,相关的事件将被触发。与事件相关的任何逻辑都可以在这里执行。
7 Pre-render Yes Yes Yes 如果你想对UI对象做最终的修改,如改变属性结构或属性值,在这些控件保存到ViewState之前。
8 Save ViewState Yes Yes Yes 一旦对服务器控件的所有修改完成,可以保存控件数据到View State。
9 Render Yes Yes Yes 如果你想添加一些自定义HTML到输出,可以在这里完成。
10 Unload Yes Yes Yes 做任何你想做的清理工作。

9

 

原文链接:ASP.NET application and page life cycle

另附几篇相关的文章:

DotText源码学习——ASP.NET的工作机制

A low-level Look at the ASP.NET Architecture,对于的译文从底层了解ASP.NET体系结构

posted @ 2010-04-30 13:42 kevin.nrabbit 阅读(70) 评论(1) 编辑

页面中在<table>标签里加了background属性后,页面OnLoad会被执行多次(经两次测试IE7和FF下情况一样);

解决方法:去掉background属性,使用bgcolor属性或style{brackground:.....};

posted @ 2010-04-30 09:02 kevin.nrabbit 阅读(175) 评论(0) 编辑

2010年4月29日 #

六、利用Ioc在不修改任何原有代码的情况下实现Remoting

上文我们提到,为了实现对HelloGenerator.dll的分布式调用,我们不得不修改了原有程序的多处代码。那么有没有可能在不动任何原有代码的情况下,单纯靠添加组件、修改配置文件实现远程访问呢?当然可以。这次我们还是使用Spring.net完成这个工作。 经过调整后的系统组件构成如下图所示:

该方案没有修改“src\Step3”中的任何代码,仅仅通过修改配置文件和添加了若干个组件就实现了远程访问。修改方案如下:

(1)使用Proxy模式代理原有HelloGenerator

如果要让某个对象具有分布式的功能,必须使其继承自MarshalByRefObject。但是由于不能修改任何原有代码,所以这次我们只能绕道而行, 借助Proxy模式代理原有的HelloGenerator。在RemotingServer项目中,我们定义了一个新类 HelloGeneratorProxy继承自MarshalByRefObject,通过委派的方式对原有的HelloGenerator进行调用,代码如下:

using System;
namespace IocInCSharp
{
   public class HelloGeneratorProxy : MarshalByRefObject, IHelloGenerator
   {
      private IHelloGenerator _helloGen;
      public IHelloGenerator HelloGenerator
      {
         get { return _helloGen; }
         set { _helloGen = value; }
      }
      public string GetHelloString(string name)
      {
         if(_helloGen != null)
            return _helloGen.GetHelloString(name);
         return null;
      }
   }
}

仔细观察,我们会发现HelloGeneratorProxy持有一个对IHelloGenerator的引用,该属性是可以Set的,因此我们可以借助Ioc的威力,通过调整Sping.net的配置文件动态决定远程服务器究竟发布EnHelloGenerator还是 CnHelloGenerator。

(2)发布HelloGeneratorProxy

通过RemotingServer.exe,我们将HelloGeneratorProxy发布出去,客户端实际上调用的是Proxy对象(不用担心,由于“针对接口编程”,客户端只知道它是IHelloGenerator类型对象)。服务器端代码如下:

using System;
using System.Configuration;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
using Spring.Context;
namespace IocInCSharp
{
   public class Server
   {
      public static void Main()
      {
         int port = Convert.ToInt32(ConfigurationSettings.AppSettings["LocalServerPort"]);
         try
         {
            BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
            BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
            IDictionary props = new Hashtable();
            props["port"] = port;
            props["timeout"] = 2000;
            HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider);
            ChannelServices.RegisterChannel(channel);
            IApplicationContext ctx = ConfigurationSettings.GetConfig("spring/context") as IApplicationContext;
            HelloGeneratorProxy proxy = (HelloGeneratorProxy)ctx.GetObject("myHelloGeneratorProxy");
            RemotingServices.Marshal(proxy, "HelloGenerator.soap");
            Console.WriteLine("Server started!\r\nPress ENTER key to stop the server...");
            Console.ReadLine();
         }
         catch
         {
            Console.WriteLine("Server Start Error!");
         }
      }
   }
}

注意其中的几条命令:

IApplicationContext ctx = ConfigurationSettings.GetConfig("spring/context") as IApplicationContext;
HelloGeneratorProxy proxy = (HelloGeneratorProxy)ctx.GetObject("myHelloGeneratorProxy");
RemotingServices.Marshal(proxy, "HelloGenerator.soap");

我们使用Ioc向HelloGeneratorProxy注入具体的HelloGenerator对象,并通过 RemotingServices.Marshal(proxy, "HelloGenerator.soap")命令将该实例发布出去。服务器端的配置文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <sectionGroup name="spring">
         <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
         <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
      </sectionGroup>
   </configSections>
   <spring>
      <context>
         <resource uri="config://spring/objects" />
      </context>
      <objects xmlns="http://www.springframework.net">
         <object id="myHelloGeneratorProxy" type="IocInCSharp.HelloGeneratorProxy, RemotingServer">
            <property name="HelloGenerator">
               <ref object="myCnHelloGenerator" />
            </property>
         </object>
         <object id="myEnHelloGenerator" type="IocInCSharp.EnHelloGenerator, HelloGenerator" />
         <object id="myCnHelloGenerator" type="IocInCSharp.CnHelloGenerator, HelloGenerator" />
      </objects>
   </spring>
   <appSettings>
      <add key="LocalServerPort" value="8100" />
   </appSettings>
</configuration>

用户可以尝试将配置文件中<ref object="myCnHelloGenerator" />更改为<ref object="myEnHelloGenerator" />,重新启动服务后看看客户端调用结果是什么?

(3)客户端实现技术-1

客户端实现起来要麻烦一些。由于不允许修改MainApp中的任何代码,我们必须能够在合适的时机拦截代码运行并创建远程连接,同时确保在Ioc注入时注入的是远程对象。所有这些工作Sping.net都考虑的很周到。它提供了depends-on属性,允许在执行某一操作前强制执行某段代码。在客户端的配置文件中,我们可以看到如下的配置选项:

         <object id="mySayHello" type="Spring.Aop.Framework.ProxyFactoryObject" depends-on="force-init">
         .........
         <object id="force-init" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core">
            <property name="TargetType" value="IocInCSharp.ForceInit, ForceInit" />
            <property name="TargetMethod" value="Init" />
         </object>

这表示,当我们初始化mySayHello时,要先去调用ForceInit.dll文件中ForceInit类的Init方法。 ForceInit是一个新编写的类,其主要目的就是创建并注册一个用于远程通讯的Channel。代码实现如下:

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
namespace IocInCSharp
{
   public class ForceInit
   {
      public static void Init()
      {
         //建立连接
         BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
         BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
         serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
         IDictionary props = new Hashtable();
         props["port"] = 8199;
         props["name"] = "myHttp";
         HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider);
         //获得当前已注册的通道;
         IChannel[] channels = ChannelServices.RegisteredChannels;
         //关闭指定名为MyHttp的通道;
         foreach (IChannel eachChannel in channels)
            if (eachChannel.ChannelName == "myHttp")
               ChannelServices.UnregisterChannel(eachChannel);
         ChannelServices.RegisterChannel(channel);
      }
   }
}

(4)客户端实现技术-2

剩下的工作就是为mySayHello的HelloGenerator注入远程对象。通常情况下我们需要使用 Activator.GetObject方法调用远程对象,不过Spring.net已经将其封装起来,我们只需修改一下配置文件,就可以确保调用到远程对象。配置文件对应部分如下:

         <object id="mySayHello" type="Spring.Aop.Framework.ProxyFactoryObject" depends-on="force-init">
            <property name="target">
               <object id="myLocalSayHello" type="IocInCSharp.SayHello, SayHello">
                  <property name="HelloGenerator">
                     <ref object="myHelloGenerator" />
                  </property>
               </object>
            </property>
            ......
         </object>
         <object name="myHelloGenerator" type="Spring.Remoting.SaoFactoryObject, Spring.Services">
            <property name="ServiceInterface">
               <value>IocInCSharp.IHelloGenerator, ICommon</value>
            </property>
            <property name="ServiceUrl">
               <value>http://127.0.0.1:8100/HelloGenerator.soap</value>
            </property>
         </object>

借助Spring.Remoting.SaoFactoryObject,我们轻松实现了远程对象访问,不必书写一行代码。(目前SAO在 Spring.net的实现尚不完整,按照Spring.net帮助手册上的做法,通过配置文件只能实现客户端访问远程对象,还做不到服务器端发布远程对象)

(5)使用AOP拦截调用

Sping.net目前已经实现AOP功能,我们可以很容易的对方法进行拦截和调用。需要做的工作就是设计相应的Interceptor,然后修改配置文件。目前Sping.net使用的AOP功能是AopAlliance的实现,因此代码编写时命名空间引用让人感觉多少有些别扭,不是以Sping 开头。我编写的MethodInterceptor代码如下:

using System;
using AopAlliance.Intercept;
namespace IocInCSharp
{
   class MethodInterceptor : IMethodInterceptor
   {
      public object Invoke(IMethodInvocation invocation)
      {
         Console.WriteLine("Before Method Call...");
         object returnValue = invocation.Proceed();
         Console.WriteLine("After Method Call...");
         return returnValue;
      }
   }
}

在方法调用前打印"Before Method Call...",在方法调用后打印"After Method Call..."。剩下的工作就是修改配置文件,将其应用到相应的方法上。配置文件片断如下:

         <object id="MethodAdvice" type="Spring.Aop.Support.RegexpMethodPointcutAdvisor">
            <property name="pattern" value="SayHelloTo" />
            <property name="advice">
               <object type="IocInCSharp.MethodInterceptor, MethodInterceptor" />
            </property>
         </object>
         <object id="mySayHello" type="Spring.Aop.Framework.ProxyFactoryObject" depends-on="force-init">
         
                  ......                  
                  
            <property name="interceptorNames">
               <list>
                  <value>MethodAdvice</value>
               </list>
            </property>
         </object>

通过以上操作,我们在没有修改任何原有代码的情况下,让原有系统实现了远程分布式访问。

请大家访问示例代码的“bin\Step5"目录,下面有3个子目录:Server、Client、WithoutRemoting。首先运行 Server目录下的RemotingServer.exe,然后运行Client目录下的MainApp.exe进行远程调用。系统通过 Remoting完成远程调用。关闭所有程序后,进入到WithoutRemoting目录,里面有个Readme.txt文件,按照操作步骤将文件:

..\Server\HelloGenerator.dll
..\Client\MainApp.exe
..\Client\ICommon.dll
..\Client\SayHello.dll
..\Client\Spring.Core.dll
..\Client\log4net.dll
 

拷贝到该目录,再次运行MainApp.exe,你会发现它是一个地地道道的本地应用程序!本地与远程唯一的区别就是配置文件的不同以及增加了几个其它的DLL。这正式我们这个示例的价值体现。

到此为止,我们完成了对Ioc应用的一系列模拟。Ioc写得多一些,AOP写得少了点。欢迎大家批评指正。

posted @ 2010-04-29 08:11 kevin.nrabbit 阅读(18) 评论(0) 编辑

五、使用Remoting对原有系统进行改造

如果使用Remoting技术对HelloGenerator进行改造,使其具有分布式远程访问能力,那么在不使用Ioc技术的情况下,我们将会作出如下调整:

(1)让HelloGenerator继承自MarshalByRefObject类

如果要让某个对象具有分布式的功能,必须使其继承自MarshalByRefObject,这样才可以具有远程访问的能力。因此我们需要调整 EnHelloGenerator和CnHelloGenerator的代码。这里以EnHelloGenerator为例:

using System;
namespace IocInCSharp
{
   public class EnHelloGenerator : MarshalByRefObject, IHelloGenerator
   {
      public string GetHelloString(string name)
      {
         return String.Format("Hello, {0}", name);
      }
   }
}

(2)将修改后的HelloGenerator发布出去

在这一步中,我们创建了一个新的Console应用程序RemotingServer,并在其中注册了一个Channel,发布服务并进行监听。代码如下:

using System;
using System.Configuration;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
namespace IocInCSharp
{
   public class Server
   {
      public static void Main()
      {
         int port = Convert.ToInt32(ConfigurationSettings.AppSettings["LocalServerPort"]);
         try
         {
            BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
            BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
            IDictionary props = new Hashtable();
            props["port"] = port;
            props["timeout"] = 2000;
            HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider);
            ChannelServices.RegisterChannel(channel);
            RemotingConfiguration.RegisterWellKnownServiceType(
               typeof(EnHelloGenerator),
               "HelloGenerator.soap",
               WellKnownObjectMode.Singleton);
            Console.WriteLine("Server started!\r\nPress ENTER key to stop the server...");
            Console.ReadLine();
         }
         catch
         {
            Console.WriteLine("Server Start Error!");
         }
      }
   }
}

(3)全新的客户端调用代码

为了使得客户端MainApp能够调用到远程的对象,我们需要修改它的代码,注册相应的Channel并进行远程访问。修改后的MainApp代码如下:

using System;
using System.Configuration;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
namespace IocInCSharp
{
   public class MainApp
   {
      public static void Main()
      {
         //建立连接
         BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
         BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
         serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
         IDictionary props = new Hashtable();
         props["port"] = System.Convert.ToInt32(ConfigurationSettings.AppSettings["ClientPort"]);
         HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider);
         ChannelServices.RegisterChannel(channel );

         //创建远程对象
         ISayHello sayHello = new SayHello();
         string RemoteServerUrl = ConfigurationSettings.AppSettings["RemoteServerUrl"];
         sayHello.HelloGenerator = (IHelloGenerator)Activator.GetObject(typeof(IHelloGenerator), RemoteServerUrl);
         sayHello.SayHelloTo("zhenyulu");
      }
   }
}

在这段代码中,远程对象的创建是通过 (IHelloGenerator)Activator.GetObject(typeof(IHelloGenerator), RemoteServerUrl)实现的。到此为止,我们就完成了对原有系统的Remoting改造。

经过调整后的系统,其组件间相互依赖关系如下图所示:

注意ICommon.dll文件在Client和Server端都有。

在整个调整过程中,我们修改了Server端的EnHelloGenerator以及CnHelloGenerator的代码,Client端的 MainApp也 作了修改,以加入了远程访问机制。那么能不能对原有代码不作任何修改就实现远程访问机制呢?当然可以!不过我们还要请出Sping.net帮助我们实现这一切。

(待续)

posted @ 2010-04-29 08:10 kevin.nrabbit 阅读(16) 评论(0) 编辑

四、使用Spring.net实现依赖注入

Spring在Java界可是响当当的名字,现在也有.net平台下的Spring框架了,那就是Spring.net。用户可以从http://www.springframework.net/下载到Spring.net的最新版本。本例子中使用的版本为“Spring Interim Build August 15, 2005 ”,并对Spring.Services组件中的Remoting部分做了微小调整,删除了代码中用于输出的部分命令。

Spring.net为我们提供了一种基于配置文件的注入方式,目前Spring.net允许将值注入到属性,也允许将一个工厂绑定到属性,工厂的产品将注入属性;除此之外,Spring.net还允许将一个方法的返回结果绑定到属性;它还可以在绑定之前强制进行初始化。另外Spring.net还专门针对.net提供了Remoting以及Windows Service的“注入”方式。Spring.AOP允许完成横切(不过目前是调用的是AopAlliance的代码)。

尽管我对基于配置文件的注入方式仍然有些偏见(我认为它很难Debug、难于理解、没有编译时错误校验、编写效率比较低。另外它还存在安全隐患 ,恶意用户可以借助修改配置文件将恶意代码注入系统。因此,Spring.net在Web开发中应当更具优势),但这并不能掩盖Spring.net的光芒(据说Castle比Spring.net要好,但目前我还没有尝试过使用Castle)。使用Spring.net,我们只需修改两三行代码,并提供相应配置文件,就可以轻松实现Ioc。应用Spring.net后,我们的系统依赖关系如下图所示:


 

从图中可以看出,MainApp、SayHello、HelloGenerator之间并不存在任何依赖关系,它们都依赖于抽象出来的接口文件。除此之外,MainApp还依赖于Spring.net,这使得MainApp可以借助Spring.net实现组件动态创建和组装。

对于原有代码,我们几乎不用作任何调整,唯一需要修改的就是MainApp中的调用方法,代码如下:

using System;
using System.Configuration;
using Spring.Context;
namespace IocInCSharp
{
   public class MainApp
   {
      public static void Main()
      {
         try
         {
            IApplicationContext ctx = ConfigurationSettings.GetConfig("spring/context") as IApplicationContext;
            ISayHello sayHello = (ISayHello)ctx.GetObject("mySayHello");
            sayHello.SayHelloTo("zhenyulu");
         }
         catch (Exception e)
         {
            Console.WriteLine(e);
         }
      }
   }
}

首先我们要添加对Spring.Context命名空间的引用,然后解析配置文件的"spring/context"结点,得到一 IApplicationContext对象(就好比在上一个例子中我们得到的ConfigInfo对象一样),剩下的事情就是向该Context索要相关的对象了(ISayHello)ctx.GetObject("mySayHello"),其中"mySayHello"由配置文件指定生成方式和注入方式。

配置文件的内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <sectionGroup name="spring">
         <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
         <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
      </sectionGroup>
   </configSections>
   <spring>
      <context>
         <resource uri="config://spring/objects" />
      </context>
      <objects xmlns="http://www.springframework.net">
         <object id="mySayHello" type="IocInCSharp.SayHello, SayHello">
            <property name="HelloGenerator">
               <ref object="myCnHelloGenerator" />
            </property>
         </object>
         <object id="myEnHelloGenerator" type="IocInCSharp.EnHelloGenerator, HelloGenerator" />
         <object id="myCnHelloGenerator" type="IocInCSharp.CnHelloGenerator, HelloGenerator" />
      </objects>
   </spring>
</configuration>

注意观察<object id="mySayHello".....>结点,就是由这里定义对象间相互依赖关系的。其中的<property name="HelloGenerator">结点定义了对什么属性执行注入,以及注入的内容是什么(<ref object="myCnHelloGenerator" />)。大家可以尝试将<ref object="myCnHelloGenerator" />改为<ref object="myEnHelloGenerator" />,看一看程序执行结果有什么变化来体会Spring.net的Ioc功能。

如果读者读到这里仍然觉得Ioc没有什么的话,那让我们再来看一个更为复杂的例子。在当前例子中,MainApp通过依赖注入调用了 HelloGenerator的功能,但所有的调用都发生在本地。当前程序是一个地地道道的本地应用程序。现在如果要求在不更改一行代码的情况下,将 HelloGenerator.dll放到另外一台计算机上,MainApp通过远程调用(Remoting)来访问HelloGenerator的功能。这似乎就有一定的难度了。

这么作并不是没有任何依据,其实Ioc除了可以实现依赖注入外,我们还应当看到它可以将我们从复杂的物理架构中解脱出来,专心于业务代码的开发。系统开发中关键是逻辑分层。在一个系统不需要Remoting时,开发的系统就是本地应用程序;当需要Remoting时,不用修改任何代码就可以将系统移植为分布式系统。Ioc使这一切成为可能。Rod Johnson在他的《J2EE without EJB》一书中有着详细的论述,很值得一读。据说该书的中文译本今年九月份出版(呵呵,就是这个月,不过我还没有看到市面上有卖的)。

为了能够更深入的分析在“Remoting”改造过程中我们可能遇到的麻烦,在后续两部分内容中,我们将分别介绍不使用Ioc的Remoting改造以及使用Ioc的改造,并比较两者之间的区别。

(待续)

posted @ 2010-04-29 08:09 kevin.nrabbit 阅读(14) 评论(0) 编辑

三、基于配置文件和Reflection的工厂模式

为了消除MainApp对其它组件的依赖性,我们引入工厂模式,并且根据配置文件指定的装配规程,利用.net提供的反射技术完成对象的组装工作。本部分代码仅仅提供一种功能演示,如果实际应用仍需进一步完善(建议使用一些成型的Ioc框架,例如Spring.net或Castle等)。经过改造后的系统,组件间依赖关系如下图:

可以看出这次实现了真正的“针对接口编程”。所有的组件只依赖于接口。MainApp所需的对象是由工厂根据配置文件动态创建并组装起来的。当系统需求发生变化时,只需要修改一下配置文件就可以了。而且MainApp、SayHello和HelloGenerator之间不存在任何的依赖关系,实现了松耦合。

这是如何实现的呢?我们首先要能够解析配置文件中的信息,然后建立包含相关信息的对象。最后根据这些信息利用反射机制完成对象的创建。首先我们看一下配置文件所包含的内容:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <sectionGroup name="IocInCSharp">
         <section name="objects" type="IocInCSharp.ConfigHandler, MainApp" />
      </sectionGroup>
   </configSections>
   <IocInCSharp>
      <objects>
         <object name="SayHello" assembly="SayHello.dll" typeName="IocInCSharp.SayHello">
            <property name="HelloGenerator" assembly="HelloGenerator.dll" 
                      typeName="IocInCSharp.CnHelloGenerator"></property>
         </object>
      </objects>
   </IocInCSharp>
</configuration>

从中我们可以看出,我们实现了一个IocInCSharp.ConfigHandler类,用来处理配置文件中 IocInCSharp\objects结点中的内容。ConfigHandler类将根据该结点下的内容处理并创建一ConfigInfo对象(关于 ConfigInfo、ObjectInfo以及PropertyInfo的代码可自行查看源代码,这里就不再赘述)。ConfigHandler类的代码实现如下:

using System;
using System.Configuration;
using System.Xml;
namespace IocInCSharp
{
   public class ConfigHandler:IConfigurationSectionHandler
   {
      public object Create(object parent, object configContext, System.Xml.XmlNode section)
      {
         ObjectInfo info;
         PropertyInfo propInfo;
         ConfigInfo cfgInfo = new ConfigInfo();
         foreach(XmlNode node in section.ChildNodes)
         {
            info = new ObjectInfo();
            info.name = node.Attributes["name"].Value;
            info.assemblyName = node.Attributes["assembly"].Value;
            info.typeName = node.Attributes["typeName"].Value;
            foreach(XmlNode prop in node)
            {
               propInfo = new PropertyInfo();
               propInfo.propertyName = prop.Attributes["name"].Value;
               propInfo.assemblyName = prop.Attributes["assembly"].Value;
               propInfo.typeName = prop.Attributes["typeName"].Value;
               info.properties.Add(propInfo);
            }
            cfgInfo.Objects.Add(info);
         }
         return cfgInfo;
      }
   }
}

通过ConfigHandler的解析,我们最终得到一个ConfigInfo实例,Factory就是根据这个实例中所包含的配置信息,利用反射技术对所需对象生成并组装的。SayHelloFactory的代码如下:

using System;
using System.IO;
using System.Configuration;
using System.Reflection;
namespace IocInCSharp
{
   public class SayHelloFactory
   {
      public static object Create(string name)
      {
         Assembly assembly;
         object o = null;
         object p;
         string rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + 
                           Path.DirectorySeparatorChar;
         ConfigInfo cfgInfo = (ConfigInfo)ConfigurationSettings.GetConfig("IocInCSharp/objects"); 
         ObjectInfo info = cfgInfo.FindByName(name);
         if(info != null)
         {
            assembly = Assembly.LoadFile(rootPath + info.assemblyName);
            o = assembly.CreateInstance(info.typeName);
            Type t = o.GetType();
            for(int i=0; i<info.properties.Count; i++)
            {               
               PropertyInfo prop = (PropertyInfo)info.properties[i];
               
               assembly = Assembly.LoadFile(rootPath + prop.assemblyName);
               p = assembly.CreateInstance(prop.typeName);
               t.InvokeMember(prop.propertyName, 
                  BindingFlags.DeclaredOnly | 
                  BindingFlags.Public | BindingFlags.NonPublic | 
                  BindingFlags.Instance | BindingFlags.SetProperty, null, o, new Object[] {p});
            }
         }
         return o;
      }
   }
}

在上面这段代码中,重点注意三条命令的使用方法:

assembly = Assembly.LoadFile(rootPath + prop.assemblyName);
p = assembly.CreateInstance(prop.typeName);
t.InvokeMember(prop.propertyName, 
   BindingFlags.DeclaredOnly | 
   BindingFlags.Public | BindingFlags.NonPublic | 
   BindingFlags.Instance | BindingFlags.SetProperty, null, o, new Object[] {p});

Assembly.LoadFile()用于将外部文件装载进来;assembly.CreateInstance()根据装载进来的程序集创建一指定类型的对象;t.InvokeMember(prop.propertyName, ........BindingFlags.SetProperty, null, o, new Object[] {p})利用反射机制对创建出来的对象设置属性值。

我们的Factory就是利用这种方式根据配置文件动态加载程序集,动态创建对象并设置属性的。有了这个Factory,MainApp中的内容就很简单了:

using System;
namespace IocInCSharp
{
   public class MainApp
   {
      public static void Main()
      {
         ISayHello sayHello = (ISayHello)SayHelloFactory.Create("SayHello");
         if(sayHello != null)
            sayHello.SayHelloTo("zhenyulu");
         else
            Console.WriteLine("Got an Error!");
      }
   }
}

现在,MainApp只依赖于接口,不再依赖于其它组件,实现了松耦合。在本例子中,大家可以尝试将配置文件中的IocInCSharp.CnHelloGenerator更改为IocInCSharp.EnHelloGenerator,看看是否输出内容由中文变为了英文。这便是“注入”的效果。

从上面这个例子我们可以看出,通过自定义配置文件和.net中的Reflection技术,我们自己就可以开发Ioc应用,根据配置文件的信息自行组装相应的对象。但是Reflection编程的技术门槛还是比较高的,并且在实际应用中配置文件的格式、Handler的设计都不是象上面代码那样的简单。不过幸好我们现在有很多的Ioc容器可供选择,它们都提供了完整的依赖注入方式,并且比自己写代码更加成熟、更加稳定。使用这些框架可以让程序员在三两行代码里完成“注入”工作。在我们下一个案例中,我们将使用Spring.net实现依赖注入。我们会发现仅仅添加几行代码并更改一下配置文件就可轻松实现依赖注入。(待续)

posted @ 2010-04-29 08:08 kevin.nrabbit 阅读(15) 评论(0) 编辑


 本系列的全部源代码及二进制文件可以从这里下载:IocInCSharp.rar

     你真的了解Ioc与AOP吗?我现在还不是很了解,而且越学习越发现自己了解的很少,Ioc与AOP中蕴涵了大量的能量等待我们去开发。在这个系列中,我仅仅利用Sping.net这个框架向大家展示一下Ioc与AOP的强大功能(呵呵,其实写这段话的目的是因为“文章题目”牛皮吹得有点大了,给自己个台阶下罢了)。

在这个系列中一共包含6个案例,从简单到复杂,也是对问题分解、思考和解决的一个过程,它们分别是:(1)类之间的依赖;(2)接口依赖;(3)基于配置文件和Reflection的工厂模式;(4)使用Spring.net实现Ioc;(5)Romoting;(6)利用Ioc在不动一行代码的情况下实现Remoting。为了更好的理解文中的内容,最好顺序阅读。

     作为一个应用系统,代码复用至关重要。如果在你的设计中,类与类存在很强的相互关联,那么你会发现在重用这些组件时就存在很严重的问题。在Step1到Step3-Reflection的例子中,我们试图 利用“针对接口编程”以及自己设计的Ioc对系统进行解耦。在Step3到Step5的例子中,我们将利用Spring.net提供的Ioc框架,轻松完成解耦以及系统改造等工作。

一、类之间的依赖

我们的第一个例子主要用于说明程序的基本构造,并且作为一个反面典型,引出为什么要解耦,以及如何下手。在这个例子中,我们将创建三个程序集,分别是MainApp.exe、HelloGenerator.dll以及SayHello.dll。它们之间的关系如下图所示:

HelloGenerator类根据提供的姓名产生一个问候字符串,代码如下:

using System;
namespace IocInCSharp
{
   public class EnHelloGenerator
   {
      public string GetHelloString(string name)
      {
         return String.Format("Hello, {0}", name);
      }
   }
}

SayHello类持有一个对EnHelloGenerator的引用,并负责将生成出来的问候字符串打印出来。

using System;
namespace IocInCSharp
{
   public class SayHello
   {
      private EnHelloGenerator _helloGen;
      public EnHelloGenerator HelloGenerator
      {
         get { return _helloGen; }
         set { _helloGen = value; }
      }
      public void SayHelloTo(string name)
      {
         if(_helloGen != null)
            Console.WriteLine(_helloGen.GetHelloString(name));
         else
            Console.WriteLine("Client.hello is not initialized");
      }
   }
}

MainApp.exe负责完成对象的创建、组装以及调用工作:

using System;
namespace IocInCSharp
{
   public class MainApp
   {
      public static void Main()
      {
         SayHello sayHello = new SayHello();
         sayHello.HelloGenerator = new EnHelloGenerator();
         sayHello.SayHelloTo("zhenyulu");
      }
   }
}

在这个设计中,组件与组件之间、类与类之间存在着紧密的耦合关系。SayHello类中的_helloGen字段类型为EnHelloGenerator,这将导致我们很难给它赋予一个其它的HelloGenerator(例如CnHelloGenerator,用于生成中文问候语)。另外MainApp也严重依赖于SayHello.dll以及HelloGenerator.dll,在程序中我们可以看到类似new SayHello();new EnHelloGenerator();的命令。

这种紧密的耦合关系导致组件的复用性降低。试想,如果想复用SayHello组件,那么我们不得不连同HelloGenerator一同拷贝过去,因为SayHello.dll是依赖与HelloGenerator.dll的。解决这个问题的办法就是“针对抽象(接口)”编程 (依赖倒置原则)。这里的抽象既包括抽象类也包括接口。我不想过多的去谈抽象类和接口的区别,在后续的例子中我们将使用接口。由于接口在进行“动态代理”时仍能保持类型信息,而抽象类可能由于代理的原因导致继承关系的“截断”(如MixIn等)。除此之外,对于单继承的C#语言而言,使用接口可以拥有更大的弹性。

二、接口依赖

既然类之间的依赖导致耦合过于紧密,按照《设计模式》的理论,我们要依赖于接口。但是人们往往发现,仅仅依赖于接口似乎并不能完全解决问题。我们从上面的例子中抽象出接口后,组件间的依赖关系可能变成如下图所示:

经过改造后,SayHello不再依赖于具体的HelloGenerator,而是依赖于IHelloGenerator接口,如此一来,我们可以动态的将EnHelloGenerator或是CnHelloGenerator赋给SayHello,其打印行为也随之发生改变。接口的定义以及改造后的SayHello代码如下(为了节省空间,将代码合并书写):

using System;
namespace IocInCSharp
{
   public interface IHelloGenerator
   {
      string GetHelloString(string name);
   }
   public interface ISayHello
   {
      IHelloGenerator HelloGenerator{ get; set; }
      void SayHelloTo(string name);
   }
   public class SayHello : ISayHello
   {
      private IHelloGenerator _helloGen;
      public IHelloGenerator HelloGenerator
      {
         get { return _helloGen; }
         set { _helloGen = value; }
      }
      public void SayHelloTo(string name)
      {
         if(_helloGen != null)
            Console.WriteLine(_helloGen.GetHelloString(name));
         else
            Console.WriteLine("Client.hello is not initialized");
      }
   }
}

但是我们的MainApp似乎并没有从接口抽象中得到什么好处,从图中看,MainApp居然依赖于三个组件:ICommon.dll、HelloGenerator.dll以及SayHello.dll。这是由于MainApp在这里负责整体的“装配”工作。如果这三个组件中的任何一个发生变化,都将导致MainApp.exe的重新编译和部署。从这个角度来看,似乎“针对接口编程”并没有为我们带来太多的好处。

如果能够将“组件装配”工作抽象出来,我们就可以将MainApp的复杂依赖关系加以简化,从而 进一步实现解耦。为此,我们引入“工厂”模式,并利用配置文件和反射技术,动态加载和装配相关组件。(待续)

 

posted @ 2010-04-29 08:07 kevin.nrabbit 阅读(16) 评论(0) 编辑

仅列出标题  下一页