在前面的章节中,我们知道了如何利用DynamicProxy程序集从类及接口中进行方法拦截。现在我们可以做一个更有趣并且更实用的操作:我们利用前面章节所讲述的知识来完成一次调用指令的路由操作,借此让我们对Castle AOP的应用层面有一个更感性的认识,因为这一章节要讲述的内容稍微有些复杂,我会试图尽量的将这些内容讲解清楚,由于写文章的水平有限,如有讲解不周的地方也请大家谅解。
先来看看我们的第一个应用场景:
找不到比较好的绘图工具,如果大家有的话推荐一个给我吧,我们先来解释一下这个应用场景:
- 我们有一个对象容器,在这个容器中包含了一系列的对象,这些对象都分别实现了某些接口。
- 我们假设这个对象容器是私有的,不可以被其它的程序集访问,只有一个ICommandPortal(指令门户)接口可以操作容器。
- 外部的程序集可以通过ICommandPortal接口来间接的访问容器里面的一些对象,ICommandPortal会解析用户的请求(Request),并依据解析后的一些信息来调用容器中相应对象的相应的方法,并返回以Response表示的调用结果。
- 每次想通过ICommandPortal接口调用对象容器里面的某个接口实现对象并返回值时,我们都需要:准备Request -> 调用ICommandPortal的Invoke -> 根据返回的Response值取回调用的结果, 所以我们可以考虑通过采用DynamicProxy技术,对需要调的接口调用进行拦截,并转换出相应的Request及Response,就可以方便的完成调用过程。
通过上面罗列出的应用场景需求,我们可以初步确定一些类型。由于类型并不复杂,所以我们也就不逐个解讲了,我们直接看看类型的代码,同时我会在代码中加入适量的注释,大家留意查看:
Request(用户请求)类型的定义
- using System;
- namespace Unit8
- {
- [Serializable]
- public class Request
- {
- private string serviceKey;
- private string commandName;
- private Type[] paramTypes;
- private object[] arguments;
- /// <summary>
- /// 容器中对象的Key值(标识)
- /// </summary>
- public string ServiceKey
- {
- get { return serviceKey; }
- set { serviceKey = value; }
- }
- /// <summary>
- /// 需要调用的方法名称
- /// </summary>
- public string CommandName
- {
- get { return commandName; }
- set { commandName = value; }
- }
- /// <summary>
- /// 向调用的方法传递的参数
- /// </summary>
- public object[] Arguments
- {
- get { return arguments; }
- set { arguments = value; }
- }
- /// <summary>
- /// 调用方法的参数类型,通过这个属性来精确匹配一些有重载的函数
- /// </summary>
- public Type[] ParamTypes
- {
- get { return paramTypes; }
- set { paramTypes = value; }
- }
- public Request()
- {
- }
- public Request(string serviceKey, string commandName,
- Type[] paramTypes, object[] arguments)
- {
- this.serviceKey = serviceKey;
- this.commandName = commandName;
- this.paramTypes = paramTypes;
- this.arguments = arguments;
- }
- }
- }
Response(响应-容器里具体对象方法调用的反回值)类型的定义
- using System;
- namespace Unit8
- {
- [Serializable]
- public class Response
- {
- private readonly object returnValue;
- /// <summary>
- /// 方法调用的返回值
- /// </summary>
- public object ReturnValue
- {
- get { return returnValue; }
- }
- public Response()
- {
- }
- public Response(object returnValue)
- {
- this.returnValue = returnValue;
- }
- }
- }
ICommandPortal接口的定义
- namespace Unit8
- {
- /// <summary>
- /// 指令调用门户
- /// </summary>
- public interface ICommandPortal
- {
- /// <summary>
- /// 根据请求的信息来调用对象容器中相应对象的相应方法,
- /// 并包装返回的值为一个Response
- /// </summary>
- Response Invoke(Request request);
- }
- }
对象容器的实现代码,主体是一个 IDictionary<string, object>
- using System.Collections.Generic;
- namespace Unit8
- {
- /// <summary>
- /// 一个简单的对象容器
- /// </summary>
- public class ObjectContainer
- {
- private readonly IDictionary<string, object> items
- = new SortedList<string, object>();
- public IDictionary<string, object> Items
- {
- get { return items; }
- }
- private ObjectContainer()
- {
- }
- /// <summary>
- /// 单例模式
- /// </summary>
- public static readonly ObjectContainer
- Instance = new ObjectContainer();
- }
- }
ICommandPortal的实现代码
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- namespace Unit8
- {
- public class CommandPortal:ICommandPortal
- {
- public Response Invoke(Request request)
- {
- IDictionary<string, object> Items =
- ObjectContainer.Instance.Items;
- //检测容器是否包含指定的实例对象(按ServiceKey区分)
- //如果不包含则向调用方抛回异常
- if (!Items.ContainsKey(request.ServiceKey))
- return new Response(
- new Exception("Invalid ServiceKey:"
- + request.ServiceKey));
- object invokeObj = Items[request.ServiceKey];
- //设置BindingFlags标志,用于指定方法的搜索条件
- //并通过反射找到相应的方法
- const BindingFlags FindFlags =
- BindingFlags.FlattenHierarchy | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public;
- Type invokeType = invokeObj.GetType();
- MethodInfo methodInfo =
- invokeType.GetMethod(
- request.CommandName,
- FindFlags,
- null,
- request.ParamTypes,
- null);
- if (methodInfo==null)
- return new Response(
- new Exception("Invalid MethodName:"
- + request.CommandName));
- //调用找到的方法,如果调用发生异常,则返回异常的信息
- try
- {
- return new Response(
- methodInfo.Invoke(invokeObj,
- request.Arguments));
- }
- catch(Exception ex)
- {
- return new Response(ex);
- }
- }
- }
- }
至此,我们ICommandPortal、接口实现对象的容器、Request、Response以及这几个类型之间的关系已全部作出实现,接下来,我们来看看如何产生容器内对象的Proxy。首先,我们来实现方法一个拦截器:
方法拦截器的具体实现代码
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using Castle.Core.Interceptor;
- namespace Unit8
- {
- public class CommandInterceptor:IInterceptor
- {
- private readonly ICommandPortal portal;
- private readonly string serviceKey;
- public CommandInterceptor(ICommandPortal portal,
- string serviceKey)
- {
- this.portal = portal;
- this.serviceKey = serviceKey;
- }
- //容器内接口实现对象的Key值
- public string ServiceKey
- {
- get { return serviceKey; }
- }
- //方法调用门户
- public ICommandPortal Portal
- {
- get { return portal; }
- }
- public void Intercept(IInvocation invocation)
- {
- List<Type> paramTypes = new List<Type>();
- foreach (ParameterInfo paramInfo in
- invocation.Method.GetParameters())
- paramTypes.Add(paramInfo.ParameterType);
- //生成Request对象并调用Portal的Invoke方法
- Request request = new Request(
- ServiceKey,
- invocation.Method.Name,
- paramTypes.ToArray(),
- invocation.Arguments);
- Response response = Portal.Invoke(request);
- //如果方法顺利执行,则填充返回值,否则触发异常
- if (response.ReturnValue is Exception)
- throw (Exception)response.ReturnValue;
- invocation.ReturnValue = response.ReturnValue;
- }
- }
- }
有了上面的拦截器,我们可以实现一个Proxy生成工厂,用来生成调用端接口的Proxy:
Proxy生成工厂的代码
- using Castle.Core.Interceptor;
- using Castle.DynamicProxy;
- namespace Unit8
- {
- /// <summary>
- /// Proxy生成工厂,根据ICommandPortal,ServiceKey
- /// 产生一个动态的Proxy
- /// </summary>
- public class PortalProxyFactory
- {
- public T CreateProxy<T>(ICommandPortal commandPortal,string serviceKey)
- {
- ProxyGenerator generator = new ProxyGenerator();
- IInterceptor interceptor
- = new CommandInterceptor(commandPortal, serviceKey);
- T proxy = generator.CreateInterfaceProxyWithoutTarget<T>(interceptor);
- return proxy;
- }
- }
- }
接下来,我们来测试一下整个过程,我们来完成一个接口对象:
一个用于测试的接口及实现
- namespace Unit8
- {
- public interface IPerson
- {
- void SayHello(string name);
- string GetFullName(string firstName, string lastName);
- }
- public class Person:IPerson
- {
- private readonly string innerName;
- public Person(string innerName)
- {
- this.innerName = innerName;
- }
- public string InnerName
- {
- get { return innerName; }
- }
- public void SayHello(string name)
- {
- Console.WriteLine("{0} Say:Hello,{1}!", InnerName, name);
- }
- public string GetFullName(string firstName, string lastName)
- {
- return string.Format("My name is {0} {1}.", firstName, lastName);
- }
- }
- }
所有的工作都已就绪,我们加载整个过程来看看运行的结果:
C#代码
- using System;
- using System.Collections.Generic;
- namespace Unit8
- {
- public class Program
- {
- private static int Main()
- {
- IDictionary<string, object> Items =
- ObjectContainer.Instance.Items;
- //初始化容器里面的对象
- Items.Add("PersonA", new Person("PersonA"));
- Items.Add("PersonB", new Person("PersonB"));
- //初始化ICommandPortal
- ICommandPortal commandPortal = new CommandPortal();
- //初始化ProtalProxyFactory
- PortalProxyFactory proxyFactory = new PortalProxyFactory();
- IPerson personA = proxyFactory.CreateProxy<IPerson>(commandPortal, "PersonA");
- IPerson personB = proxyFactory.CreateProxy<IPerson>(commandPortal, "PersonB");
- IPerson personC = proxyFactory.CreateProxy<IPerson>(commandPortal, "PersonC");
- personA.SayHello("Roger");
- Console.WriteLine(personA.GetFullName("Roger", "Tong"));
- Console.WriteLine("=========================");
- personB.SayHello("Roger");
- Console.WriteLine(personB.GetFullName("Roger", "Tong"));
- Console.WriteLine("=========================");
- //由于"PersonC"在容器中不存在,所以我们测试一下是否可以抛出异常
- try
- {
- personC.SayHello("Roger");
- Console.WriteLine(personC.GetFullName("Roger", "Tong"));
- }
- catch(Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
- Console.ReadLine();
- return 0;
- }
- }
- }
看一下输入的结果:
总结:我们利用DynamicProxy产生了一个客户端调用的Proxy,并且拦截、重组(拼成Request)并转发了相应的一些方法调用的信息,并解析了返回的Response。完成了一个完整的指令路由过程。这个应用可以进行扩展的,大家可以试想一下,如果Request及Response作为字节流的方式,那么就很容易扩展为通过网络的分存式服务门户,具体的代码大家可以自己实现一下。