First we try, then we trust

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

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

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

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

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

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

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


本部分示例代码请参考"src\Step3-Reflection"目录

三、基于配置文件和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 on 2005-09-10 17:00 吕震宇 阅读(5490) 评论(23)  编辑 收藏 所属分类: 面向对象技术

评论

#1楼  2005-10-29 14:05 loveyzy [未注册用户]
吕老师,你的第三个步骤的源代码运行出错:
"Error instantiating context."
  回复  引用    

#2楼  2005-10-29 14:53 loveyzy [未注册用户]
ConfigInfo cfgInfo = (ConfigInfo)ConfigurationSettings.GetConfig("IocInCSharp/objects");
这行也出错: 配置节处理程序中的异常
  回复  引用    

#3楼 [楼主] 2005-10-29 21:15 吕震宇      
@loveyzy

很奇怪的问题,在我的机器上没有问题。建议:

1、你是运行解压缩后bin目录下Step3目录下的可执行程序,看看有没有问题?
2、如果你是在VS.NET环境下点击运行按钮的话,则一定不可以执行。因为借助反射技术,组件间消除了依赖(不需要在项目中再添加什么引用了),因此,在.net的默认编译下,不会将诸如HelloGenerator.dll之类的文件拷贝到bin的Release目录或Debug目录下,因此执行时由于找不到这些文件而报错。需要自己手工拷贝过去。

其实从这里也可以看出我们消除了“依赖”。同时,反射技术也给调试工作带来了很多的不便。这也是为什么有些公司需要使用NANT之类的工具辅助进行编译,单纯靠.net IDE是不管用的。

回头你再试试这次还报错吗?
  回复  引用  查看    

#4楼  2005-11-10 15:52 liuyu [未注册用户]
吕老师,我也运行失败。
测试了一下,是
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);
}
出错。引用node.Attributes["name"].Values时候出错
  回复  引用    

#5楼  2005-11-10 18:03 liuyu [未注册用户]
吕老师,是不是App.config有特殊的格式规定?
我修改了一下你的程序,直接用xml来读取就没有问题。
你觉得呢?
  回复  引用    

#6楼 [楼主] 2005-11-11 14:45 吕震宇      
奇怪的问题,在我的机器上运行和编译没有任何问题,我的.net 1.1没有打SP1的补丁,是不是与这个有关呢?
  回复  引用  查看    

#7楼  2005-11-28 12:44 zhoup [未注册用户]
http://hobe.cnblogs.com/archive/2005/10/19/258245.aspx
  回复  引用    

#8楼  2005-11-28 12:48 zhoup [未注册用户]
我修改好了,但不知道如何把修改好的例子上传上来?
  回复  引用    

#9楼 [楼主] 2005-11-28 14:03 吕震宇      
@zhoup

能发到我的邮箱吗?我来上传。zhenyulu@163.com
  回复  引用  查看    

#10楼  2005-11-28 15:09 zhoup [未注册用户]
To @loveyzy

MainApp项目需要包含对HelloGenerator项目和SayHello项目的引用.

因为在MainApp项目中SayHelloFactory.cs文件需要查找SayHello.dll和HelloGenerator.dll.
如果不包含对对HelloGenerator项目和SayHello项目的引用,
assembly = Assembly.LoadFile(rootPath + info.assemblyName);和
assembly = Assembly.LoadFile(rootPath + prop.assemblyName);
会发生异常. MainApp在rootPath下找不到 SayHello.dll和 HelloGenerator.dll.
  回复  引用    

#11楼  2006-04-28 14:13 sunjt [未注册用户]
在ConfigHandler类的Create方法中第一个foreach里加上以下几名,就不会出现Config文件的错误了
if ((node.NodeType == XmlNodeType.Comment) || (node.NodeType == XmlNodeType.Whitespace))
{
return true;
}
  回复  引用    

#12楼  2006-04-28 14:45 sunjt [未注册用户]
还要改另外一个地方:在ConfigHandler类的Create方法中第二个foreach里加上以下几句:

if ((prop.NodeType == XmlNodeType.Comment) || (prop.NodeType == XmlNodeType.Whitespace))
{
continue;
}

在ConfigHandler类的Create方法中第一个foreach里加上以下几名,就不会出现Config文件的错误了
if ((node.NodeType == XmlNodeType.Comment) || (node.NodeType == XmlNodeType.Whitespace))
{
continue;
}
  回复  引用    

#13楼  2006-06-20 21:36 zzz [未注册用户]
吕老师,由于我已经写了其他东西在一个项目里,所以我把所有以WindowsApplication8,替代了IocInCSharp,而且我要在窗体的按钮的事件中使用这些方法但是我发生的是以下情况
未处理的“System.Configuration.ConfigurationException”类型的异常出现在 system.dll 中。

其他信息: 无法创建 WindowsApplication8.ConfigHandler

  回复  引用    

#14楼  2007-02-15 16:09 Jeriffe [未注册用户]
今天才细看作者文章,有点相见恨晚。我运行Step3-Reflection 文件夹里的例子是也出错,后来我把 HelloGenerator.dll;SayHello.dll 2个DLL 拷贝到 主工程的References下就成功了(我用的是VS2005)。
  回复  引用    

1234567890
  回复  引用    

#16楼  2007-03-19 16:47 David [未注册用户]
ObjectInfo,ConfigInfo,PropertyInfo都在那个命名空间了啊? 我怎么引入不了啊?
  回复  引用    

#17楼 [楼主] 2007-03-20 13:41 吕震宇      
@David
那些都是代码中自己定义的类,你可以下载本系列的源代码,里面这些东西都有。
  回复  引用  查看    

#18楼  2007-03-21 10:24 David [未注册用户]
真不好意思,我打开的是Step3的程序,然后自己照着你代码写ConfigHandler.

多谢!

我还想问下,对于微软的petshop3.0 是不是也是采用的依赖注入?

我觉得你们只是采用reflect方式不同而已?对吗?
  回复  引用    

#19楼  2007-04-23 20:36 zakorne      
如果是B/S的。

string rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) +
Path.DirectorySeparatorChar;
不依赖HttpServerUtility.Server对象,这句该如何改写?


  回复  引用  查看    

能帮我看看下面的问题吗?
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Security.Permissions;

[assembly: AssemblyVersionAttribute("1.0.2000.0")]

namespace ReflectionOperation
{
public class Example
{
private int factor;
public Example(int f)
{
factor = f;
}

public int SampleMethod(int x)
{
return x * factor;
}

public void TestRefletion()
{
Assembly assem = Assembly.GetExecutingAssembly();
string strAssem = assem.FullName;
AssemblyName assemName = assem.GetName();
string strAssName = assemName.Name;
string strVerMar = assemName.Version.Major.ToString();
string strVerMin = assemName.Version.Minor.ToString();
string strCodeBase = assemName.CodeBase;
Object oExample = assem.CreateInstance("Example");
MethodInfo m = assem.GetType("Example").GetMethod("SampleMethod");
Object ret = m.Invoke(oExample,new object[] {42});
string strRet = ret.ToString();
string strEntry = assem.EntryPoint.ToString();

}

}
}

以上是我练习反射时做的练习,代码是MSDN上面的,Object oExample = assem.CreateInstance("Example");,总是创建不了实例,一直都运行失败,不知道为什么。
  回复  引用    

@salangane0512
2个错误:
1) assem.CreateInstance("Example");需要无参构造函数
2)assem.CreateInstance("ReflectionOperation.Example");和assem.GetType("ReflectionOperation.Example").类型都要带上命名空间ReflectionOperation
  回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-09-12 08:42 编辑过


相关链接: