First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 2984 评论 :: 339 引用

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

你真的了解Ioc与AOP吗?(1)

你真的了解Ioc与AOP吗?(2)

你真的了解Ioc与AOP吗?(3)

你真的了解Ioc与AOP吗?(4)

你真的了解Ioc与AOP吗?(5)


本部分示例代码请参考"src\Step5"目录

六、利用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 on 2005-09-10 20:11 吕震宇 阅读(4095) 评论(23)  编辑 收藏 所属分类: 面向对象技术

评论

#1楼  2005-09-13 00:55 古月春秋      
全部看完了。偶对IoC并不是非常的了解,但是感觉这种方式是把耦合的位置从应用程序的代码中转移到了配置文件中去了,同时MainApp仍然要依赖一个接口(这是吹毛求疵了)。不过不管怎么说,能够不修改代码实现功能的替换至少在发布方面是能够带来不错的效果。至于能够带来多大程度上的好处,我想可能还是要视环境和应用的场合来定吧。
不知以为如何?偶是保守派~ 呵呵

BTW, 文笔不错,讲的很清楚,鼓励一个~~
  回复  引用  查看    

#2楼  2005-09-13 01:04 古月春秋      
另外,刚才写完了突然有一个感想。其实在这个实现中花了很多的力气来做类型的匹配工作,这个是强类型语言所不能避免的。既然如此的话,那么无类型的动态语言实现IoC应该是非常的容易并且效果应该是不错的——可是同时负面效果就是运行期才能进行类型的识别和匹配工作容易出错,但目前似乎配置文件的方式也无法避免这个问题。
进一步的,配置中的内容是否可以以元数据的方式出现?或者以一个程序集的形式出现?该程序集其实就是配置文件或者元数据功能上的替代者——虽然修改不方便了,但是同时带来了安全上的保证。
  回复  引用  查看    

#3楼  2005-09-13 01:15 古月春秋      
再感慨一个,当下形形色色的庞大的配置文件已经成灾了,怪不得RoR要热起来。其实就配置文件来说,本质上是为了把耦合的位置从不可修改的编译代码中转移到一个可修改的文本文件中,这样就可以在替换部分功能的时候免除掉“编译”这个环节(不修改代码但还是要修改配置文件,其实是一回事),为的就是这个效果吧(不知道这样说是否正确)。
话说回来,这个趋势发展到极致,就是大家都用配置文件来写程序了——说实在的,这个也是可以实现的,XML配合一个解释器不就是一个语言么?那么这个配置文件就相当于一个解释型的语言了。然后,为了提高这种配置方式程序的编写效率——就又转回来了。说到底,这是一个平衡的问题。
  回复  引用  查看    

#4楼 [楼主] 2005-09-13 11:27 吕震宇      
同意!在前面得一个评论中我也提到将配置文件抽取成一个程序集得问题。Castle似乎更注重自动匹配,当不能自动匹配时也要配置文件加以指定。最近我正读Agile Web Development with Rails,试图从动态语言那里找到些出路,刚刚开始...
  回复  引用  查看    

#5楼  2005-09-14 00:34 yyanghhong      
XML 和 Script的最大区别是 parser, 我们可以很容易的做出基于XML <=> GUI的双向编辑工具, 但很难做出基于Script <=> GUI的工具。

比如现在有很多工具可以生成和修改hibernate配置HBM文件, 没有必要去手动修改XML了。
  回复  引用  查看    

#6楼  2005-09-14 00:41 古月春秋      
其实Script<==>GUI的工具并不是那么的难?只是Parser复杂一点而已。比如.Net的GUI设计可以直接生成构造代码,Eclipse中也同样有,Delphi的.Frm文件虽然看起来是一个类似于XML结构的“配置文件”,但是通过这个文件也很容易可以生成Form的构造语句。

同时,以XML形式出现的编程语言并不是没有,XSLT不就是一个很好的例子么——只是功能稍微局限了一点,连变量都没有,只有参数。
  回复  引用  查看    

#7楼  2005-09-14 02:11 yyanghhong      
Net的GUI设计在2.0就改为用xaml把GUI和code分离了, 原因就是维护太复杂。

Delphi的.dfm格式也是远比pascal要简单。

XSLT不能叫script, 只能叫template, 他没有流程的实现, NANT到是一个XML形式出现的script,
  回复  引用  查看    

#8楼  2005-09-14 10:40 古月春秋      
唔,这个我不是很同意。
1,据我了解,XAML的发展并不是建立在GUI和CODE的维护复杂问题的基础上的。Delphi的dfm文件,其实和XAML有异曲同工之妙,可以说XAML是XML格式的DFM。
2,XSLT里面是有流程的。它允许在模板定义中调用另外一个模板,而这个调用的模板相当于是一个函数。可以传递参数、返回值、定义if/else分支语句、for循环、各种XSLT内置函数调用等等。我想这就好像FP一样,是另外一种编程的方式。从某种程度上说,类似awk吧,属于模式-匹配这种行为方式的语言。
  回复  引用  查看    

