利用IOC和AOP实现一个简单框架(完整)
实现框架之前,我想先讲一讲为什么我会写这篇文章。
暑假期间,我在一家软件公司实习。在8月15这天(周一)早上,项目小组组长对我说:“IOC和AOP在项目中经常用到,我希望你能用这两种技术实现一个简单框架”。
"IOC”、"AOP",我的天,我听都没听过,这是什么。组长说:"我一会儿会发给你一些资料和一个例子,你再查查资料,学习学习,给你两周时间"。好吧,只能这样,框架还是听过,但没做过,对于一些没做过的东西,人总是觉得很神秘、很深奥,不容易做,但当你做过之后才发现,它们只是人们用一些已有的技术手段实现的一些想法,并不太难。
IOC:(Inversion of Control) 中文意思是“控制反转” 还有一个原理相同的概念是:Dependency Injection(依赖注入)
AOP:Aspect Oriented Programming-面向方面编程,确切的说应是:面向切面编程。
上面这两个概念要想详细了解解决什么问题可以查资料,在这就不多说了。
在查资料和学习的过程中我很痛苦,为什么呢?因为我除了懂得这些原理以外,我还想找个确切的实例,但是找个确切的例子凭的是人品,一般网上说的例子都很零散,有的只给其中的一段代码,你不能看到程序运行的过程;有的只是给出了其中一方面的例子,你也不知道怎样有机的结合起来,你还是不能完全看到整个过程;有的虽然给出了整个实现代码,但还是引用了一些dll文件,你也不能看到这些dll文件中是怎样实现的。有些人说的云里雾里的,不是很懂。于是我经过两周实现一个简单框架之后就想将这个例子共享出来,让后来学习的人能容易入手。
IOC实际好处就是消除耦合或者降低耦合,减轻以后软件维护的难度,一般主要用到反射。
可以使用反射动态地创建类型的实例,将类型绑定(其实就是对象引用实例,绑定我刚开始也不懂)到现有对象,然后,可以调用实例的方法等。
首先我们先做一个通用的可以得到实例的类:Instance.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Xml;
 6 using System.Reflection;
 7 
 8 namespace MyIOC.IOC
 9 {
10     class Instance
11     {
12         public static object GetInstance(String key)
13         {
14             String className = null;
15             object obj = null;
16             XmlDocument doc = new XmlDocument();
17 
18             // 加载配置文件
19             doc.Load("App.config");
20 
21             XmlNode classNode;
22 
23             // 得到根节点
24             XmlNode root = doc.DocumentElement;
25             
26             classNode = root.SelectSingleNode("descendant::class[key='" + key + "']");
27 
28             //得到子节点集
29             XmlNodeList list = classNode.ChildNodes;
30 
31             // 得到类名
32             foreach(XmlNode node in list)
33             {
34                 // 得到节点名为“class-name”的节点
35                 if (node.Name.Equals("class-name"))
36                 {
37                     className = node.InnerText;
38                 }     
39             }
40 
41             if (className != null)
42             {
43                 // 得到UserPerssion的类型
44                 Type t = Type.GetType(className);
45 
46                 // 得到UserPermission的实例
47                 obj = Activator.CreateInstance(t);
48             }
49             
50             return obj;
51         }
52     }
53 }
配置文件主要写反射所需要的类的信息
App.config配置文件内容如下:
View Code 
 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration xmlns:bk="urn:samples">
 3   
 4   <class>
 5     <key>IUserPermission</key>
 6     <class-name>MyIOC.IOC.UserPermission</class-name>
 7   </class>
 8   
 9   <class>
10     <key>IData</key>
11     <class-name>MyIOC.Data</class-name>
12   </class>
13   
14 </configuration>
为了方便演示,我们反射项目中的一个类,你可以看到和上面配置文件的关系,实际应用中一般是引用外部dll文件,一般这些dll是处理日志记录,性能统计,安全控制,事务处理,异常处理这些与业务逻辑无关的东西,比如我们要验证一个人的权限,我们就不需要满世界的写验证权限的代码。下面给出的就是反射时需要验证权限的接口与实现类
IUserPermission.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 
 6 namespace MyIOC.IOC
 7 {
 8     interface IUserPermission
 9     {
10         void HasPermission(String name);
11     }
12 }
UserPermission.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Windows.Forms;
 6 
 7 namespace MyIOC.IOC
 8 {
 9     class UserPermission : IUserPermission 
10     {
11         private String name = "user";
12 
13         #region IUserPermission Members
14 
15         public void HasPermission(string name)
16         {
17             if (this.name.Equals(name))
18             {
19                 Console.WriteLine("you have the permission");
20             }
21             else
22             {
23                 Console.WriteLine("you haven't the permission");
24             }
25         }
26 
27         #endregion
28     }
29 }
其实使用接口也是方便日后维护,如果以后我们不用UserPermission这样验证权限,我们就可以在定义一个类UserPermission2去实现接口IUserPermission,在配置文件里用UserPermission2替换UserPermission即可,如果你不用接口,你就要修改每一处使用UserPermission的地方,这样不是很麻烦。
顺便说一下,这个验证权限与具体的业务逻辑无关,这里也就体现了面向切面编程的思想,假如我们要写数据,我们就要自动验证权限,看有没有写数据的权限,所以程序写到这我们还不能实现自动验证权限,下一步我们就要用拦截器进行拦截,关于拦截器的的原理和使用我建议你看这篇博文
首先定义一个接收器
InterceptorContext.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Runtime.Remoting.Messaging;
 6 using MyIOC.IOC;
 7 
 8 namespace AOP
 9 {
10    
11     public class InterceptorContext : IMessageSink //实现IMessageSink
12     {
13         private IMessageSink nextSink;  //保存下一个接收器
14 
15         //在构造器中初始化下一个接收器
16         public InterceptorContext(IMessageSink next)
17         {
18             nextSink = next;
19         }
20 
21         //必须实现的IMessageSink接口属性
22         public IMessageSink NextSink
23         {
24             get
25             {
26                 return nextSink;
27             }
28         }
29 
30         //实现IMessageSink的接口方法,当消息传递的时候,该方法被调用
31         public IMessage SyncProcessMessage(IMessage msg)
32         {
33             //拦截消息,做前处理
34             beforeProcess(msg);
35 
36             //传递消息给下一个接收器
37             IMessage retMsg = nextSink.SyncProcessMessage(msg);
38 
39             //调用返回时进行拦截,并进行后处理
40             afterProcess(msg, retMsg);
41             return retMsg;
42         }
43 
44         //IMessageSink接口方法,用于异步处理,我们不实现异步处理,所以简单返回null,
45         //不管是同步还是异步,这个方法都需要定义
46         public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
47         {
48             return null;
49         }
50 
51         private void beforeProcess(IMessage msg)
52         {
53             //检查是否是方法调用,我们只拦截Data的WriteData方法。
54             IMethodCallMessage callMes = msg as IMethodCallMessage;
55 
56             if (callMes == null)
57                 return;
58 
59             if (callMes.MethodName == "WriteData")
60             {
61                 IUserPermission userPermission = (IUserPermission)Instance.GetInstance("IUserPermission");
62                 userPermission.HasPermission("user"); 
63                 
64             }
65         }
66 
67         // 写入日志文件
68         private void afterProcess(IMessage msg, IMessage retMsg)
69         {
70             IMethodCallMessage callMes = msg as IMethodCallMessage;
71 
72             if (callMes == null)
73                 return;
74             Console.WriteLine("Log the operation");
75         }
76     }  
77 }
上面61行就是使用反射得到一个UserPermission的实例。但是在这块没有扩展性,我们只能拦截Data的WriteData()的方法,所以要提高扩展性,我们还是要配置我们的App.config文件,利用反射来确定我们拦截哪些方法,你可以试着实现。上面的写入日志文件也要使用反射来实现,为了演示,我们就这样简单处理了。你可以将它重写,so easy 啦!
下来我们定义上下文环境的属性,至于为什么要定义这个,请再看上面推荐的博文
InterceptorProperty.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Runtime.Remoting.Contexts;
 6 using System.Runtime.Remoting.Messaging;
 7 
 8 namespace AOP
 9 {
10     class InterceptorProperty : IContextProperty, IContributeObjectSink
11     {
12         public InterceptorProperty()
13         {
14         }
15 
16         //IContributeObjectSink的接口方法,实例化消息接收器
17         public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
18         {
19             return new InterceptorContext(next);
20         }
21 
22         //IContextProperty接口方法,如果该方法返回ture,在新的上下文环境中激活对象
23         public bool IsNewContextOK(Context newCtx)
24         {
25             return true;
26         }
27 
28         //IContextProperty接口方法,提供高级使用
29         public void Freeze(Context newCtx)
30         {
31         }
32 
33         //IContextProperty接口属性
34         public string Name
35         {
36             get { return "DataTrace"; }
37         }
38 
39     }
40 }
  接着是ContextAttribute 
  InterceptorAttribute.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Runtime.Remoting.Contexts;
 6 using System.Runtime.Remoting.Activation;
 7 
 8 namespace AOP
 9 {
10     [AttributeUsage(AttributeTargets.Class)]
11     class InterceptorAttribute : ContextAttribute
12     {
13         public InterceptorAttribute()
14             : base("Interceptor")
15       {
16       }
17       
18       //重载ContextAttribute方法,创建一个上下文环境属性
19       public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
20       {
21           ctorMsg.ContextProperties.Add(new InterceptorProperty());
22       }
23    }    
24 }
至此,我们利用IOC和AOP实现的框架就完成了,虽然程序不长,但涉及的知识比较多。
好了,激动人心的时刻到了,我们现在可以使用我们的框架了。
首先我们设定一个IData接口,还是那句话,使用接口易于维护。
IData.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 
 6 namespace MyIOC
 7 {
 8     interface IData
 9     {
10         void WriteData(String data);
11     }
12 }
实现它:
Data.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using AOP;
 6 
 7 namespace MyIOC
 8 {
 9     [Interceptor]
10     class Data : ContextBoundObject, IData
11     {
12         public void WriteData(String data)
13         {
14             Console.WriteLine(data);
15         }
16     }
17 }
第9行的 [Interceptor] 是一个拦截标志,要不程序怎么知道拦截哪里的方法呢? 在InterceptorAttribute.cs的第14行你也可以看到它。
最后看看我们是怎么WriteData的
Program.cs
View Code 
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Reflection;
 6 using MyIOC.IOC;
 7 
 8 namespace MyIOC
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             IData data = (IData)Instance.GetInstance("IData");
15             data.WriteData("you have write some data");
16             Console.ReadLine();
17         }
18     }
19 }
在主程序中,我们只写了三句话,其实只是两句,我们利用反射得到Data的实例,然后调用WriteData的方法,当然我们的Data也在App.config需要注册,看看配置文件,是不是这样。
看看运行结果:
  you have the permission
  you have write some data
  Log the operation
  OK,我们可以看到我们只利用反射得到Data的实例,然后调用了WriteData的方法,结果在写那句话之前,拦截器拦截到了这个消息,进行了权限验证,然后输出那句话,最后在日志文件中写入的这个操作。
  最后,我希望我这个小例子可以帮助你了解AOP、IOC和框架,当然这只是我浅浅的一些理解,如果有什么错误不足之处,希望大师批评指正,但拒绝人参公鸡。
