利用IOC和AOP实现一个简单框架(完整)
实现框架之前,我想先讲一讲为什么我会写这篇文章。
暑假期间,我在一家软件公司实习。在8月15这天(周一)早上,项目小组组长对我说:“IOC和AOP在项目中经常用到,我希望你能用这两种技术实现一个简单框架”。
"IOC”、"AOP",我的天,我听都没听过,这是什么。组长说:"我一会儿会发给你一些资料和一个例子,你再查查资料,学习学习,给你两周时间"。好吧,只能这样,框架还是听过,但没做过,对于一些没做过的东西,人总是觉得很神秘、很深奥,不容易做,但当你做过之后才发现,它们只是人们用一些已有的技术手段实现的一些想法,并不太难。
IOC:(Inversion of Control) 中文意思是“控制反转” 还有一个原理相同的概念是:Dependency Injection(依赖注入)
AOP:Aspect Oriented Programming-面向方面编程,确切的说应是:面向切面编程。
上面这两个概念要想详细了解解决什么问题可以查资料,在这就不多说了。
在查资料和学习的过程中我很痛苦,为什么呢?因为我除了懂得这些原理以外,我还想找个确切的实例,但是找个确切的例子凭的是人品,一般网上说的例子都很零散,有的只给其中的一段代码,你不能看到程序运行的过程;有的只是给出了其中一方面的例子,你也不知道怎样有机的结合起来,你还是不能完全看到整个过程;有的虽然给出了整个实现代码,但还是引用了一些dll文件,你也不能看到这些dll文件中是怎样实现的。有些人说的云里雾里的,不是很懂。于是我经过两周实现一个简单框架之后就想将这个例子共享出来,让后来学习的人能容易入手。
IOC实际好处就是消除耦合或者降低耦合,减轻以后软件维护的难度,一般主要用到反射。
可以使用反射动态地创建类型的实例,将类型绑定(其实就是对象引用实例,绑定我刚开始也不懂)到现有对象,然后,可以调用实例的方法等。
首先我们先做一个通用的可以得到实例的类:Instance.cs
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 publicstaticobject 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配置文件内容如下:
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
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
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 publicvoid 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
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 publicclass 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 returnnull;
49 }
50
51 privatevoid 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 privatevoid 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
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 returnnew InterceptorContext(next);
20 }
21
22 //IContextProperty接口方法,如果该方法返回ture,在新的上下文环境中激活对象
23 publicbool IsNewContextOK(Context newCtx)
24 {
25 returntrue;
26 }
27
28 //IContextProperty接口方法,提供高级使用
29 publicvoid Freeze(Context newCtx)
30 {
31 }
32
33 //IContextProperty接口属性
34 publicstring Name
35 {
36 get { return"DataTrace"; }
37 }
38
39 }
40 }
接着是ContextAttribute
InterceptorAttribute.cs
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 publicoverridevoid GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
20 {
21 ctorMsg.ContextProperties.Add(new InterceptorProperty());
22 }
23 }
24 }
至此,我们利用IOC和AOP实现的框架就完成了,虽然程序不长,但涉及的知识比较多。
好了,激动人心的时刻到了,我们现在可以使用我们的框架了。
首先我们设定一个IData接口,还是那句话,使用接口易于维护。
IData.cs
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
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 publicvoid WriteData(String data)
13 {
14 Console.WriteLine(data);
15 }
16 }
17 }
第9行的 [Interceptor] 是一个拦截标志,要不程序怎么知道拦截哪里的方法呢? 在InterceptorAttribute.cs的第14行你也可以看到它。
最后看看我们是怎么WriteData的
Program.cs
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 staticvoid 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和框架,当然这只是我浅浅的一些理解,如果有什么错误不足之处,希望大师批评指正,但拒绝人参公鸡。