#9楼  2005-09-14 15:22 guag [未注册用户]
他妈的,从来没见过这么清楚的文章。
  回复  引用    

#10楼  2005-09-14 15:22 guag [未注册用户]
希望可以看到关于aop更多的文章.
  回复  引用    

#11楼  2005-09-14 17:56 wayfarer      
介绍IoC确实是很清楚了,可以说是step by step。不过,在文章最后引入的AOP好像和前面要实现的目标没有什么关系啊。

我对spring.net不了解,想问一下:
在“客户端实现技术-1”处,提出了depends-on属性,他的目的是在初始化mySayHello时,要先去调用ForceInit.dll文件中ForceInit类的Init方法。这可以看作是AOP的一个应用。但我不明白后面的MethodInterceptor,有什么用处?它只是为了在执行方法前后,在Invoke()方法中插入新的逻辑;还是depend-on属性在引入ForeInit时,必须要使用到该Interceptor?

也就是说,MethodInterceptor与引入ForceInit有关吗?如果没有关系,那么这里介绍就和全文实现的目标没有任何关系啊,难道只是为了介绍AOP而介绍它?
  回复  引用  查看    

#12楼 [楼主] 2005-09-14 20:23 吕震宇      
@Wayfarer

其实我是先写的代码后写的文章。当时写代码时目标是Ioc,没有AOP。可是编到最后一个例子时想测试一下Spring.net的AOP功能,于是就多出了个“尾巴”。其实这里的AOP对于本文的例子来说应当没有什么用处。

后来在写“预告”时随手起了个题目,连AOP也给挂上了,最后弄得自己下不来台:(

其实我只是提供了个AOP实现,depend-on和AOP没有任何关系。可以采用如下手段将AOP去掉。

1、删除MethodInterceptor.dll文件。

2、将Client端的配置文件修改如下:

<?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="myHelloGenerator" />
</property>
</object>
<object name="myHelloGenerator" type="Spring.Remoting.SaoFactoryObject, Spring.Services" depends-on="force-init">
<property name="ServiceInterface">
<value>IocInCSharp.IHelloGenerator, ICommon</value>
</property>
<property name="ServiceUrl">
<value>http://127.0.0.1:8100/HelloGenerator.soap</value>
</property>
</object>
<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>
</objects>
</spring>
</configuration>

  回复  引用  查看    

#13楼 [楼主] 2005-09-14 20:27 吕震宇      
我对AOP了解还不是很深刻,所以现在也一直没敢写什么文章。日后等有了深入的认识后再写吧:)

Wayfarer处到是有几篇不错的AOP文章:)
  回复  引用  查看    

#14楼  2005-09-22 15:15 殷祥 [未注册用户]
不错,写得很不错。我刚开始对Sprint.net还不算了解。有了你这篇文章,我终算有点眉目了。:)
我觉得不管怎样变。要看具体情况而定。正在学NHibernate等。多接受点新东西(已不算什么新的了,而是我太落后了)
  回复  引用    

#15楼  2005-10-26 14:43 李天平 [未注册用户]
最近在研究学习这方面的东西,鼓励一下老兄,希望能写出更多的好文章。
  回复  引用    

#16楼  2006-08-06 22:17 main      
2005年的文章了!自己现在才开始认识起来:(
写的真棒!
  回复  引用  查看    

#17楼  2006-08-24 05:22 怪怪 [未注册用户]
AOP用接口和delegate(说白了也是一种形式的接口)就可以实现吧..? 为什么一定要借助第三方呢。Sprint这些东西,说白了几年前就开始发展的ASP.NET Forum的源代码里早就开始以Provider的形式有一个简单的实现了,.NET自己很多地方也用到类似的实现~,所以我还是不太明白用到Sprint或者其它IoC框架有啥好处...,倒是.NET自己的那些容器的接口和实现,吕老师要是能介绍一下就好了。

本来和吕老师有差距,不想指手画脚,但是我觉得Jdon那哥们是个例子,模式一大堆,概念一大堆,绝对是个真正有所理解的明白人,但东西却破的很...,吕老师现在当然不是这样,可如果过于追求形而上学的东西,未来可不一定呢。最近做个工程,应用了大量的解耦的概念,才知道既有爽的地方,也有不爽的地方,有时候用来解耦的代码比简单的功能还多。有时候就想,即使未来功能要有很大扩展,改代码,重编译,发个大个儿点儿的升级也未必不行吧,我觉得还是具体情况具体分析,能提高平均生产力的方法(当然,程序写死了,以后修改、扩展特别费力也是一种低生产力)就是正确的方法了。
  回复  引用    

花了快一个上午的时间来读这片文章,楼主写得通俗易懂,看起来如浴春风。真实获益非浅。
谢谢楼主!
  回复  引用    

#19楼  2007-03-14 09:33 wingpro [未注册用户]
不顶就憋死了~加油楼主~
  回复  引用    

#20楼  2007-06-23 08:28 坏男孩 [未注册用户]
写的好啊
  回复  引用    

#21楼  2007-12-10 14:47 zhchang [未注册用户]
经典的入门文章,说到底还是平衡的问题
  回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-09-12 08:46 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: